---
id: 61f2009278a5f7366f79395da59447cf
author: lemonmint
title: Analyse van de gedragswijzigingen in encoding/json/v2 van Go 1.25 en de compatibiliteitslaag met v1
description: Dit vergelijkt de hoofdletterongevoelige en hoofdlettergevoelige matching, de gewijzigde semantiek van omitempty en omitzero, en de serialisatie van nil-slices en nil-maps alsook de bijbehorende opties van encoding/json/v2, dat in Go 1.25 als experimentele functie (goexperiment.jsonv2) is geïntroduceerd.
language: nl
date: 2025-06-20T05:38:20.100520237Z
path: /blog/posts/go-1-25-encoding-json-v1-vs-v2-z74a36b08
category: release
---

Het `encoding/json`-pakket v2 in Go is een nieuwe implementatie die is ontworpen om verschillende tekortkomingen van de bestaande v1 te verbeteren (gebrek aan consistentie, verrassend gedrag, prestatieproblemen). Het is een experimentele functie die wordt geactiveerd via de build-tag `goexperiment.jsonv2`.

Het belangrijkste punt is dat **wanneer v2 is geactiveerd, v1 fungeert als een compatibiliteitslaag die het gedrag van v1 emuleert bovenop de v2-implementatie**. Dit wordt gerealiseerd via de functie `DefaultOptionsV1()` in het bestand `v2_v1.go`. Met andere woorden, v2 biedt opties waarmee het gedrag van v1 volledig kan worden gereproduceerd, en introduceert tegelijkertijd nieuw standaardgedrag dat strenger en voorspelbaarder is.

De belangrijkste doelen van v2 zijn als volgt.

1. **Verbetering van correctheid en voorspelbaarheid**: Door standaard strengere regels toe te passen (bijvoorbeeld hoofdlettergevoeligheid en verboden dubbele sleutels) worden onverwachte gedragingen beperkt.
2. **Prestatieverbetering**: De parsing- en coderingsengine is opnieuw ontworpen om de efficiëntie te verhogen.
3. **Meer flexibiliteit en controle**: Een gedetailleerd optiesysteem stelt ontwikkelaars in staat de verwerking van JSON fijnmazig te controleren.

---

## Belangrijkste verschillen in semantiek en gedrag

Met het bestand `v2_test.go` als uitgangspunt zijn de gedragsverschillen tussen v1 en v2 per onderwerp samengevat.

### 1. Matching van veldnamen (hoofdlettergevoeligheid)

- **Gedrag van v1**: Bij het unmarshalen van JSON-objectleden naar Go-structvelden wordt **hoofdletterongevoelig (case-insensitive)** gematcht. Zowel `"FirstName"` als `"firstname"` worden aan het veld `FirstName` gekoppeld.
- **Gedrag van v2**: Standaard wordt **hoofdlettergevoelig (case-sensitive)** gematcht, waarbij alleen velden met een exact overeenkomende naam worden gekoppeld.
- **Reden voor de wijziging**: Hoofdletterongevoelige matching kan een bron van onverwacht gedrag zijn en veroorzaakt prestatieverlies bij het verwerken van niet-overeenkomende velden. v2 hanteert als standaard een duidelijker en voorspelbaarder gedrag.
- **Gerelateerde opties**: In v2 kan hoofdletterongevoelige matching expliciet per veld worden ingeschakeld met de tagoptie `json:"...,case:ignore"`, of globaal worden toegepast met de optie `json.MatchCaseInsensitiveNames(true)`.

### 2. Wijziging in de betekenis van de tagoptie `omitempty`

