Analýza zmien správania encoding/json/v2 v Go 1.25 a vrstvy kompatibility s v1

Balík encoding/json v2 v Go je novou implementáciou, ktorej cieľom je odstrániť viaceré nedostatky pôvodnej verzie v1 (nedostatočná konzistentnosť, prekvapivé správanie, problémy s výkonom). Ide o experimentálnu funkciu, ktorá sa aktivuje prostredníctvom build tagu goexperiment.jsonv2.
Najdôležitejšou skutočnosťou je, že ak je v2 aktivované, v1 funguje ako kompatibilná vrstva, ktorá na implementácii v2 emuluje správanie v1. Deje sa tak prostredníctvom funkcie DefaultOptionsV1() v súbore v2_v1.go. To znamená, že v2 poskytuje možnosti, ktorými možno správanie v1 úplne reprodukovať, a zároveň ponúka prísnejšie a predvídateľnejšie nové predvolené správanie.
Hlavné ciele v2 sú nasledujúce:
- Zlepšenie správnosti a predvídateľnosti: Predvolene uplatňuje prísnejšie pravidlá (napríklad rozlišovanie veľkých a malých písmen, zákaz duplicitných kľúčov), čím znižuje výskyt neočakávaného správania.
- Zlepšenie výkonu: Parsovací a kódovací engine bol prepracovaný s cieľom zvýšiť efektivitu.
- Rozšírenie flexibility a kontroly: Zavádza podrobný systém možností, ktorý vývojárom umožňuje jemne riadiť spracovanie JSON.
Hlavné významové a behaviorálne rozdiely
Rozdiely medzi v1 a v2 sú zhrnuté po položkách so zameraním na súbor v2_test.go.
1. Porovnávanie názvov polí (rozlišovanie veľkých a malých písmen)
- Správanie v1: Pri unmarshalovaní členov JSON objektu do polí štruktúry v Go sa ignoruje veľkosť písmen (case-insensitive). Ako
FirstName, takfirstnamesa namapujú na poleFirstName. - Správanie v2: Predvolene rozlišuje veľkosť písmen (case-sensitive) a mapuje iba polia, ktoré sa presne zhodujú.
- Dôvod zmeny: Porovnávanie bez ohľadu na veľkosť písmen môže viesť k neočakávanému správaniu a pri spracovaní nezhodných polí spôsobuje zníženie výkonu. v2 prijíma jasnejšie a predvídateľnejšie správanie ako predvolené.
- Súvisiace možnosti: V v2 možno pomocou tagu
json:"...,case:ignore"explicitne povoliť ignorovanie veľkosti písmen pre jednotlivé polia alebo globálne aplikovaťjson.MatchCaseInsensitiveNames(true).
2. Zmena významu voľby omitempty
- Správanie v1: Polia sa vynechávajú na základe "prázdneho stavu" hodnoty v Go. "Prázdnym stavom" sa rozumie
false,0,nilukazovateľ/rozhranie a pole, reťazec, slice alebo mapa s dĺžkou 0. - Správanie v2: Polia sa vynechávajú na základe "prázdneho stavu" zakódovanej hodnoty JSON. To znamená, že sa vynechajú, ak sú zakódované ako
null,"",{}alebo[]. - Dôvod zmeny: Definícia v1 závisí od typového systému Go. v2 vychádza z typového systému JSON a poskytuje tak konzistentnejšie správanie. Napríklad v v1 sa hodnota
falsetypuboolvynechá, kým v v2 sa nevynechá, pretožefalseje neprázdna hodnota JSON. V v2 možno pridaním voľbyomitzeronahradiť správanie v1, pri ktoromomitemptypôsobilo na0alebofalse. - Súvisiace možnosti: Ak sa v v2 požaduje správanie zhodné s v1, použije sa voľba
json.OmitEmptyWithLegacyDefinition(true).
3. Zmena správania voľby string
- Správanie v1: Použije sa pri poliach číselného, logického a reťazcového typu. Príslušná hodnota sa znovu zakóduje do reťazca JSON (napríklad
int(42)->"42"). Na hodnoty vnútri zložených typov (slice, mapa a pod.) sa nevzťahuje rekurzívne. - Správanie v2: Vzťahuje sa iba na číselné typy a uplatňuje sa rekurzívne. To znamená, že aj čísla vnútri slice, napríklad
[]int, sa zakódujú ako reťazce JSON. - Dôvod zmeny: Hlavným účelom voľby
stringje vyjadriť čísla ako reťazce, aby sa predišlo strate presnosti 64-bitových celých čísel. Správanie v1 bolo obmedzené a málo konzistentné. v2 sa sústreďuje na tento kľúčový účel a rozširuje správanie rekurzívne, čím je užitočnejšie. - Súvisiace možnosti: Správanie v1 možno napodobniť voľbou
json.StringifyWithLegacySemantics(true).
4. Marshalovanie hodnôt nil slice a mapy
- Správanie v1: Hodnoty
nilslice anilmapa sa marshalujú akonull. - Správanie v2: Predvolene sa
nilslice marshaluje ako[](prázdne pole) anilmapa ako{}(prázdny objekt). - Dôvod zmeny:
nilje implementačný detail jazyka Go a sprístupňovať ho v jazykovo neutrálnom formáte JSON nie je vhodné. Zápisy[]a{}, ktoré reprezentujú prázdne kolekcie, sú univerzálnejším vyjadrením. - Súvisiace možnosti: V v2 možno pomocou
json.FormatNilSliceAsNull(true)alebojson.FormatNilMapAsNull(true)marshalovať akonull, rovnako ako v v1.
5. Unmarshalovanie polí
- Správanie v1: Pri unmarshalovaní do poľa Go (
[N]T) nevyvolá dĺžka poľa JSON chybu, ak sa líši od dĺžky poľa Go. Ak je pole kratšie, zvyšné miesto sa vyplní nulovými hodnotami, a ak je dlhšie, prebytok sa zahodí. - Správanie v2: Dĺžka poľa JSON musí presne zodpovedať dĺžke poľa Go. V opačnom prípade vznikne chyba.
- Dôvod zmeny: V Go má dĺžka polí pevnej veľkosti často zásadný význam. Správanie v1 môže spôsobiť tiché straty dát. v2 prísnejšími pravidlami zvyšuje správnosť.
- Súvisiace možnosti: Správanie v1 možno napodobniť voľbou
json.UnmarshalArrayFromAnyLength(true).
6. Spracovanie time.Duration
- Správanie v1:
time.Durationsa interne považuje zaint64a kóduje sa ako číslo JSON v nanosekundách. - Správanie v2: Kóduje sa ako reťazec JSON vo formáte, napríklad
"1h2m3s", pomocou metódytime.Duration.String(). - Dôvod zmeny: Číselné nanosekundy sa ťažšie čitajú a štandardné reťazcové vyjadrenie
time.Durationje užitočnejšie. - Súvisiace možnosti: Správanie v1 možno použiť prostredníctvom tagu
json:",format:nano"alebo voľbyjson.FormatTimeWithLegacySemantics(true).
7. Spracovanie neplatného UTF-8
- Správanie v1: Pri marshalovaní/unmarshalovaní sa neplatné bajty UTF-8 v reťazcoch ticho nahradia Unicode náhradným znakom (
\uFFFD). - Správanie v2: Predvolene pri narazení na neplatný UTF-8 vracia chybu.
- Dôvod zmeny: Cieľom je zabrániť tichému poškodeniu dát a dodržiavať prísnejší štandard JSON (RFC 7493).
- Súvisiace možnosti: Správanie v1 možno napodobniť voľbou
jsontext.AllowInvalidUTF8(true).
8. Spracovanie duplicitných názvov členov objektu
- Správanie v1: V objekte JSON sa povoľuje výskyt členov s rovnakým názvom. Posledná hodnota prepíše predchádzajúce.
- Správanie v2: Predvolene pri výskyte duplicitného názvu člena vracia chybu.
- Dôvod zmeny: Štandard RFC 8259 nedefinuje správanie pri duplicitných názvoch, preto sa implementácie môžu líšiť. Môže to byť príčinou bezpečnostných zraniteľností. v2 to explicitne odmieta, čím zvyšuje správnosť a bezpečnosť.
- Súvisiace možnosti: Správanie v1 možno napodobniť voľbou
jsontext.AllowDuplicateNames(true).
Rozdiely v implementácii a architektúre
- v1: Výrazne sa spolieha na
decodeStatev súboredecode.goa na ručne napísaný stavový automat (state machine) v súborescanner.go. Ide o monolitickú štruktúru, v ktorej sú parsovacia logika a sémantická analýza silne previazané. - v2: Architektúra je modulárnejšia.
encoding/json/jsontext: Poskytuje nízkoúrovňový, vysoko výkonný tokenizér JSON (Decoder) a kodér (Encoder). Tento balík sa sústreďuje výhradne na syntaktickú stránku JSON.encoding/json/v2: Na základejsontextzabezpečuje sémantickú konverziu medzi typmi Go a hodnotami JSON.- Vďaka tomuto oddeleniu sú syntaktická a sémantická analýza oddelené, čo zlepšuje prehľadnosť kódu a výkon.
Nové API a funkcie v2
v2 poskytuje veľmi flexibilné riadenie prostredníctvom systému json.Options.
json.Options: Súbor možností, ktoré menia správanie pri marshalovaní/unmarshalovaní.json.JoinOptions(...): Zlúči viacero možností do jednej.WithMarshalers/WithUnmarshalers: Výkonná funkcia, ktorá umožňuje vložiť logiku serializácie/deserializácie pre konkrétny typ bez implementácie rozhraníMarshaler/Unmarshaler. Je obzvlášť užitočná pri spracovaní typov z externých balíkov.- Nové možnosti:
RejectUnknownMembers,Deterministic(false),FormatNilSliceAsNulla ďalšie umožňujú riadenie správania, ktoré v v1 nebolo možné.
Záver
encoding/json v2 je moderná implementácia, ktorá na základe skúseností s v1 výrazne zlepšuje správnosť, výkon a flexibilitu. Predvolené správanie sa sprísnilo, no prostredníctvom sofistikovaného systému Options možno plne podporiť všetko správanie v1, vďaka čomu sa zachová kompatibilita s existujúcim kódom a výhody v2 je možné zavádzať postupne.
- Pre nové projekty sa odporúča používať v2 ako predvolenú možnosť.
- Existujúce projekty môžu buď ponechať
jsonv1, alebo prejsť najsonv2a postupne zavádzať prísnejšie správanie v2 pomocouDefaultOptionsV1().