Comparaison de `encoding/json` v1 vs v2 dans Go 1.25
Le package encoding/json
v2 de Go est une nouvelle implémentation visant à améliorer plusieurs lacunes de la v1 existante (manque de cohérence, comportements surprenants, problèmes de performance). Il s'agit d'une fonctionnalité expérimentale activée via le tag de build goexperiment.jsonv2
.
Le point le plus important est que lorsque la v2 est activée, la v1 fonctionne comme une couche de compatibilité qui émule le comportement de la v1 au-dessus de l'implémentation de la v2. Ceci est réalisé via la fonction DefaultOptionsV1()
dans le fichier v2_v1.go
. En d'autres termes, la v2 offre des options pour reproduire parfaitement le comportement de la v1, tout en présentant de nouveaux comportements par défaut plus stricts et prévisibles.
Les objectifs principaux de la v2 sont les suivants :
- Amélioration de la précision et de la prévisibilité : Appliquer par défaut des règles plus strictes (par exemple, sensible à la casse, interdiction des clés dupliquées) pour réduire les comportements inattendus.
- Amélioration des performances : Refonte des moteurs d'analyse et d'encodage pour accroître l'efficacité.
- Accroissement de la flexibilité et du contrôle : Introduction d'un système d'options détaillé permettant aux développeurs de contrôler finement la manière dont le JSON est traité.
Différences majeures de sémantique/comportement
Les différences de comportement entre la v1 et la v2 sont organisées par élément, en se concentrant sur le fichier v2_test.go
.
1. Correspondance des noms de champ (sensibilité à la casse)
- Comportement de la v1 : Lors de la désérialisation (unmarshaling) des membres d'objets JSON vers des champs de structures Go, la correspondance est effectuée sans tenir compte de la casse (case-insensitive). Aussi bien
"FirstName"
que"firstname"
sont mappés au champFirstName
. - Comportement de la v2 : Par défaut, la correspondance est sensible à la casse (case-sensitive), et seuls les champs correspondant exactement sont mappés.
- Raison du changement : La correspondance insensible à la casse peut être une source de comportements inattendus et entraîner une dégradation des performances lors du traitement des champs non correspondants. La v2 a adopté un comportement par défaut plus clair et prévisible.
- Option associée : En v2, l'option de tag
json:"...,case:ignore"
peut être utilisée pour activer explicitement l'insensibilité à la casse par champ, ou l'optionjson.MatchCaseInsensitiveNames(true)
peut être appliquée globalement.
2. Changement de la signification de l'option de tag omitempty
- Comportement de la v1 : Omet le champ en fonction de l'état "vide" de la valeur Go. Ici, "vide" signifie
false
,0
, les pointeurs/interfacesnil
, et les tableaux/slices/maps/chaînes de longueur nulle. - Comportement de la v2 : Omet le champ en fonction de l'état "vide" de la valeur JSON encodée. C'est-à-dire qu'il est omis s'il est encodé en
null
,""
,{}
,[]
. - Raison du changement : La définition de la v1 dépend du système de types de Go. La v2 fournit un comportement plus cohérent en se basant sur le système de types JSON. Par exemple, en v1, une valeur
false
de typebool
est omise, mais en v2,false
n'est pas une valeur JSON vide et n'est donc pas omise. La v2 ajoute l'optionomitzero
pour remplacer le comportement deomitempty
de la v1 qui s'appliquait à0
oufalse
. - Option associée : Si le même comportement que la v1 est souhaité en v2, utilisez l'option
json.OmitEmptyWithLegacyDefinition(true)
.
3. Changement de comportement de l'option de tag string
- Comportement de la v1 : S'applique aux champs de type numérique, booléen et chaîne de caractères. Réencode la valeur à l'intérieur d'une chaîne JSON (par exemple,
int(42)
->"42"
). Ne s'applique pas récursivement aux valeurs à l'intérieur de types composites (slices, maps, etc.). - Comportement de la v2 : S'applique uniquement aux types numériques et est appliqué récursivement. C'est-à-dire que tous les nombres à l'intérieur d'un slice comme
[]int
sont également encodés en chaînes JSON. - Raison du changement : L'utilisation principale de l'option
string
est de représenter les nombres sous forme de chaînes pour éviter la perte de précision des entiers 64 bits. Le comportement de la v1 était limité et manquait de cohérence. La v2 se concentre sur cette utilisation essentielle et étend le comportement de manière récursive pour le rendre plus utile. - Option associée : L'option
json.StringifyWithLegacySemantics(true)
peut être utilisée pour imiter le comportement de la v1.
4. Marshalling de slices et maps nil
- Comportement de la v1 : Les slices
nil
et les mapsnil
sont marshallés ennull
. - Comportement de la v2 : Par défaut, les slices
nil
sont marshallés en[]
(tableau vide), et les mapsnil
sont marshallés en{}
(objet vide). - Raison du changement :
nil
est un détail d'implémentation du langage Go, et il n'est pas souhaitable de l'exposer au format JSON indépendant du langage. Les expressions[]
ou{}
pour représenter des collections vides sont plus universelles. - Option associée : En v2, les options
json.FormatNilSliceAsNull(true)
oujson.FormatNilMapAsNull(true)
peuvent être utilisées pour marshaller ennull
comme en v1.
5. Désérialisation (Unmarshaling) de tableaux
- Comportement de la v1 : Lors de la désérialisation vers un tableau Go (
[N]T
), aucune erreur n'est générée même si la longueur du tableau JSON diffère de la longueur du tableau Go. Si la longueur est plus courte, les espaces restants sont remplis de valeurs zéro ; si la longueur est plus longue, l'excédent est ignoré. - Comportement de la v2 : La longueur du tableau JSON doit correspondre exactement à la longueur du tableau Go. Sinon, une erreur est générée.
- Raison du changement : Dans Go, la longueur d'un tableau de taille fixe a souvent une signification importante. Le comportement de la v1 pouvait entraîner une perte silencieuse de données. La v2 a augmenté la précision avec des règles plus strictes.
- Option associée : L'option
json.UnmarshalArrayFromAnyLength(true)
peut être utilisée pour imiter le comportement de la v1.
6. Traitement de time.Duration
- Comportement de la v1 :
time.Duration
est traité en interne comme unint64
et encodé en un nombre JSON représentant des nanosecondes. - Comportement de la v2 : Est encodé en une chaîne JSON au format
"1h2m3s"
en utilisant la méthodetime.Duration.String()
. - Raison du changement : Les nanosecondes numériques sont moins lisibles, et la représentation standard des chaînes de caractères de
time.Duration
est plus utile. - Option associée : L'option de tag
json:",format:nano"
ou l'optionjson.FormatTimeWithLegacySemantics(true)
peuvent être utilisées pour obtenir le comportement de la v1.
7. Traitement des UTF-8 invalides
- Comportement de la v1 : Lors du marshalling/unmarshalling, si des octets UTF-8 invalides sont présents dans une chaîne, ils sont silencieusement remplacés par le caractère de remplacement Unicode (
\uFFFD
). - Comportement de la v2 : Par défaut, une erreur est renvoyée si un UTF-8 invalide est rencontré.
- Raison du changement : Pour prévenir la corruption silencieuse des données et pour se conformer à la norme JSON plus stricte (RFC 7493).
- Option associée : L'option
jsontext.AllowInvalidUTF8(true)
peut être utilisée pour imiter le comportement de la v1.
8. Traitement des noms de membres d'objets dupliqués
- Comportement de la v1 : Autorise la présence de membres avec le même nom dupliqués dans un objet JSON. La dernière valeur rencontrée écrase les précédentes.
- Comportement de la v2 : Par défaut, une erreur est renvoyée si des noms de membres dupliqués sont présents.
- Raison du changement : La norme RFC 8259 ne définit pas le comportement des noms dupliqués, ce qui peut entraîner des comportements différents selon les implémentations. Cela peut être une source de vulnérabilités de sécurité. La v2 rejette explicitement cela pour augmenter la précision et la sécurité.
- Option associée : L'option
jsontext.AllowDuplicateNames(true)
peut être utilisée pour imiter le comportement de la v1.
Différences d'implémentation et d'architecture
- v1 : Dépend fortement de
decodeState
dansdecode.go
et de la machine à états écrite manuellement dansscanner.go
. Il s'agit d'une structure monolithique où la logique d'analyse et l'analyse sémantique sont fortement couplées. - v2 : L'architecture est plus modularisée.
encoding/json/jsontext
: Fournit un tokenizeur (Decoder
) et un encodeur (Encoder
) JSON de bas niveau et haute performance. Ce package se concentre uniquement sur l'aspect syntaxique du JSON.encoding/json/v2
: Gère la conversion sémantique entre les types Go et les valeurs JSON, en se basant surjsontext
.- Cette séparation a permis de distinguer l'analyse syntaxique de l'analyse sémantique, améliorant ainsi la clarté du code et les performances.
Nouvelles API et fonctionnalités de la v2
La v2 offre des capacités de contrôle très flexibles grâce au système json.Options
.
json.Options
: Un ensemble d'options qui modifient le comportement de marshalling/unmarshalling.json.JoinOptions(...)
: Fusionne plusieurs options en une seule.WithMarshalers
/WithUnmarshalers
: Une fonctionnalité puissante qui permet d'injecter une logique de sérialisation/désérialisation pour des types spécifiques sans avoir à implémenter les interfacesMarshaler
/Unmarshaler
. Ceci est particulièrement utile lors du traitement de types provenant de packages externes.- Nouvelles options :
RejectUnknownMembers
,Deterministic(false)
,FormatNilSliceAsNull
, etc. Une variété de contrôles de comportement qui n'étaient pas possibles en v1 sont désormais disponibles.
Conclusion
encoding/json
v2 est une implémentation moderne qui améliore considérablement la précision, les performances et la flexibilité en se basant sur l'expérience de la v1. Bien que le comportement par défaut soit plus strict, le système Options
sophistiqué prend en charge parfaitement tous les comportements de la v1, permettant d'introduire progressivement les avantages de la v2 tout en maintenant la compatibilité avec le code existant.
- Pour les nouveaux projets, il est recommandé d'utiliser la v2 par défaut.
- Les projets existants peuvent continuer à utiliser
jsonv1
, ou migrer versjsonv2
en utilisantDefaultOptionsV1()
pour introduire progressivement les comportements plus stricts de la v2.