- **Gedrag van v1**: Velden worden weggelaten op basis van de "lege staat" (empty state) van de Go-waarde. Met "lege staat" wordt bedoeld `false`, `0`, `nil`-pointers/interfaces en arrays/slices/maps/strings met lengte nul.
- **Gedrag van v2**: Velden worden weggelaten op basis van de "lege staat" van de gecodeerde JSON-waarde. Dat wil zeggen dat velden worden weggelaten wanneer ze worden gecodeerd als `null`, `""`, `{}` of `[]`.
- **Reden voor de wijziging**: De definitie van v1 is afhankelijk van het typesysteem van Go. v2 baseert zich op het JSON-typesysteem en biedt daardoor consistenter gedrag. In v1 wordt bijvoorbeeld de waarde `false` van een `bool`-veld weggelaten, terwijl in v2 `false` een niet-lege JSON-waarde is en dus niet wordt weggelaten. In v2 is de optie `omitzero` toegevoegd, waarmee het gedrag van `omitempty` van v1 voor `0` of `false` kan worden vervangen.
- **Gerelateerde optie**: Om in v2 exact het gedrag van v1 te verkrijgen, wordt de optie `json.OmitEmptyWithLegacyDefinition(true)` gebruikt.

### 3. Wijziging in het gedrag van de tagoptie `string`

- **Gedrag van v1**: De optie is van toepassing op velden van numerieke, booleaanse en stringtypen. De waarde wordt opnieuw binnen een JSON-string gecodeerd (bijvoorbeeld `int(42)` -> `"42"`). Op waarden binnen samengestelde typen (slices, maps, enzovoort) wordt de optie niet recursief toegepast.
- **Gedrag van v2**: De optie is **uitsluitend van toepassing op numerieke typen** en wordt recursief toegepast. Dat betekent dat ook de getallen binnen een slice zoals `[]int` allemaal als JSON-string worden gecodeerd.
- **Reden voor de wijziging**: Het belangrijkste doel van de optie `string` is het voorkomen van precisieverlies bij 64-bits gehele getallen door getallen als string weer te geven. Het gedrag van v1 was beperkt en inconsistent. v2 concentreert zich op dit kerndoel en breidt het gedrag recursief uit, zodat de optie nuttiger is.
- **Gerelateerde optie**: Met de optie `json.StringifyWithLegacySemantics(true)` kan het gedrag van v1 worden nagebootst.

### 4. Marshalen van `nil`-slices en -maps

- **Gedrag van v1**: `nil`-slices en `nil`-maps worden gemarshald als `null`.
- **Gedrag van v2**: Standaard worden `nil`-slices gemarshald als `[]` (lege array) en `nil`-maps als `{}` (leeg object).
- **Reden voor de wijziging**: `nil` is een implementatiedetail van de Go-taal, en het is niet wenselijk dit bloot te stellen in een taalonafhankelijk JSON-formaat. `[]` en `{}` zijn de meer algemene representaties van lege collecties.
- **Gerelateerde opties**: In v2 kan met `json.FormatNilSliceAsNull(true)` of `json.FormatNilMapAsNull(true)` worden bereikt dat ze, zoals in v1, als `null` worden gemarshald.

### 5. Unmarshalen van arrays

- **Gedrag van v1**: Bij het unmarshalen naar een Go-array (`[N]T`) levert een JSON-array met een andere lengte dan de Go-array geen fout op. Is de JSON-array korter, dan worden de overige posities met nulwaarden gevuld; is hij langer, dan wordt het surplus weggegooid.
- **Gedrag van v2**: De lengte van de JSON-array moet **exact overeenkomen** met de lengte van de Go-array. Anders wordt een fout geretourneerd.
- **Reden voor de wijziging**: In Go hebben vaste arrays vaak een betekenisvolle lengte. Het gedrag van v1 kan stil dataverlies veroorzaken. v2 hanteert strengere regels om de correctheid te verhogen.
- **Gerelateerde optie**: Met de optie `json.UnmarshalArrayFromAnyLength(true)` kan het gedrag van v1 worden nagebootst.

### 6. Verwerking van `time.Duration`

- **Gedrag van v1**: `time.Duration` wordt intern als `int64` behandeld en gecodeerd als een JSON-getal in nanoseconden.
- **Gedrag van v2**: Met de methode `time.Duration.String()` wordt gecodeerd als een JSON-string in een formaat zoals `"1h2m3s"`.
- **Reden voor de wijziging**: Getallen in nanoseconden zijn moeilijk leesbaar, en de standaard stringrepresentatie van `time.Duration` is nuttiger.
- **Gerelateerde opties**: Met de tagoptie `json:",format:nano"` of de optie `json.FormatTimeWithLegacySemantics(true)` kan het gedrag van v1 worden gebruikt.

