Analyse av endringer i atferd i Go 1.25 encoding/json/v2 og kompatibilitetslaget for v1

Go-pakken encoding/json v2 er en ny implementasjon som er ment å forbedre flere ulemper ved den eksisterende v1 (manglende konsistens, overraskende oppførsel, ytelsesproblemer). Den aktiveres som en eksperimentell funksjon via byggetaggen goexperiment.jsonv2.
Det viktigste å merke seg er at når v2 er aktivert, fungerer v1 som et kompatibilitetslag som emulerer v1-oppførselen oppå v2-implementasjonen. Dette skjer via funksjonen DefaultOptionsV1() i filen v2_v1.go. Med andre ord tilbyr v2 alternativer som kan reprodusere v1-oppførselen fullt ut, samtidig som den introduserer strengere og mer forutsigbare nye standardinnstillinger.
Hovedmålene for v2 er som følger.
- Økt korrekthet og forutsigbarhet: Strengere regler som standard (for eksempel skille mellom store og små bokstaver, og forbud mot dupliserte nøkler) reduserer uventet oppførsel.
- Forbedret ytelse: Parse- og kodingsmotorene er redesignet for økt effektivitet.
- Større fleksibilitet og kontroll: Et detaljert alternativsystem gjør det mulig for utviklere å styre JSON-behandlingen nøyaktig.
Viktige forskjeller i semantikk og oppførsel
Forskjellene mellom v1 og v2 er oppsummert punkt for punkt, med utgangspunkt i filen v2_test.go.
1. Matching av feltnavn (skille mellom store og små bokstaver)
- v1-oppførsel: Ved unmarshaling av JSON-objektmedlemmer til Go-structfelt ignoreres forskjeller i store og små bokstaver (case-insensitive). Både
"FirstName"og"firstname"knyttes til feltetFirstName. - v2-oppførsel: Som standard skilles det mellom store og små bokstaver (case-sensitive), og bare felt som samsvarer nøyaktig, blir knyttet til.
- Begrunnelse for endringen: Matching som ignorerer store og små bokstaver kan føre til uventet oppførsel og medfører ytelsestap når felt uten treff må håndteres. v2 har valgt en tydeligere og mer forutsigbar oppførsel som standard.
- Relaterte alternativer: I v2 kan man eksplisitt aktivere matching uten hensyn til store og små bokstaver for enkeltfelt med taggalternativet
json:"...,case:ignore", eller globalt medjson.MatchCaseInsensitiveNames(true).
2. Endret betydning av taggalternativet omitempty
- v1-oppførsel: Felt utelates basert på Go-verdiens «tomme tilstand». Med «tom tilstand» menes
false,0,nil-pekere eller grensesnitt, samt arrayer, slices, maps og strenger med lengde null. - v2-oppførsel: Felt utelates basert på den kodede JSON-verdiens «tomme tilstand». Det vil si at felt utelates når de kodes som
null,"",{}eller[]. - Begrunnelse for endringen: v1-definisjonen er avhengig av Gos typesystem. v2 tar utgangspunkt i JSON-typesystemet og gir dermed mer konsistent oppførsel. For eksempel utelates verdien
falsefor enbooli v1, mensfalsei v2 ikke utelates, fordi det er en ikke-tom JSON-verdi. I v2 er det lagt til alternativetomitzero, som kan erstatte den tidligere oppførselen deromitemptygjaldt for0ogfalse. - Relaterte alternativer: For å oppnå nøyaktig samme oppførsel som v1 i v2 brukes alternativet
json.OmitEmptyWithLegacyDefinition(true).
3. Endret oppførsel for taggalternativet string
- v1-oppførsel: Gjelder for felt av typene tall, boolske verdier og strenger. Verdien kodes på nytt inn i en JSON-streng (for eksempel
int(42)->"42"). Verdier inne i sammensatte typer (slices, maps osv.) påvirkes ikke rekursivt. - v2-oppførsel: Gjelder bare for tallfelt og anvendes rekursivt. Det vil si at også tall inne i en slice, for eksempel
[]int, kodes som JSON-strenger. - Begrunnelse for endringen: Hovedformålet med alternativet
stringer å forhindre tap av presisjon for 64-bits heltall ved å representere tall som strenger. Oppførselen i v1 var begrenset og lite konsistent. v2 fokuserer på dette kjerneformålet og utvider oppførselen rekursivt for å gjøre den mer nyttig. - Relaterte alternativer: Oppførselen i v1 kan etterlignes med
json.StringifyWithLegacySemantics(true).
4. Marshaling av nil-slices og -maps
- v1-oppførsel:
nil-slices ognil-maps marshales somnull. - v2-oppførsel: Som standard marshales
nil-slices som[](tom array) ognil-maps som{}(tomt objekt). - Begrunnelse for endringen:
niler en implementasjonsdetalj i Go-språket, og det er lite ønskelig å eksponere den i et språkuavhengig JSON-format.[]og{}er mer vanlige uttrykk for tomme samlinger. - Relaterte alternativer: I v2 kan man marshale til
null, slik som i v1, medjson.FormatNilSliceAsNull(true)ellerjson.FormatNilMapAsNull(true).
5. Unmarshaling av arrayer
- v1-oppførsel: Ved unmarshaling til Go-arrayer (
[N]T) gir det ingen feil om lengden på JSON-arrayen avviker fra lengden på Go-arrayen. Hvis JSON-arrayen er kortere, fylles resten med nullverdier, og hvis den er lengre, kastes overskytende elementer. - v2-oppførsel: Lengden på JSON-arrayen må stemme nøyaktig overens med lengden på Go-arrayen. Hvis ikke, oppstår en feil.
- Begrunnelse for endringen: I Go har faste arrayer ofte en lengde som er meningsbærende. Oppførselen i v1 kan føre til stille tap av data. v2 bruker strengere regler for å øke korrektheten.
- Relaterte alternativer: Oppførselen i v1 kan etterlignes med alternativet
json.UnmarshalArrayFromAnyLength(true).
6. Håndtering av time.Duration
- v1-oppførsel:
time.Durationbehandles internt somint64og kodes som et JSON-tall i nanosekunder. - v2-oppførsel: Verdien kodes som en JSON-streng i formatet
"1h2m3s"ved bruk av metodentime.Duration.String(). - Begrunnelse for endringen: Tall i nanosekunder er vanskelige å lese, og den standardiserte strengrepresentasjonen av
time.Durationer mer nyttig. - Relaterte alternativer: Oppførselen i v1 kan brukes med taggalternativet
json:",format:nano"eller med alternativetjson.FormatTimeWithLegacySemantics(true).
7. Håndtering av ugyldig UTF-8
- v1-oppførsel: Ved marshaling og unmarshaling erstattes ugyldige UTF-8-bytes i strenger stille med Unicode-erstatningstegnet (
\uFFFD). - v2-oppførsel: Som standard returneres en feil når ugyldig UTF-8 påtreffes.
- Begrunnelse for endringen: Dette forhindrer stille datakorrupsjon og er i tråd med den strengere JSON-standarden RFC 7493.
- Relaterte alternativer: Oppførselen i v1 kan etterlignes med alternativet
jsontext.AllowInvalidUTF8(true).
8. Håndtering av dupliserte medlemsnavn i objekter
- v1-oppførsel: Et JSON-objekt kan inneholde medlemmer med samme navn flere ganger. Den siste verdien overskriver de tidligere.
- v2-oppførsel: Som standard returneres en feil dersom det finnes dupliserte medlemsnavn.
- Begrunnelse for endringen: RFC 8259 definerer ikke oppførselen ved dupliserte navn, slik at implementasjoner kan oppføre seg ulikt. Dette kan være en kilde til sikkerhetssårbarheter. v2 avviser dette eksplisitt for å øke korrektheten og sikkerheten.
- Relaterte alternativer: Oppførselen i v1 kan etterlignes med alternativet
jsontext.AllowDuplicateNames(true).
Forskjeller i implementasjon og arkitektur
- v1: Er i stor grad avhengig av
decodeStateidecode.goog en manuelt skrevet tilstandsmaskin iscanner.go. Dette er en monolittisk struktur der parselogikk og semantisk analyse er tett koblet sammen. - v2: Arkitekturen er mer modulær.
encoding/json/jsontext: Tilbyr en lavnivå og høyytelses JSON-tokenizer (Decoder) og koder (Encoder). Denne pakken fokuserer utelukkende på JSONs syntaktiske sider.encoding/json/v2: Bygger påjsontextog håndterer den semantiske konverteringen mellom Go-typer og JSON-verdier.- Ved å skille disse lagene fra hverandre er syntaktisk og semantisk analyse atskilt, noe som gir bedre kodeklarhet og ytelse.
Nye API-er og funksjoner i v2
v2 tilbyr svært fleksibel kontroll gjennom alternativsystemet json.Options.
json.Options: En samling av alternativer som endrer oppførselen ved marshaling og unmarshaling.json.JoinOptions(...): Slår flere alternativer sammen til ett.WithMarshalers/WithUnmarshalers: En kraftig funksjon som gjør det mulig å injisere serialiserings- og deserialiseringslogikk for en bestemt type, uten at typen må implementere grensesnitteneMarshalerellerUnmarshaler. Dette er særlig nyttig når man må håndtere typer fra eksterne pakker.- Nye alternativer:
RejectUnknownMembers,Deterministic(false),FormatNilSliceAsNullog andre gir kontroll over oppførsel som ikke var mulig i v1.
Konklusjon
encoding/json v2 er en moderne implementasjon som bygger på erfaringene fra v1 og i betydelig grad forbedrer korrekthet, ytelse og fleksibilitet. Standardoppførselen er blitt strengere, men det avanserte Options-systemet gjør det mulig å reprodusere alle v1-oppførsler fullt ut. Dermed kan eksisterende kode fortsatt fungere, samtidig som man gradvis tar i bruk fordelene med v2.
- Nye prosjekter bør som hovedregel bruke v2.
- Eksisterende prosjekter kan fortsette å bruke
jsonv1uendret, eller migrere tiljsonv2og innføre v2s strengere oppførsel gradvis ved hjelp avDefaultOptionsV1().