GoSuda

Jämförelse av Go 1.25 encoding/json v1 och v2

By lemonmint
views ...

Go:s encoding/json paket v2 är en ny implementering som syftar till att förbättra flera brister i den befintliga v1 (brist på konsekvens, överraskande beteende, prestandaproblem). Detta är en experimentell funktion som aktiveras via build-taggen goexperiment.jsonv2.

Det viktigaste är att när v2 är aktiverat, fungerar v1 som ett kompatibilitetslager som emulerar v1:s beteende ovanpå v2-implementeringen. Detta uppnås genom funktionen DefaultOptionsV1() i filen v2_v1.go. Detta innebär att v2 erbjuder alternativ för att fullständigt återskapa v1:s beteende, samtidigt som det introducerar ett striktare och mer förutsägbart nytt standardbeteende.

Huvudmålen för v2 är följande:

  1. Förbättrad noggrannhet och förutsägbarhet: Genom att tillämpa striktare regler som standard (t.ex. skiftlägeskänslighet, förbud mot dubbla nycklar) minskas oväntade beteenden.
  2. Prestandaförbättringar: Parsnings- och kodningsmotorn har omdesignats för att förbättra effektiviteten.
  3. Ökad flexibilitet och kontroll: Ett detaljerat options-system har introducerats för att ge utvecklare finjusterad kontroll över hur JSON bearbetas.

Huvudsakliga skillnader i betydelse/beteende

Skillnaderna i beteende mellan v1 och v2 har sammanställts per punkt, med fokus på filen v2_test.go.

1. Matchning av fältnamn (skiftlägeskänslighet)

  • v1-beteende: Vid unmarshaling av JSON-objektmedlemmar till Go-strukturfält matchas de skiftlägesokänsligt (case-insensitive). Både "FirstName" och "firstname" mappas till fältet FirstName.
  • v2-beteende: Som standard matchas de skiftlägeskänsligt (case-sensitive), och endast exakt matchande fält mappas.
  • Anledning till ändring: Skiftlägesokänslig matchning kan leda till oväntade beteenden och orsaka prestandaförsämring vid hantering av icke-matchande fält. v2 har antagit ett tydligare och mer förutsägbart beteende som standard.
  • Relaterade Options: I v2 kan du explicit aktivera skiftlägesokänslighet per fält med taggoptionen json:"...,case:ignore" eller tillämpa optionen json.MatchCaseInsensitiveNames(true) globalt.

2. Ändrad betydelse av omitempty taggoptionen

  • v1-beteende: Fält utelämnas baserat på Go-värdets "tomma tillstånd". Här betyder "tomt tillstånd" false, 0, nil pekare/interface, och arrayer/slices/maps/strängar med längden 0.
  • v2-beteende: Fält utelämnas baserat på det kodade JSON-värdets "tomma tillstånd". Det vill säga, de utelämnas om de kodas som null, "", {}, [].
  • Anledning till ändring: v1:s definition är beroende av Go:s typsystem. v2 baserar sig på JSON:s typsystem för att ge ett mer konsekvent beteende. Till exempel utelämnas ett false-värde av typen bool i v1, men i v2 utelämnas false inte eftersom det är ett icke-tomt JSON-värde. I v2 har optionen omitzero lagts till för att ersätta v1:s omitempty beteende som gällde för 0 eller false.
  • Relaterade Options: Om du vill ha samma beteende som v1 i v2, använd optionen json.OmitEmptyWithLegacyDefinition(true).

3. Ändrat beteende för string taggoptionen

  • v1-beteende: Tillämpas på numeriska, booleska och strängtypsfält. Värdet kodas om till en JSON-sträng (t.ex. int(42) -> "42"). Det tillämpas inte rekursivt på värden inuti sammansatta typer (slices, maps, etc.).
  • v2-beteende: Tillämpas endast på numeriska typer och tillämpas rekursivt. Det vill säga, alla nummer inuti slices som []int kommer också att kodas som JSON-strängar.
  • Anledning till ändring: Huvudanvändningen av string-optionen är att representera nummer som strängar för att undvika förlust av precision för 64-bitars heltal. v1:s beteende var begränsat och inkonsekvent. v2 fokuserar på denna kärnanvändning och utökar beteendet rekursivt för att göra det mer användbart.
  • Relaterade Options: Beteendet från v1 kan efterliknas med optionen json.StringifyWithLegacySemantics(true).

4. Marshalling av nil slices och maps

  • v1-beteende: nil slices och nil maps marshallas som null.
  • v2-beteende: Som standard marshallas nil slices som [] (tom array) och nil maps som {} (tomt objekt).
  • Anledning till ändring: nil är en implementeringsdetalj i Go-språket, och det är inte önskvärt att exponera detta i det språkoberoende JSON-formatet. [] eller {} för att representera tomma samlingar är en mer universell representation.
  • Relaterade Options: I v2 kan du marshallera som null likt v1 genom optionerna json.FormatNilSliceAsNull(true) eller json.FormatNilMapAsNull(true).

