Comparație între Go 1.25 encoding/json v1 și v2
Pachetul encoding/json
v2 din Go reprezintă o nouă implementare menită să amelioreze multiple deficiențe ale versiunii v1 (lipsa de consistență, comportamente surprinzătoare, probleme de performanță). Aceasta este o funcționalitate experimentală, activată prin intermediul tag-ului de compilare goexperiment.jsonv2
.
Aspectul cel mai important este că atunci când v2 este activată, v1 funcționează ca un strat de compatibilitate care emulează comportamentul v1 peste implementarea v2. Acest lucru este realizat prin funcția DefaultOptionsV1()
din fișierul v2_v1.go
. Aceasta înseamnă că v2 oferă opțiuni pentru a reproduce perfect comportamentul v1, prezentând în același timp un nou comportament implicit, mai strict și mai predictibil.
Principalele obiective ale v2 sunt următoarele:
- Îmbunătățirea preciziei și predictibilității: Prin aplicarea implicită a unor reguli mai stricte (de exemplu, sensibilitatea la majuscule/minuscule, interzicerea cheilor duplicate), se reduc comportamentele neașteptate.
- Îmbunătățirea performanței: Motoarele de parsare și codificare au fost reproiectate pentru a crește eficiența.
- Extinderea flexibilității și controlului: A fost introdus un sistem detaliat de opțiuni, permițând dezvoltatorilor să controleze fin modul de prelucrare a JSON-ului.
Diferențe majore de semantică/comportament
Diferențele de comportament dintre v1 și v2 au fost organizate pe categorii, având ca punct central fișierul v2_test.go
.
1. Potrivirea numelui câmpului (sensibilitate la majuscule/minuscule)
- Comportament v1: La unmarshaling-ul membrilor obiectelor JSON în câmpurile structurilor Go, potrivirea se realizează fără a ține cont de majuscule/minuscule (case-insensitive). Atât
"FirstName"
, cât și"firstname"
sunt mapate la câmpulFirstName
. - Comportament v2: Implicit, potrivirea se realizează ținând cont de majuscule/minuscule (case-sensitive), mapând doar câmpurile care se potrivesc exact.
- Motivul modificării: Potrivirea fără sensibilitate la majuscule/minuscule poate fi o sursă de comportamente neașteptate și poate duce la degradarea performanței la procesarea câmpurilor care nu se potrivesc. V2 a adoptat implicit un comportament mai clar și mai predictibil.
- Opțiuni relevante: În v2, se poate utiliza opțiunea de tag
json:"...,case:ignore"
pentru a activa explicit potrivirea fără sensibilitate la majuscule/minuscule per câmp, sau se poate aplica opțiuneajson.MatchCaseInsensitiveNames(true)
la nivel global.
2. Modificarea semnificației opțiunii de tag omitempty
- Comportament v1: Omite câmpurile pe baza stării "goale" a valorii Go. Prin "stare goală" se înțelege
false
,0
, pointeri/interfețenil
, array-uri/slice-uri/mape/șiruri de caractere cu lungimea zero. - Comportament v2: Omite câmpurile pe baza stării "goale" a valorii JSON codificate. Adică, sunt omise dacă sunt codificate ca
null
,""
,{}
,[]
. - Motivul modificării: Definiția v1 este dependentă de sistemul de tipuri Go. V2 oferă un comportament mai consistent bazat pe sistemul de tipuri JSON. De exemplu, în v1, valoarea
false
a tipuluibool
este omisă, dar în v2,false
nu este o valoare JSON goală, deci nu este omisă. V2 a adăugat opțiuneaomitzero
pentru a înlocui comportamentulomitempty
din v1 aplicat la0
saufalse
. - Opțiuni relevante: Dacă se dorește același comportament ca în v1 în v2, se utilizează opțiunea
json.OmitEmptyWithLegacyDefinition(true)
.
3. Modificarea comportamentului opțiunii de tag string
- Comportament v1: Se aplică câmpurilor de tip numeric, boolean și șir de caractere. Valoarea respectivă este re-codificată într-un șir JSON (de exemplu:
int(42)
->"42"
). Nu se aplică recursiv valorilor din tipuri compuse (slice-uri, mape etc.). - Comportament v2: Se aplică doar tipurilor numerice și se aplică recursiv. Adică, chiar și numerele din interiorul slice-urilor, cum ar fi
[]int
, sunt codificate ca șiruri JSON. - Motivul modificării: Scopul principal al opțiunii
string
este de a reprezenta numerele ca șiruri pentru a preveni pierderea preciziei în cazul numerelor întregi pe 64 de biți. Comportamentul v1 era limitat și inconsistent. V2 se concentrează pe această utilizare esențială și extinde comportamentul recursiv pentru a-l face mai util. - Opțiuni relevante: Se poate simula comportamentul v1 cu opțiunea
json.StringifyWithLegacySemantics(true)
.
4. Marshalling-ul slice-urilor și map-urilor nil
- Comportament v1: Slice-urile
nil
și map-urilenil
sunt marshalate canull
. - Comportament v2: Implicit, slice-urile
nil
sunt marshalate ca[]
(array gol), iar map-urilenil
ca{}
(obiect gol). - Motivul modificării:
nil
este un detaliu de implementare al limbajului Go, și expunerea acestuia în formatul JSON, independent de limbaj, nu este de dorit.[]
sau{}
sunt reprezentări mai universale pentru colecțiile goale. - Opțiuni relevante: În v2, se poate marshall-a ca
null
, similar cu v1, prin opțiunilejson.FormatNilSliceAsNull(true)
saujson.FormatNilMapAsNull(true)
.
5. Unmarshaling-ul array-urilor
- Comportament v1: La unmarshaling-ul într-un array Go (
[N]T
), nu se generează eroare chiar dacă lungimea array-ului JSON este diferită de lungimea array-ului Go. Dacă lungimea este mai scurtă, spațiul rămas este umplut cu valori zero; dacă este mai lungă, excesul este ignorat. - Comportament v2: Lungimea array-ului JSON trebuie să se potrivească exact cu lungimea array-ului Go. Altfel, se generează o eroare.
- Motivul modificării: În Go, array-urile de dimensiune fixă au adesea o semnificație importantă în ceea ce privește lungimea lor. Comportamentul v1 putea duce la pierderi silențioase de date. V2 a sporit precizia prin reguli mai stricte.
- Opțiuni relevante: Se poate simula comportamentul v1 cu opțiunea
json.UnmarshalArrayFromAnyLength(true)
.
6. Tratarea time.Duration
- Comportament v1:
time.Duration
este tratat intern ca unint64
și codificat ca un număr JSON în nanosecunde. - Comportament v2: Este codificat ca un șir JSON de forma
"1h2m3s"
folosind metodatime.Duration.String()
. - Motivul modificării: Nanosecundele numerice au o lizibilitate redusă, iar reprezentarea standard a șirului pentru
time.Duration
este mai utilă. - Opțiuni relevante: Se poate utiliza comportamentul v1 prin opțiunea de tag
json:",format:nano"
saujson.FormatTimeWithLegacySemantics(true)
.
7. Tratarea UTF-8 invalid
- Comportament v1: La marshalling/unmarshaling, dacă există octeți UTF-8 invalizi într-un șir, aceștia sunt înlocuiți silențios cu caracterul de înlocuire Unicode (
\uFFFD
). - Comportament v2: Implicit, dacă se întâlnește UTF-8 invalid, se returnează o eroare.
- Motivul modificării: Pentru a preveni deteriorarea silențioasă a datelor și pentru a respecta standardul JSON mai strict (RFC 7493).
- Opțiuni relevante: Se poate simula comportamentul v1 cu opțiunea
jsontext.AllowInvalidUTF8(true)
.
8. Tratarea numelor de membri de obiect duplicate
- Comportament v1: Permite apariția duplicată a membrilor cu același nume într-un obiect JSON. Valoarea care apare ultima o suprascrie pe cea anterioară.
- Comportament v2: Implicit, dacă există nume de membri duplicate, se returnează o eroare.
- Motivul modificării: Standardul RFC 8259 nu definește comportamentul numelor duplicate, ceea ce poate duce la comportamente diferite în diverse implementări. Acest lucru poate fi o sursă de vulnerabilități de securitate. V2 refuză explicit acest lucru pentru a crește precizia și securitatea.
- Opțiuni relevante: Se poate simula comportamentul v1 cu opțiunea
jsontext.AllowDuplicateNames(true)
.
Diferențe de implementare și arhitectură
- v1: Depinde în mare măsură de
decodeState
dindecode.go
și de o mașină de stări (state machine) scrisă manual înscanner.go
. Aceasta este o structură monolitică în care logica de parsare și analiza semantică sunt puternic cuplate. - v2: Arhitectura este mai modularizată.
encoding/json/jsontext
: Oferă un tokenizer JSON (Decoder
) și un encoder (Encoder
) de nivel scăzut, de înaltă performanță. Acest pachet se concentrează exclusiv pe aspectele sintactice ale JSON-ului.encoding/json/v2
: Gestionează transformările semantice între tipurile Go și valorile JSON, bazându-se pejsontext
.- Această separare a analizei sintactice și semantice îmbunătățește claritatea codului și performanța.
API-uri și funcționalități noi în v2
V2 oferă funcționalități de control extrem de flexibile prin sistemul json.Options
.
json.Options
: Un set de opțiuni care modifică comportamentul de marshalling/unmarshaling.json.JoinOptions(...)
: Combină mai multe opțiuni într-una singură.WithMarshalers
/WithUnmarshalers
: O funcționalitate puternică care permite injectarea logicii de serializare/deserializare pentru un anumit tip, chiar și fără implementarea interfețelorMarshaler
/Unmarshaler
. Acest lucru este deosebit de util la procesarea tipurilor din pachete externe.- Opțiuni noi:
RejectUnknownMembers
,Deterministic(false)
,FormatNilSliceAsNull
și multe altele, care permit un control variat al comportamentului, imposibil în v1.
Concluzie
encoding/json
v2 este o implementare modernă care îmbunătățește semnificativ precizia, performanța și flexibilitatea, bazându-se pe experiența v1. Deși comportamentul implicit este mai strict, sistemul sofisticat de Options
suportă pe deplin toate comportamentele v1, permițând adoptarea treptată a avantajelor v2, menținând în același timp compatibilitatea cu codul existent.
- Pentru proiecte noi, este recomandat să se utilizeze v2 implicit.
- Proiectele existente pot continua să utilizeze
jsonv1
sau pot migra lajsonv2
, adoptând treptat comportamentul strict al v2 prinDefaultOptionsV1()
.