Analyse af adfærdsændringerne i Go 1.25 encoding/json/v2 og v1-kompatibilitetslaget

Go-pakken encoding/json v2 er en ny implementering, der har til formål at forbedre flere ulemper ved den eksisterende v1 (manglende konsistens, overraskende adfærd, ydelsesproblemer). Den er en eksperimentel funktion, som aktiveres via build-tagget goexperiment.jsonv2.
Det vigtigste punkt er, at når v2 er aktiveret, fungerer v1 som et kompatibilitetslag, der emulerer v1's adfærd oven på v2-implementeringen. Dette sker via funktionen DefaultOptionsV1() i filen v2_v1.go. Med andre ord tilbyder v2 muligheden for at reproducere v1's adfærd fuldt ud, samtidig med at den introducerer nye standardadfærd, der er strengere og mere forudsigelig.
Hovedmålene for v2 er følgende:
- Forbedret korrekthed og forudsigelighed: Som standard anvendes strengere regler (f.eks. versalfølsomhed og forbud mod dublerede nøgler) for at reducere uventet adfærd.
- Forbedret ydelse: Parsing- og kodningsmotoren er redesignet for at øge effektiviteten.
- Større fleksibilitet og kontrol: Et detaljeret optionssystem introduceres, så udviklere kan styre JSON-behandlingen præcist.
Væsentlige forskelle i betydning og adfærd
Med udgangspunkt i filen v2_test.go er forskellene i adfærd mellem v1 og v2 opstillet punkt for punkt.
1. Matchning af feltnavne (versalfølsomhed)
- v1-adfærd: Ved unmarshaling af JSON-objektmedlemmer til Go-structfelter matches der uden hensyn til versaler (case-insensitive). Både
"FirstName"og"firstname"mappes til feltetFirstName. - v2-adfærd: Som standard er matchningen versalfølsom (case-sensitive), og kun felter, der matcher præcist, mappes.
- Begrundelse for ændringen: Matchning uden hensyn til versaler kan være årsag til uventet adfærd og forårsager ydelsestab ved håndtering af felter, der ikke matcher. v2 har valgt en tydeligere og mere forudsigelig adfærd som standard.
- Relevante optioner: I v2 kan man eksplicit aktivere versaluafhængig matchning for enkelte felter med tag-optionen
json:"...,case:ignore", eller anvendejson.MatchCaseInsensitiveNames(true)globalt.
2. Ændret betydning af tag-optionen omitempty
- v1-adfærd: Felter udelades ud fra Go-værdiens "tomme tilstand". Med "tom tilstand" menes
false,0,nil-pointere/-interfaces samt arrays/slices/maps/strenge med længden 0. - v2-adfærd: Felter udelades ud fra den kodede JSON-værdis "tomme tilstand". Det vil sige, at felter udelades, når de kodes til
null,"",{}eller[]. - Begrundelse for ændringen: v1's definition er afhængig af Go's typesystem. v2 tager udgangspunkt i JSON's typesystem og giver dermed en mere konsistent adfærd. For eksempel udelades værdien
falseaf typebooli v1, men i v2 udeladesfalseikke, da det er en ikke-tom JSON-værdi. I v2 er optionenomitzerotilføjet, så den kan erstatte v1's adfærd, hvoromitemptygjaldt for0ogfalse. - Relevante optioner: Ønsker man samme adfærd som v1 i v2, anvendes
json.OmitEmptyWithLegacyDefinition(true).
3. Ændret adfærd for tag-optionen string
- v1-adfærd: Gælder for felter af typen tal, boolean og streng. Værdien kodes igen ind i en JSON-streng (f.eks.
int(42)->"42"). Værdier inden i sammensatte typer (slices, maps osv.) påvirkes ikke rekursivt. - v2-adfærd: Gælder kun for talttyper, og anvendes rekursivt. Det betyder, at tal inden i en slice som
[]intogså kodes til JSON-strenge. - Begrundelse for ændringen: Hovedformålet med optionen
stringer at forhindre præcisionstab ved 64-bit heltal ved at repræsentere tal som strenge. v1's adfærd var begrænset og manglede konsistens. v2 fokuserer på dette kernebehov og udvider adfærden rekursivt for at gøre den mere nyttig. - Relevante optioner: Med
json.StringifyWithLegacySemantics(true)kan v1's adfærd efterlignes.
4. Marshaling af nil-slices og -maps
- v1-adfærd:
nil-slices ognil-maps marshales tilnull. - v2-adfærd: Som standard marshales
nil-slices til[](tomt array) ognil-maps til{}(tomt objekt). - Begrundelse for ændringen:
niler en implementeringsdetalje i sproget Go, og det er uhensigtsmæssigt at eksponere den i et sprogneutralt JSON-format.[]og{}er mere udbredte repræsentationer af tomme samlinger. - Relevante optioner: I v2 kan man med
json.FormatNilSliceAsNull(true)ellerjson.FormatNilMapAsNull(true)marshale tilnullligesom i v1.
5. Unmarshaling af arrays
- v1-adfærd: Ved unmarshaling til et Go-array (
[N]T) forårsager det ingen fejl, hvis længden af JSON-arrayet afviger fra længden af Go-arrayet. Er JSON-arrayet kortere, udfyldes de resterende pladser med nulværdier, og er det længere, kasseres overskydende elementer. - v2-adfærd: Længden af JSON-arrayet skal præcis svare til længden af Go-arrayet. Ellers opstår der en fejl.
- Begrundelse for ændringen: I Go har faste arraystørrelser ofte en betydning, der afhænger af længden. v1's adfærd kan føre til lydløst datatab. v2 indfører strengere regler for at øge korrektheden.
- Relevante optioner: Med
json.UnmarshalArrayFromAnyLength(true)kan v1's adfærd efterlignes.
6. Håndtering af time.Duration
- v1-adfærd:
time.Durationbehandles internt somint64og kodes som et JSON-tal i nanosekunder. - v2-adfærd: Kodes som en JSON-streng i formatet
"1h2m3s"ved hjælp af metodentime.Duration.String(). - Begrundelse for ændringen: Tal i nanosekunder er svære at læse, og
time.Duration's standardstrengrepræsentation er mere anvendelig. - Relevante optioner: Med tag-optionen
json:",format:nano"eller optionenjson.FormatTimeWithLegacySemantics(true)kan v1's adfærd anvendes.
7. Håndtering af ugyldig UTF-8
- v1-adfærd: Ved marshaling/unmarshaling erstattes ugyldige UTF-8-bytes i strenge lydløst med Unicode-erstatningstegnet (
\uFFFD). - v2-adfærd: Som standard returneres en fejl, når der stødes på ugyldig UTF-8.
- Begrundelse for ændringen: Formålet er at forhindre lydløs datakorruption og at overholde den strengere JSON-standard (RFC 7493).
- Relevante optioner: Med
jsontext.AllowInvalidUTF8(true)kan v1's adfærd efterlignes.
8. Håndtering af dublerede objektmedlemsnavne
- v1-adfærd: Et JSON-objekt må indeholde medlemmer med samme navn. Den sidst forekommende værdi overskriver de tidligere.
- v2-adfærd: Som standard returneres en fejl, hvis der findes dublerede medlemsnavne.
- Begrundelse for ændringen: RFC 8259 definerer ikke adfærden for dublerede navne, hvilket kan give forskellig adfærd mellem implementeringer. Dette kan være årsag til sikkerhedssårbarheder. v2 afviser dem eksplicit for at øge korrektheden og sikkerheden.
- Relevante optioner: Med
jsontext.AllowDuplicateNames(true)kan v1's adfærd efterlignes.
Forskelle i implementering og arkitektur
- v1: Er i høj grad afhængig af
decodeStateidecode.goog den manuelt skrevne tilstandsmaskine (state machine) iscanner.go. Det er en monolitisk struktur, hvor parsinglogik og semantisk analyse er tæt koblet. - v2: Arkitekturen er mere modulær.
encoding/json/jsontext: Leverer en lavniveau-tokenizer (Decoder) og encoder (Encoder) med høj ydelse. Pakken fokuserer udelukkende på JSON's syntaktiske aspekter.encoding/json/v2: Bygger påjsontextog håndterer den semantiske konvertering mellem Go-typer og JSON-værdier.- Ved denne opdeling adskilles syntaktisk og semantisk analyse, hvilket forbedrer kodens klarhed og ydelse.
Nye API'er og funktioner i v2
v2 tilbyder meget fleksibel styring via optionssystemet json.Options.
json.Options: En samling af optioner, der ændrer adfærden ved marshaling/unmarshaling.json.JoinOptions(...): Sammenfletter flere optioner til én.WithMarshalers/WithUnmarshalers: En kraftfuld funktion, der gør det muligt at indsætte serialiserings- og deserialiseringslogik for en bestemt type uden at implementere grænsefladerneMarshaler/Unmarshaler. Den er særligt nyttig ved håndtering af typer fra eksterne pakker.- Nye optioner:
RejectUnknownMembers,Deterministic(false),FormatNilSliceAsNullm.fl. gør det muligt at styre adfærden på måder, der ikke var mulige i v1.
Konklusion
encoding/json v2 er en moderne implementering, der bygger på erfaringerne fra v1 og væsentligt forbedrer korrekthed, ydelse og fleksibilitet. Standardadfærden er blevet strengere, men via det avancerede Options-system understøtter v2 fuldt ud alle v1's adfærdsmønstre. Dermed kan eksisterende kode bevares kompatibel, mens man gradvist tager v2's fordele i brug.
- Nye projekter bør som udgangspunkt anvende v2.
- Eksisterende projekter kan enten fortsætte med
jsonv1uændret, eller migrere tiljsonv2og gradvist indføre v2's strengere adfærd viaDefaultOptionsV1().