### 7. Verwerking van ongeldige UTF-8

- **Gedrag van v1**: Bij marshalen/unmarshalen worden ongeldige UTF-8-bytes in strings stil vervangen door het Unicode-vervangingsteken (`\uFFFD`).
- **Gedrag van v2**: Standaard wordt een fout geretourneerd wanneer ongeldige UTF-8 wordt aangetroffen.
- **Reden voor de wijziging**: Dit voorkomt stille dataschade en sluit aan bij de strengere JSON-standaard (RFC 7493).
- **Gerelateerde optie**: Met de optie `jsontext.AllowInvalidUTF8(true)` kan het gedrag van v1 worden nagebootst.

### 8. Verwerking van dubbele objectledennamen

- **Gedrag van v1**: Een JSON-object mag leden met dezelfde naam bevatten. De laatst voorkomende waarde overschrijft de eerdere.
- **Gedrag van v2**: Standaard wordt een fout geretourneerd wanneer dubbele ledennamen voorkomen.
- **Reden voor de wijziging**: RFC 8259 definieert het gedrag bij dubbele namen niet, waardoor implementaties verschillend kunnen reageren. Dit kan een bron van beveiligingskwetsbaarheden zijn. v2 wijst dit expliciet af om correctheid en beveiliging te verhogen.
- **Gerelateerde optie**: Met de optie `jsontext.AllowDuplicateNames(true)` kan het gedrag van v1 worden nagebootst.

---

## Verschillen in implementatie en architectuur

- **v1**: Vertrouwt sterk op `decodeState` in `decode.go` en op de handmatig geschreven state machine in `scanner.go`. Dit is een monolithische structuur waarin parsinglogica en semantische analyse sterk met elkaar verweven zijn.
- **v2**: De architectuur is modularer.
  - **`encoding/json/jsontext`**: Biedt een laagniveau, hoogpresterende JSON-tokenizer (`Decoder`) en encoder (`Encoder`). Dit pakket richt zich uitsluitend op de syntactische aspecten van JSON.
  - **`encoding/json/v2`**: Verwerkt, op basis van `jsontext`, de semantische conversie tussen Go-typen en JSON-waarden.
  - Door deze scheiding zijn syntactische en semantische analyse van elkaar losgekoppeld, wat de duidelijkheid van de code en de prestaties heeft verbeterd.

---

## Nieuwe API en functies in v2

v2 biedt via het optiesysteem `json.Options* een zeer flexibele besturing.

- **`json.Options`**: Een verzameling opties die het marshal- en unmarshalgedrag wijzigen.
- **`json.JoinOptions(...)`**: Voegt meerdere opties samen tot één.
- **`WithMarshalers` / `WithUnmarshalers`**: Een krachtige functie waarmee serialisatie- en deserialisatielogica voor specifieke typen kan worden geïnjecteerd zonder dat de interfaces `Marshaler` of `Unmarshaler` hoeven te worden geïmplementeerd. Dit is vooral nuttig bij het verwerken van typen uit externe pakketten.
- **Nieuwe opties**: `RejectUnknownMembers`, `Deterministic(false)`, `FormatNilSliceAsNull` en andere opties maken een reeks gedragsinstellingen mogelijk die in v1 niet beschikbaar waren.

---

## Conclusie

`encoding/json` v2 is een moderne implementatie die, voortbouwend op de ervaring met v1, **correctheid, prestaties en flexibiliteit** aanzienlijk verbetert. Het standaardgedrag is strenger geworden, maar via het geavanceerde `Options`-systeem worden alle gedragingen van v1 volledig ondersteund. Hierdoor kunnen bestaande code-compatibiliteit behouden blijven en kunnen de voordelen van v2 geleidelijk worden ingevoerd.

- Voor **nieuwe projecten** is het aan te raden v2 als standaard te gebruiken.
- **Bestaande projecten** kunnen `jsonv1` ongewijzigd blijven gebruiken, of migreren naar `jsonv2` en via `DefaultOptionsV1()` geleidelijk de strengere gedragingen van v2 invoeren.
