Analys av beteendeförändringar i Go 1.25 encoding/json/v2 och kompatibilitetslagret för v1

Paketet encoding/json v2 i Go är en ny implementation som syftar till att förbättra flera brister i den befintliga v1 (bristande konsekvens, överraskande beteenden, prestandaproblem). Den aktiveras via byggtaggen goexperiment.jsonv2 och är en experimentell funktion.
Det viktigaste är att när v2 är aktiverat fungerar v1 som ett kompatibilitetslager ovanpå v2-implementationen, som emulerar v1:s beteende. Detta sker via funktionen DefaultOptionsV1() i filen v2_v1.go. Det innebär att v2 tillhandahåller alternativ som helt kan återskapa v1:s beteende, samtidigt som den erbjuder nya standardbeteenden som är strängare och mer förutsägbara.
Huvudmålen för v2 är följande.
- Förbättrad korrekthet och förutsägbarhet: Genom strängare standardregler (till exempel skiftlägeskänslighet och förbud mot dubbletter av nycklar) minskas oväntade beteenden.
- Förbättrad prestanda: Parsnings- och kodningsmotorerna har konstruerats om för ökad effektivitet.
- Ökad flexibilitet och kontroll: Ett detaljerat alternativsystem införs så att utvecklare kan finjustera hur JSON-bearbetningen sker.
Viktiga semantiska och beteendemässiga skillnader
Skillnaderna mellan v1 och v2 sammanfattas nedan utifrån filen v2_test.go.
1. Matchning av fältnamn (skiftlägeskänslighet)
- v1-beteende: Vid avmarshalling av JSON-objektmedlemmar till Go-structfält ignoreras skiftläge (case-insensitive) vid matchningen. Både
"FirstName"och"firstname"mappas till fältetFirstName. - v2-beteende: Som standard är matchningen skiftlägeskänslig (case-sensitive) och mappar endast fält som matchar exakt.
- Orsak till ändringen: Skiftlägesoberoende matchning kan orsaka oväntade beteenden och medför prestandaförluster när icke-matchande fält ska hanteras. v2 har därför antagit ett tydligare och mer förutsägbart standardbeteende.
- Relaterade alternativ: I v2 kan skiftlägesoberoende matchning aktiveras explicit för enskilda fält med taggalternativet
json:"...,case:ignore", eller globalt genom alternativetjson.MatchCaseInsensitiveNames(true).
2. Förändrad betydelse av taggalternativet omitempty
- v1-beteende: Fält utelämnas utifrån Go-värdets "tomma tillstånd". Med "tomt tillstånd" avses
false,0,nil-pekare eller gränssnitt samt arrayer, slice:ar, maps och strängar med längden noll. - v2-beteende: Fält utelämnas utifrån det kodade JSON-värdets "tomma tillstånd". Det innebär att fältet utelämnas om det kodas som
null,"",{}eller[]. - Orsak till ändringen: v1:s definition är beroende av Go:s typsystem. v2 utgår i stället från JSON:s typsystem och ger därför ett mer konsekvent beteende. Till exempel utelämnas i v1 värdet
falseför enbool, medan det i v2 inte utelämnas eftersomfalseär ett icke-tomt JSON-värde. I v2 kan alternativetomitzeroanvändas för att ersätta v1:s beteende däromitemptygällde för0ellerfalse. - Relaterade alternativ: För att i v2 få samma beteende som i v1 används alternativet
json.OmitEmptyWithLegacyDefinition(true).
3. Förändrat beteende för taggalternativet string
- v1-beteende: Tillämpas på fält av typerna tal, booleska värden och strängar. Värdet kodas på nytt inuti en JSON-sträng (till exempel
int(42)->"42"). Det tillämpas inte rekursivt på värden inuti sammansatta typer (slice:ar, maps med mera). - v2-beteende: Tillämpas endast på numeriska typer och tillämpas rekursivt. Det innebär att även tal inuti slice:ar, till exempel
[]int, kodas som JSON-strängar. - Orsak till ändringen: Det huvudsakliga syftet med alternativet
stringär att förhindra precisionsförlust för 64-bitars heltal genom att representera talen som strängar. v1:s beteende var begränsat och inkonsekvent. v2 koncentrerar sig på detta kärnsyfte och utökar beteendet rekursivt för att göra det mer användbart. - Relaterade alternativ: Med alternativet
json.StringifyWithLegacySemantics(true)kan v1:s beteende efterliknas.
4. Marshalling av nil-slice:ar och maps
- v1-beteende:
nil-slice:ar ochnil-maps marshalleras somnull. - v2-beteende: Som standard marshalleras
nil-slice:ar som[](tom array) ochnil-maps som{}(tomt objekt). - Orsak till ändringen:
nilär en implementationsdetalj i Go-språket, och det är inte önskvärt att exponera detta i ett språkoberoende JSON-format.[]och{}är vanligare representationer av tomma samlingar. - Relaterade alternativ: I v2 kan marshalling till
null, likt v1, uppnås med alternativenjson.FormatNilSliceAsNull(true)ellerjson.FormatNilMapAsNull(true).
5. Avmarshalling av arrayer
- v1-beteende: Vid avmarshalling till Go-arrayer (
[N]T) ger en JSON-array vars längd avviker från Go-arrayens längd inget fel. Om JSON-arrayen är kortare fylls de återstående platserna med nollvärden, och om den är längre kastas det överskjutande bort. - v2-beteende: JSON-arrayens längd måste exakt överensstämma med Go-arrayens längd. Annars uppstår ett fel.
- Orsak till ändringen: I Go har arrayer med fast storlek ofta en betydelsefull längd. v1:s beteende kan orsaka tyst dataförlust. v2 har genom striktare regler ökat korrektheten.
- Relaterade alternativ: Med alternativet
json.UnmarshalArrayFromAnyLength(true)kan v1:s beteende efterliknas.
6. Hantering av time.Duration
- v1-beteende:
time.Durationbehandlas internt somint64och kodas som ett JSON-tal i nanosekunder. - v2-beteende: Kodas som en JSON-sträng i formatet
"1h2m3s"med hjälp av metodentime.Duration.String(). - Orsak till ändringen: Tal i nanosekunder är svåra att läsa, och den standardiserade strängrepresentationen av
time.Durationär mer användbar. - Relaterade alternativ: v1:s beteende kan användas med taggalternativet
json:",format:nano"eller alternativetjson.FormatTimeWithLegacySemantics(true).
7. Hantering av ogiltig UTF-8
- v1-beteende: Vid marshalling och avmarshalling ersätts tyst ogiltiga UTF-8-bytes i strängar med Unicodes ersättningstecken (
\uFFFD). - v2-beteende: Som standard returneras ett fel när ogiltig UTF-8 påträffas.
- Orsak till ändringen: Syftet är att förhindra tyst datakorruption och att följa den striktare JSON-standarden (RFC 7493).
- Relaterade alternativ: Med alternativet
jsontext.AllowInvalidUTF8(true)kan v1:s beteende efterliknas.
8. Hantering av dubbla medlemsnamn i objekt
- v1-beteende: JSON-objekt får innehålla medlemmar med samma namn. Det sist förekommande värdet skriver över de tidigare.
- v2-beteende: Som standard returneras ett fel om dubbla medlemsnamn förekommer.
- Orsak till ändringen: RFC 8259 definierar inte beteendet vid dubbla namn, vilket kan leda till olika beteenden mellan implementationer. Detta kan vara en källa till säkerhetsbrister. v2 avvisar därför dubbletterna explicit för ökad korrekthet och säkerhet.
- Relaterade alternativ: Med alternativet
jsontext.AllowDuplicateNames(true)kan v1:s beteende efterliknas.
Skillnader i implementation och arkitektur
- v1: Förlitar sig i hög grad på
decodeStateidecode.gooch en manuellt skriven tillståndsmaskin (state machine) iscanner.go. Detta är en monolitisk struktur där parsningslogik och semantisk analys är starkt kopplade. - v2: Arkitekturen är mer modulär.
encoding/json/jsontext: Tillhandahåller en lågnivå-tokenizer (Decoder) och kodare (Encoder) med hög prestanda. Paketet fokuserar enbart på JSON:s syntaktiska aspekter.encoding/json/v2: Bygger påjsontextoch hanterar den semantiska omvandlingen mellan Go-typer och JSON-värden.- Genom denna uppdelning separeras syntaktisk analys från semantisk analys, vilket förbättrar kodens tydlighet och prestanda.
Nya API:er och funktioner i v2
v2 erbjuder mycket flexibel kontroll via systemet json.Options.
json.Options: En samling alternativ som ändrar marshalling- och avmarshallingbeteendet.json.JoinOptions(...): Slår samman flera alternativ till ett.WithMarshalers/WithUnmarshalers: Ett kraftfullt sätt att injicera serialiserings- och deserialiseringslogik för specifika typer, utan att typerna behöver implementera gränssnittenMarshalerellerUnmarshaler. Detta är särskilt användbart vid hantering av typer från externa paket.- Nya alternativ:
RejectUnknownMembers,Deterministic(false),FormatNilSliceAsNullmed flera ger kontroll över beteenden som inte var möjliga i v1.
Slutsats
encoding/json v2 är en modern implementation som, med utgångspunkt i erfarenheterna från v1, avsevärt förbättrar korrekthet, prestanda och flexibilitet. Standardbeteendet har blivit strängare, men genom det avancerade systemet Options kan v1:s alla beteenden fullt ut återskapas. Det gör det möjligt att behålla kompatibilitet med befintlig kod och successivt införa v2:s fördelar.
- För nya projekt rekommenderas att v2 används som standard.
- Befintliga projekt kan fortsätta använda
jsonv1som tidigare, eller migrera tilljsonv2och successivt införa v2:s strängare beteende med hjälp avDefaultOptionsV1().