GoSuda

Go 1.25 encoding/json v1 vs v2 vergelijking

By lemonmint
views ...

Go's encoding/json pakket v2 is een nieuwe implementatie die diverse tekortkomingen van de bestaande v1 versie (gebrek aan consistentie, verrassend gedrag, prestatieproblemen) verbetert. Dit is een experimentele functionaliteit die geactiveerd wordt via de goexperiment.jsonv2 build tag.

Het belangrijkste punt is dat wanneer v2 geactiveerd is, v1 functioneert als een compatibiliteitslaag die het gedrag van v1 emuleert bovenop de v2 implementatie. Dit wordt gerealiseerd via de DefaultOptionsV1() functie in het v2_v1.go bestand. Dit betekent dat v2 opties biedt om het gedrag van v1 perfect te reproduceren, terwijl het tegelijkertijd een nieuw, strikter en voorspelbaarder standaardgedrag introduceert.

De hoofddoelstellingen van v2 zijn als volgt:

  1. Verbeterde nauwkeurigheid en voorspelbaarheid: Door standaard striktere regels toe te passen (bijvoorbeeld hoofdlettergevoeligheid, verbod op dubbele sleutels) wordt onverwacht gedrag verminderd.
  2. Prestatieverbetering: De parsing- en encoding-engine is opnieuw ontworpen om de efficiëntie te verhogen.
  3. Verhoogde flexibiliteit en controle: Er is een gedetailleerd optiesysteem geïntroduceerd waarmee ontwikkelaars de JSON-verwerkingsmethode nauwkeurig kunnen controleren.

Belangrijkste Semantische/Gedragsverschillen

De gedragsverschillen tussen v1 en v2 zijn categorisch samengevat, met de nadruk op het v2_test.go bestand.

1. Veldnaam Matching (Hoofdlettergevoeligheid)

  • v1 Gedrag: Bij het unmarshalling van JSON objectleden naar Go structuurvelden, wordt er hoofdletterongevoelig (case-insensitive) gematcht. Zowel "FirstName" als "firstname" worden gemapt naar het FirstName veld.
  • v2 Gedrag: Standaard wordt er hoofdlettergevoelig (case-sensitive) gematcht, waarbij alleen exact overeenkomende velden worden gemapt.
  • Reden voor Wijziging: Hoofdletterongevoelige matching kan de oorzaak zijn van onverwacht gedrag en leidt tot prestatievermindering bij het verwerken van niet-overeenkomende velden. v2 heeft een duidelijker en voorspelbaarder gedrag als standaard aangenomen.
  • Gerelateerde Opties: In v2 kan hoofdletterongevoeligheid expliciet per veld worden geactiveerd met de json:"...,case:ignore" tag optie, of globaal worden toegepast met de json.MatchCaseInsensitiveNames(true) optie.

2. Wijziging in de Betekenis van de omitempty Tag Optie

  • v1 Gedrag: Velden worden weggelaten op basis van de "lege staat" van de Go waarde. Hierbij betekent "lege staat" false, 0, nil pointers/interfaces, en arrays/slices/maps/strings met lengte 0.
  • v2 Gedrag: Velden worden weggelaten op basis van de "lege staat" van de gecodeerde JSON waarde. Dat wil zeggen, ze worden weggelaten indien ze als null, "", {}, [] worden gecodeerd.
  • Reden voor Wijziging: De definitie van v1 is afhankelijk van het type systeem van Go. v2 biedt consistenter gedrag door uit te gaan van het JSON type systeem. Bijvoorbeeld, in v1 wordt een false waarde van het type bool weggelaten, maar in v2 wordt false niet weggelaten omdat het een niet-lege JSON waarde is. In v2 is de omitzero optie toegevoegd om het gedrag van v1's omitempty dat van toepassing was op 0 of false te vervangen.
  • Gerelateerde Opties: Indien in v2 hetzelfde gedrag als v1 gewenst is, gebruikt men de json.OmitEmptyWithLegacyDefinition(true) optie.

3. Wijziging in het Gedrag van de string Tag Optie

  • v1 Gedrag: Van toepassing op velden van numerieke, booleaanse en string-typen. De betreffende waarde wordt opnieuw gecodeerd binnen een JSON string (bijv. int(42) -> "42"). Dit wordt niet recursief toegepast op waarden binnen complexe typen (slices, maps, enz.).
  • v2 Gedrag: Is alleen van toepassing op numerieke typen en wordt recursief toegepast. Dat wil zeggen, alle getallen binnen een slice zoals []int worden ook als JSON strings gecodeerd.
  • Reden voor Wijziging: Het primaire doel van de string optie is om precisieverlies bij 64-bit integers te voorkomen door getallen als strings weer te geven. Het gedrag van v1 was beperkt en inconsistent. v2 concentreert zich op dit kerndoel en breidt het gedrag recursief uit om het nuttiger te maken.
  • Gerelateerde Opties: Met de json.StringifyWithLegacySemantics(true) optie kan het gedrag van v1 worden nagebootst.

4. nil Slice en Map Marshalling

  • v1 Gedrag: nil slices en nil maps worden gemarshald als null.
  • v2 Gedrag: Standaard worden nil slices gemarshald als [] (lege array) en nil maps als {} (leeg object).
  • Reden voor Wijziging: nil is een implementatiedetail van de Go taal, en het blootstellen hiervan aan het taal-onafhankelijke JSON-formaat is onwenselijk. Lege verzamelingen, aangeduid door [] of {}, zijn een meer universele representatie.
  • Gerelateerde Opties: In v2 kan, net als in v1, gemarshald worden als null via de json.FormatNilSliceAsNull(true) of json.FormatNilMapAsNull(true) optie.

