GoSuda

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

By lemonmint views ...

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:

  1. 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.
  2. Zlepšenie výkonu: Parsovací a kódovací engine bol prepracovaný s cieľom zvýšiť efektivitu.
  3. 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, tak firstname sa namapujú na pole FirstName.
  • 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, nil ukazovateľ/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 false typu bool vynechá, kým v v2 sa nevynechá, pretože false je neprázdna hodnota JSON. V v2 možno pridaním voľby omitzero nahradiť správanie v1, pri ktorom omitempty pôsobilo na 0 alebo false.
  • 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 string je 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 nil slice a nil mapa sa marshalujú ako null.
  • Správanie v2: Predvolene sa nil slice marshaluje ako [] (prázdne pole) a nil mapa ako {} (prázdny objekt).
  • Dôvod zmeny: nil je 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) alebo json.FormatNilMapAsNull(true) marshalovať ako null, 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.Duration sa interne považuje za int64 a 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ódy time.Duration.String().
  • Dôvod zmeny: Číselné nanosekundy sa ťažšie čitajú a štandardné reťazcové vyjadrenie time.Duration je užitočnejšie.
  • Súvisiace možnosti: Správanie v1 možno použiť prostredníctvom tagu json:",format:nano" alebo voľby json.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 decodeState v súbore decode.go a na ručne napísaný stavový automat (state machine) v súbore scanner.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áklade jsontext zabezpeč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), FormatNilSliceAsNull a ď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ť na jsonv2 a postupne zavádzať prísnejšie správanie v2 pomocou DefaultOptionsV1().