5. Array Unmarshaling

  • v1-beteende: Vid unmarshaling till en Go-array ([N]T) utlöses inget fel även om JSON-arrayens längd skiljer sig från Go-arrayens längd. Om längden är kortare fylls återstående utrymme med nollvärden, och om den är längre kasseras överskottet.
  • v2-beteende: JSON-arrayens längd måste vara exakt lika med Go-arrayens längd. Annars uppstår ett fel.
  • Anledning till ändring: I Go har arrayer med fast storlek ofta en viktig betydelse för sin längd. v1:s beteende kan leda till tyst förlust av data. v2 ökar noggrannheten med striktare regler.
  • Relaterade Options: Beteendet från v1 kan efterliknas med optionen json.UnmarshalArrayFromAnyLength(true).

6. Hantering av time.Duration

  • v1-beteende: time.Duration behandlas internt som en int64 och kodas som ett JSON-nummer i nanosekunder.
  • v2-beteende: Använder time.Duration.String()-metoden för att koda som en JSON-sträng i formatet "1h2m3s".
  • Anledning till ändring: Numeriska nanosekunder är svårlästa, och den standardiserade strängrepresentationen av time.Duration är mer användbar.
  • Relaterade Options: Beteendet från v1 kan användas genom taggoptionen json:",format:nano" eller optionen json.FormatTimeWithLegacySemantics(true).

7. Hantering av ogiltig UTF-8

  • v1-beteende: Vid marshalling/unmarshaling ersätts ogiltiga UTF-8-byte i strängen tyst med Unicode-ersättningskaraktären (\uFFFD).
  • v2-beteende: Som standard returneras ett fel om ogiltig UTF-8 påträffas.
  • Anledning till ändring: För att förhindra tyst datakorruption och för att följa striktare JSON-standarder (RFC 7493).
  • Relaterade Options: Beteendet från v1 kan efterliknas med optionen jsontext.AllowInvalidUTF8(true).

8. Hantering av dubblerade objektnamn

  • v1-beteende: Tillåter att samma medlemsnamn förekommer flera gånger i ett JSON-objekt. Värdet som uppträder sist skriver över de föregående.
  • v2-beteende: Som standard returneras ett fel om dubblerade medlemsnamn finns.
  • Anledning till ändring: RFC 8259-standarden definierar inte beteendet för dubblerade namn, vilket kan leda till olika beteenden mellan implementeringar. Detta kan vara en källa till säkerhetsbrister. v2 förkastar detta explicit för att öka noggrannheten och säkerheten.
  • Relaterade Options: Beteendet från v1 kan efterliknas med optionen jsontext.AllowDuplicateNames(true).

Skillnader i implementering och arkitektur

  • v1: Förlitar sig starkt på decodeState i decode.go och en manuellt skriven tillståndsmaskin (state machine) i scanner.go. Detta är en monolitisk struktur där parsninglogiken och semantisk analys är starkt kopplade.
  • v2: Arkitekturen är mer modulär.
    • encoding/json/jsontext: Tillhandahåller en lågnivå, högpresterande JSON-tokeniserare (Decoder) och kodare (Encoder). Detta paket fokuserar endast på den syntaktiska aspekten av JSON.
    • encoding/json/v2: Bygger på jsontext och hanterar den semantiska omvandlingen mellan Go-typer och JSON-värden.
    • Denna separation av syntaxanalys och semantisk analys förbättrar kodens tydlighet och prestanda.

Nya API:er och funktioner i v2

v2 erbjuder mycket flexibla kontrollfunktioner genom json.Options-systemet.

  • json.Options: En uppsättning optioner som ändrar marshalling/unmarshalling-beteendet.
  • json.JoinOptions(...): Slår samman flera optioner till en.
  • WithMarshalers / WithUnmarshalers: En kraftfull funktion som möjliggör injicering av serialiserings-/deserialiseringslogik för specifika typer utan att implementera Marshaler/Unmarshaler-interfacet. Detta är särskilt användbart vid hantering av typer från externa paket.
  • Nya optioner: Olika beteendekontroller som inte var möjliga i v1, såsom RejectUnknownMembers, Deterministic(false), FormatNilSliceAsNull, har blivit tillgängliga.

Slutsats

encoding/json v2 är en modern implementering som, baserad på erfarenheterna från v1, har förbättrat noggrannhet, prestanda och flexibilitet avsevärt. Även om standardbeteendet har blivit striktare, stöder det sofistikerade Options-systemet fullständigt alla v1:s beteenden, vilket möjliggör gradvis implementering av v2:s fördelar samtidigt som kompatibiliteten med befintlig kod bibehålls.

  • För nya projekt är det bäst att använda v2 som standard.
  • Befintliga projekt kan fortsätta att använda jsonv1 eller använda en strategi för att gradvis introducera v2:s strikta beteende genom DefaultOptionsV1() vid migrering till jsonv2.