5. Array Unmarshalling

  • v1 Gedrag: Bij het unmarshalling naar een Go array ([N]T), wordt er geen fout gegenereerd, zelfs niet als de lengte van de JSON array afwijkt van de lengte van de Go array. Indien korter, wordt de resterende ruimte opgevuld met nulwaarden; indien langer, worden de overtollige elementen verworpen.
  • v2 Gedrag: De lengte van de JSON array moet exact overeenkomen met de lengte van de Go array. Anders wordt er een fout gegenereerd.
  • Reden voor Wijziging: In Go is de lengte van fixed-size arrays vaak van cruciaal belang. Het gedrag van v1 kon leiden tot stil dataverlies. v2 verhoogt de nauwkeurigheid door striktere regels te hanteren.
  • Gerelateerde Opties: Met de json.UnmarshalArrayFromAnyLength(true) optie kan het gedrag van v1 worden nagebootst.

6. time.Duration Verwerking

  • v1 Gedrag: time.Duration wordt intern behandeld als int64 en gecodeerd als een JSON-nummer in nanoseconden.
  • v2 Gedrag: Wordt gecodeerd als een JSON string in het formaat "1h2m3s" met behulp van de time.Duration.String() methode.
  • Reden voor Wijziging: Numerieke nanoseconden zijn minder leesbaar, en de standaard stringweergave van time.Duration is nuttiger.
  • Gerelateerde Opties: Het gedrag van v1 kan worden gebruikt via de json:",format:nano" tag optie of de json.FormatTimeWithLegacySemantics(true) optie.

7. Verwerking van Ongeldige UTF-8

  • v1 Gedrag: Bij marshalling/unmarshalling, indien ongeldige UTF-8 bytes aanwezig zijn in een string, worden deze stilzwijgend vervangen door het Unicode vervangingskarakter (\uFFFD).
  • v2 Gedrag: Standaard wordt er een fout geretourneerd bij het aantreffen van ongeldige UTF-8.
  • Reden voor Wijziging: Dit is om stilzwijgende datacorruptie te voorkomen en om te voldoen aan de striktere JSON-standaard (RFC 7493).
  • Gerelateerde Opties: Met de jsontext.AllowInvalidUTF8(true) optie kan het gedrag van v1 worden nagebootst.

8. Verwerking van Dubbele Objectlidnamen

  • v1 Gedrag: Dubbele lidnamen binnen een JSON-object zijn toegestaan. De laatst voorkomende waarde overschrijft de eerdere.
  • v2 Gedrag: Standaard wordt er een fout geretourneerd indien er dubbele lidnamen zijn.
  • Reden voor Wijziging: De RFC 8259-standaard definieert het gedrag van dubbele namen niet, waardoor implementaties verschillend kunnen reageren. Dit kan een bron zijn van beveiligingskwetsbaarheden. v2 weigert dit expliciet om de nauwkeurigheid en veiligheid te verhogen.
  • Gerelateerde Opties: Met de jsontext.AllowDuplicateNames(true) optie kan het gedrag van v1 worden nagebootst.

Implementatie- en Architectuurverschillen

  • v1: Is sterk afhankelijk van decodeState in decode.go en een handmatig geschreven state machine in scanner.go. Dit is een monolithische structuur waarbij parsing logica en semantische analyse sterk met elkaar verweven zijn.
  • v2: De architectuur is meer gemodulariseerd.
    • encoding/json/jsontext: Biedt een laag-niveau, hoogwaardige JSON tokenizer (Decoder) en encoder (Encoder). Dit pakket concentreert zich uitsluitend op de syntactische aspecten van JSON.
    • encoding/json/v2: Behandelt de semantische conversie tussen Go typen en JSON waarden, gebaseerd op jsontext.
    • Deze scheiding van syntactische en semantische analyse verbetert de helderheid van de code en de prestaties.

Nieuwe API en Functionaliteiten van v2

v2 biedt zeer flexibele controlefunctionaliteiten via het json.Options systeem.

  • json.Options: Een verzameling van opties die het marshalling/unmarshalling gedrag wijzigen.
  • json.JoinOptions(...): Voegt meerdere opties samen tot één.
  • WithMarshalers / WithUnmarshalers: Een krachtige functionaliteit die het injecteren van serialisatie/deserialisatie logica voor specifieke typen mogelijk maakt, zonder dat de Marshaler/Unmarshaler interface geïmplementeerd hoeft te worden. Dit is bijzonder nuttig bij het verwerken van typen uit externe pakketten.
  • Nieuwe Opties: Diverse gedragscontroles die in v1 niet mogelijk waren, zoals RejectUnknownMembers, Deterministic(false), FormatNilSliceAsNull, zijn nu beschikbaar.

Conclusie

encoding/json v2 is een moderne implementatie die, voortbouwend op de ervaringen van v1, de nauwkeurigheid, prestaties en flexibiliteit aanzienlijk heeft verbeterd. Hoewel het standaardgedrag strikter is geworden, ondersteunt het geavanceerde Options systeem alle gedragingen van v1 volledig, waardoor compatibiliteit met bestaande code behouden blijft en de voordelen van v2 geleidelijk kunnen worden geïntroduceerd.

  • Voor nieuwe projecten wordt aanbevolen v2 als standaard te gebruiken.
  • Bestaande projecten kunnen jsonv1 blijven gebruiken, of een strategie hanteren om geleidelijk de striktere gedragingen van v2 te introduceren via DefaultOptionsV1() tijdens de migratie naar jsonv2.