Porovnání encoding/json v1 a v2 v Go 1.25
Balíček encoding/json
v2 jazyka Go představuje novou implementaci, která vylepšuje několik nedostatků (nedostatečná konzistence, překvapivé chování, problémy s výkonem) stávající verze v1. Jedná se o experimentální funkci, která se aktivuje pomocí build tagu goexperiment.jsonv2
.
Nejdůležitější je, že po aktivaci v2 se v1 chová jako kompatibilní vrstva emulující chování v1 nad implementací v2. Toho je dosaženo prostřednictvím funkce DefaultOptionsV1()
v souboru v2_v1.go
. To znamená, že v2 poskytuje možnost plně reprodukovat chování v1 a zároveň zavádí nové, přísnější a předvídatelnější výchozí chování.
Hlavní cíle v2 jsou následující:
- Zlepšení přesnosti a předvídatelnosti: Aplikuje přísnější pravidla (např. rozlišování velkých a malých písmen, zákaz duplicitních klíčů) pro snížení neočekávaného chování.
- Zlepšení výkonu: Přepracováním parsovacího a kódovacího enginu byla zvýšena efektivita.
- Zvýšení flexibility a kontroly: Zavedením detailního systému Options umožňuje vývojářům jemně kontrolovat způsob zpracování JSON.
Klíčové sémantické/provozní rozdíly
Rozdíly v chování mezi v1 a v2 jsou uspořádány po položkách, přičemž se zaměřujeme na soubor v2_test.go
.
1. Přizpůsobení názvů polí (rozlišování velkých a malých písmen)
- Chování v1: Při unmarshalling JSON objektových členů do Go struct polí, ignoruje se rozlišování velkých a malých písmen (case-insensitive) pro shodu. Jak
"FirstName"
, tak"firstname"
se mapují na poleFirstName
. - Chování v2: Ve výchozím nastavení rozlišuje velká a malá písmena (case-sensitive) a mapuje pouze přesně odpovídající pole.
- Důvod změny: Přizpůsobení bez rozlišování velkých a malých písmen může být příčinou neočekávaného chování a způsobuje snížení výkonu při zpracování neodpovídajících polí. v2 přijalo jasnější a předvídatelnější chování jako výchozí.
- Související Option: Ve v2 lze explicitně povolit ignorování velkých a malých písmen pro jednotlivá pole pomocí tag Option
json:"...,case:ignore"
, nebo lze globálně použít Optionjson.MatchCaseInsensitiveNames(true)
.
2. Změna významu tagu omitempty
Option
- Chování v1: Pole jsou vynechána na základě „prázdného stavu“ Go hodnoty. „Prázdný stav“ zde znamená
false
,0
,nil
pointer/interface, a pole/slice/map/string s nulovou délkou. - Chování v2: Pole jsou vynechána na základě „prázdného stavu“ kódované JSON hodnoty. To znamená, že jsou vynechána, pokud jsou kódována jako
null
,""
,{}
,[]
. - Důvod změny: Definice v1 je závislá na Go type system. v2 poskytuje konzistentnější chování založené na JSON type system. Například, ve v1 je hodnota
false
typubool
vynechána, ale ve v2false
není prázdná JSON hodnota, takže není vynechána. v2 přidalo Optionomitzero
, které může nahradit chováníomitempty
v1 aplikované na0
nebofalse
. - Související Option: Pokud chcete ve v2 stejné chování jako ve v1, použijte Option
json.OmitEmptyWithLegacyDefinition(true)
.
3. Změna chování string
tag Option
- Chování v1: Aplikuje se na pole typu číslo, boolean a string. Kóduje danou hodnotu zpět do JSON stringu (např.
int(42)
->"42"
). Rekurzivně se neaplikuje na hodnoty uvnitř složených typů (slices, maps atd.). - Chování v2: Aplikuje se pouze na číselné typy a aplikuje se rekurzivně. To znamená, že i čísla uvnitř slices jako
[]int
jsou kódována jako JSON stringy. - Důvod změny: Hlavním účelem
string
Option je vyjádřit čísla jako řetězce, aby se zabránilo ztrátě přesnosti 64bitových celých čísel. Chování v1 bylo omezené a nekonzistentní. v2 se zaměřilo na tento klíčový účel a rekurzivně rozšířilo chování, aby bylo užitečnější. - Související Option: Chování v1 lze simulovat pomocí Option
json.StringifyWithLegacySemantics(true)
.
4. Marshalling nil
slice a map
- Chování v1:
nil
slices anil
maps jsou marshallovány jakonull
. - Chování v2: Ve výchozím nastavení jsou
nil
slices marshallovány jako[]
(prázdné pole) anil
maps jako{}
(prázdný objekt). - Důvod změny:
nil
je implementační detail jazyka Go a jeho vystavení JSON formátu, který je nezávislý na jazyce, není žádoucí.[]
nebo{}
pro reprezentaci prázdných kolekcí je univerzálnější reprezentace. - Související Option: Ve v2 lze marshallovat jako
null
podobně jako ve v1 pomocí Optionjson.FormatNilSliceAsNull(true)
nebojson.FormatNilMapAsNull(true)
.
5. Unmarshalling polí
- Chování v1: Při unmarshalling do Go pole (
[N]T
) nevyvolává chybu, i když se délka JSON pole liší od délky Go pole. Pokud je délka kratší, zbývající prostor se vyplní nulovými hodnotami; pokud je delší, přebytek se zahodí. - Chování v2: Délka JSON pole se musí přesně shodovat s délkou Go pole. V opačném případě dojde k chybě.
- Důvod změny: V Go mají pole s pevnou velikostí často důležitý význam pro jejich délku. Chování v1 by mohlo vést k tiché ztrátě dat. v2 zvýšilo přesnost pomocí přísnějších pravidel.
- Související Option: Chování v1 lze simulovat pomocí Option
json.UnmarshalArrayFromAnyLength(true)
.
6. Zpracování time.Duration
- Chování v1:
time.Duration
je interně považováno zaint64
a je kódováno jako JSON číslo v nanosekundách. - Chování v2: Používá metodu
time.Duration.String()
k zakódování jako JSON string ve formátu jako"1h2m3s"
. - Důvod změny: Nanosekundy v číslech jsou špatně čitelné a standardní stringová reprezentace
time.Duration
je užitečnější. - Související Option: Chování v1 lze použít pomocí tag Option
json:",format:nano"
nebo Optionjson.FormatTimeWithLegacySemantics(true)
.
7. Zpracování neplatného UTF-8
- Chování v1: Při marshallingu/unmarshallingu, pokud jsou v řetězci neplatné UTF-8 bajty, jsou tiše nahrazeny náhradním znakem Unicode (
\uFFFD
). - Chování v2: Ve výchozím nastavení, pokud narazí na neplatné UTF-8, vrátí chybu.
- Důvod změny: Aby se zabránilo tichému poškození dat a aby se dodržovaly přísnější JSON standardy (RFC 7493).
- Související Option: Chování v1 lze simulovat pomocí Option
jsontext.AllowInvalidUTF8(true)
.
8. Zpracování duplicitních názvů členů objektu
- Chování v1: Povoluje duplicitní názvy členů v JSON objektu. Poslední hodnota přepisuje předchozí.
- Chování v2: Ve výchozím nastavení, pokud existují duplicitní názvy členů, vrátí chybu.
- Důvod změny: Standard RFC 8259 nedefinuje chování duplicitních názvů, což může vést k odlišnému chování v různých implementacích. To může být zdrojem bezpečnostních zranitelností. v2 to explicitně odmítá, čímž zvyšuje přesnost a bezpečnost.
- Související Option: Chování v1 lze simulovat pomocí Option
jsontext.AllowDuplicateNames(true)
.
Rozdíly v implementaci a architektuře
- v1: Silně se spoléhá na
decodeState
vdecode.go
a ručně napsaný state machine vscanner.go
. Jedná se o monolitickou strukturu, kde jsou logika parsování a sémantická analýza silně propojeny. - v2: Architektura je modulárnější.
encoding/json/jsontext
: Poskytuje nízkoúrovňový, vysoce výkonný JSON tokenizer (Decoder
) a encoder (Encoder
). Tento balíček se zaměřuje pouze na syntaktické aspekty JSON.encoding/json/v2
: Na základějsontext
zpracovává sémantickou konverzi mezi Go typy a JSON hodnotami.- Toto oddělení zlepšilo jasnost kódu a výkon oddělením syntaktické a sémantické analýzy.
Nové API a funkce v2
v2 poskytuje velmi flexibilní ovládání prostřednictvím systému json.Options
.
json.Options
: Sada Optionů, které mění chování marshallingu/unmarshallingu.json.JoinOptions(...)
: Slučuje více Optionů do jednoho.WithMarshalers
/WithUnmarshalers
: Výkonná funkce, která umožňuje vkládat logiku serializace/deserializace pro konkrétní typy bez implementace rozhraníMarshaler
/Unmarshaler
. To je zvláště užitečné při zpracování typů z externích balíčků.- Nové Options:
RejectUnknownMembers
,Deterministic(false)
,FormatNilSliceAsNull
a další, které nebyly možné ve v1, jsou nyní k dispozici pro ovládání různých chování.
Závěr
encoding/json
v2 je moderní implementace, která výrazně zlepšuje přesnost, výkon a flexibilitu na základě zkušeností s v1. Ačkoli je výchozí chování přísnější, sofistikovaný systém Options
plně podporuje všechna chování v1, což umožňuje postupně zavádět výhody v2 při zachování kompatibility se stávajícím kódem.
- Pro nové projekty se doporučuje používat v2 jako výchozí.
- Stávající projekty mohou buď nadále používat
jsonv1
, nebo migrovat najsonv2
a pomocíDefaultOptionsV1()
postupně zavádět přísné chování v2.