GoSuda

Sammenligning af Go 1.25 encoding/json v1 og v2

By lemonmint
views ...

Go's encoding/json package v2 er en ny implementering, der sigter mod at forbedre flere ulemper ved den tidligere v1 (mangel på konsistens, overraskende adfærd, ydelsesproblemer). Dette er en eksperimentel funktion, der aktiveres via goexperiment.jsonv2 build-tagget.

Det vigtigste er, at når v2 er aktiveret, fungerer v1 som et kompatibilitetslag, der emulerer v1's adfærd oven på v2-implementeringen. Dette opnås gennem funktionen DefaultOptionsV1() i filen v2_v1.go. Med andre ord tilbyder v2 muligheder for fuldt ud at genskabe v1's adfærd, samtidig med at den introducerer en ny standardadfærd, der er strengere og mere forudsigelig.

Hovedmålene for v2 er som følger:

  1. Forbedret nøjagtighed og forudsigelighed: Ved at anvende strengere regler som standard (f.eks. store/små bogstaver, ingen duplikerede nøgler) reduceres uventet adfærd.
  2. Forbedret ydeevne: Parse- og kodningsmotoren er redesignet for at øge effektiviteten.
  3. Øget fleksibilitet og kontrol: Et detaljeret options-system er introduceret, hvilket giver udviklere finmasket kontrol over JSON-behandlingen.

Væsentlige forskelle i betydning/adfærd

Forskellene i adfærd mellem v1 og v2 er opsummeret punkt for punkt, med fokus på filen v2_test.go.

1. Matchning af feltnavn (store/små bogstaver)

  • v1-adfærd: Ved unmarshaling af JSON-objektmedlemmer til Go-strukturfelter matches der uden hensyn til store/små bogstaver (case-insensitive). Både "FirstName" og "firstname" mappes til FirstName-feltet.
  • v2-adfærd: Som standard matches der med hensyn til store/små bogstaver (case-sensitive), og kun eksakte match mappes.
  • Årsag til ændring: Case-insensitive matching kan forårsage uventet adfærd og føre til ydelsesforringelse ved håndtering af ikke-matchende felter. v2 har vedtaget en klarere og mere forudsigelig adfærd som standard.
  • Relaterede muligheder: I v2 kan man eksplicit aktivere case-insensitive matching pr. felt ved at bruge json:"...,case:ignore" tag-optionen, eller man kan anvende json.MatchCaseInsensitiveNames(true) globalt.

2. Ændring af betydningen af omitempty tag-optionen

  • v1-adfærd: Felter udelades baseret på Go-værdiernes "tomme tilstand". Her betyder "tom tilstand" false, 0, nil pointere/interfaces, arrays/slices/maps/strings med længde nul.
  • v2-adfærd: Felter udelades baseret på den "tomme tilstand" af de kodede JSON-værdier. Det betyder, at de udelades, hvis de kodes til null, "", {}, [].
  • Årsag til ændring: v1's definition er afhængig af Go's typesystem. v2 tilbyder en mere konsistent adfærd ved at basere sig på JSON-typesystemet. For eksempel udelades en false-værdi af typen bool i v1, men i v2 udelades false ikke, da det er en ikke-tom JSON-værdi. I v2 er omitzero-optionen tilføjet for at erstatte den adfærd, hvor v1's omitempty blev anvendt på 0 eller false.
  • Relaterede muligheder: Hvis man ønsker den samme adfærd som v1 i v2, kan man bruge json.OmitEmptyWithLegacyDefinition(true)-optionen.

3. Ændring af string tag-optionens adfærd

  • v1-adfærd: Anvendes på numeriske, booleske og streng-typefelter. Værdien genkodes i en JSON-streng (f.eks. int(42) -> "42"). Anvendes ikke rekursivt på værdier inden for sammensatte typer (slices, maps osv.).
  • v2-adfærd: Anvendes kun på numeriske typer og anvendes rekursivt. Det betyder, at tal inden for slices som []int også kodes som JSON-strenge.
  • Årsag til ændring: Hovedformålet med string-optionen er at repræsentere tal som strenge for at undgå tab af præcision i 64-bit heltal. v1's adfærd var begrænset og manglede konsistens. v2 fokuserer på dette kerneformål og udvider adfærden rekursivt for at gøre den mere anvendelig.
  • Relaterede muligheder: json.StringifyWithLegacySemantics(true)-optionen kan bruges til at efterligne v1's adfærd.

4. Marshaling af nil slices og maps

  • v1-adfærd: nil slices og nil maps marshales til null.
  • v2-adfærd: Som standard marshales nil slices til [] (tomt array) og nil maps til {} (tomt objekt).
  • Årsag til ændring: nil er en implementeringsdetalje i Go-sproget, og det er uønsket at eksponere dette i det sprog-agnostiske JSON-format. [] eller {} er mere universelle repræsentationer for tomme samlinger.
  • Relaterede muligheder: I v2 kan man marshalere til null som i v1 via json.FormatNilSliceAsNull(true) eller json.FormatNilMapAsNull(true)-optionerne.

5. Unmarshaling af arrays

  • v1-adfærd: Ved unmarshaling til Go-arrays ([N]T) udløses der ikke en fejl, selvom længden af JSON-arrayet adskiller sig fra længden af Go-arrayet. Hvis længden er kortere, fyldes den resterende plads med nulværdier, og hvis den er længere, kasseres de overskydende elementer.
  • v2-adfærd: Længden af JSON-arrayet skal matche nøjagtigt længden af Go-arrayet. Ellers opstår der en fejl.
  • Årsag til ændring: I Go har faste størrelse arrays ofte en vigtig betydning i deres længde. v1's adfærd kunne forårsage lydløst tab af data. v2 øger nøjagtigheden med strengere regler.
  • Relaterede muligheder: json.UnmarshalArrayFromAnyLength(true)-optionen kan bruges til at efterligne v1's adfærd.

6. Håndtering af time.Duration

  • v1-adfærd: time.Duration behandles internt som int64 og kodes som et JSON-tal i nanosekunder.
  • v2-adfærd: time.Duration.String()-metoden bruges til at kode som en JSON-streng i formatet "1h2m3s".
  • Årsag til ændring: Numeriske nanosekunder er svære at læse, og standard strengrepræsentationen af time.Duration er mere nyttig.
  • Relaterede muligheder: Man kan bruge v1's adfærd via json:",format:nano" tag-optionen eller json.FormatTimeWithLegacySemantics(true)-optionen.

7. Håndtering af ugyldig UTF-8

  • v1-adfærd: Ved marshaling/unmarshaling, hvis der er ugyldige UTF-8 bytes i strengen, erstattes de lydløst med Unicode-erstatningstegnet (\uFFFD).
  • v2-adfærd: Som standard, hvis der opstår ugyldig UTF-8, returneres en fejl.
  • Årsag til ændring: For at forhindre lydløs beskadigelse af data og for at overholde den strengere JSON-standard (RFC 7493).
  • Relaterede muligheder: jsontext.AllowInvalidUTF8(true)-optionen kan bruges til at efterligne v1's adfærd.

8. Håndtering af duplikerede objektmedlemsnavne

  • v1-adfærd: Tillader duplikerede medlemsnavne inden for et JSON-objekt. Den sidst forekommende værdi overskriver de tidligere.
  • v2-adfærd: Som standard returneres en fejl, hvis der er duplikerede medlemsnavne.
  • Årsag til ændring: RFC 8259-standarden definerer ikke adfærden for duplikerede navne, hvilket kan føre til forskellig adfærd mellem implementeringer. Dette kan være en kilde til sikkerhedssårbarheder. v2 afviser dette eksplicit for at øge nøjagtighed og sikkerhed.
  • Relaterede muligheder: jsontext.AllowDuplicateNames(true)-optionen kan bruges til at efterligne v1's adfærd.

Implementerings- og arkitekturelle forskelle

  • v1: Er i høj grad afhængig af decodeState i decode.go og en manuelt skrevet state machine i scanner.go. Dette er en monolitisk struktur, hvor parsing-logik og semantisk analyse er stærkt forbundet.
  • v2: Arkitekturen er mere modulopbygget.
    • encoding/json/jsontext: Leverer en lavniveau, højtydende JSON-tokeniser (Decoder) og en encoder (Encoder). Denne pakke fokuserer udelukkende på JSON's syntaktiske aspekter.
    • encoding/json/v2: Håndterer den semantiske konvertering mellem Go-typer og JSON-værdier baseret på jsontext.
    • Denne opdeling adskiller syntaktisk analyse fra semantisk analyse, hvilket forbedrer kodeklarhed og ydeevne.

Nye API'er og funktioner i v2

v2 tilbyder meget fleksibel kontrol via json.Options-systemet.

  • json.Options: En samling af muligheder, der ændrer marshalling/unmarshalling-adfærden.
  • json.JoinOptions(...): Sammenslutter flere muligheder til én.
  • WithMarshalers / WithUnmarshalers: En kraftfuld funktion, der gør det muligt at injicere serialiserings-/deserialiseringslogik for specifikke typer uden at skulle implementere Marshaler/Unmarshaler-interfacet. Dette er især nyttigt ved håndtering af typer fra eksterne pakker.
  • Nye muligheder: Forskellige adfærdskontroller, der ikke var mulige i v1, såsom RejectUnknownMembers, Deterministic(false), FormatNilSliceAsNull, er nu tilgængelige.

Konklusion

encoding/json v2 er en moderne implementering, der bygger på erfaringerne fra v1 og forbedrer nøjagtighed, ydeevne og fleksibilitet betydeligt. Selvom standardadfærden er blevet strengere, understøtter det sofistikerede Options-system fuldt ud alle v1's adfærd, hvilket gør det muligt at indføre v2's fordele gradvist, mens man opretholder kompatibilitet med eksisterende kode.

  • For nye projekter anbefales det at bruge v2 som standard.
  • Eksisterende projekter kan fortsat bruge jsonv1 eller migrere til jsonv2 og gradvist introducere v2's strengere adfærd via DefaultOptionsV1().