---
id: 61f2009278a5f7366f79395da59447cf
author: lemonmint
title: Analyse der Verhaltensänderungen in encoding/json/v2 unter Go 1.25 und der Kompatibilitätsschicht zu v1
description: Dieser Vergleich untersucht die Groß- und Kleinschreibung bei der Zuordnung, die geänderte Semantik von omitempty und omitzero, die Serialisierung von nil-Slices und nil-Maps sowie die Optionen von encoding/json/v2, das in Go 1.25 als Experiment (goexperiment.jsonv2) eingeführt wurde.
language: de
date: 2025-06-20T05:38:20.100520237Z
path: /blog/posts/go-1-25-encoding-json-v1-vs-v2-z74a36b08
category: release
---

Das Paket `encoding/json` in Version 2 von Go ist eine neue Implementierung, die mehrere Nachteile der bisherigen Version 1 (mangelnde Konsistenz, überraschendes Verhalten, Leistungsprobleme) beheben soll. Sie wird über den Build-Tag `goexperiment.jsonv2` aktiviert und stellt ein experimentelles Feature dar.

Am wichtigsten ist, dass **bei aktiviertem v2 die v1-API als Kompatibilitätsschicht auf der v2-Implementierung arbeitet, die das Verhalten von v1 emuliert**. Dies geschieht über die Funktion `DefaultOptionsV1()` in der Datei `v2_v1.go`. Somit bietet v2 Optionen, mit denen sich das Verhalten von v1 vollständig reproduzieren lässt, und führt gleichzeitig strengere und vorhersehbarere neue Standardverhaltensweisen ein.

Die Hauptziele von v2 sind die folgenden:

1. **Verbesserte Korrektheit und Vorhersehbarkeit**: Durch standardmäßig strengere Regeln (z. B. Groß-/Kleinschreibung beachten, keine doppelten Schlüssel) wird unerwartetes Verhalten reduziert.
2. **Leistungsverbesserung**: Die Parsing- und Encoding-Engine wurde neu entworfen, um die Effizienz zu steigern.
3. **Mehr Flexibilität und Kontrolle**: Ein detailliertes Optionssystem ermöglicht es Entwicklern, die JSON-Verarbeitung präzise zu steuern.

---

## Wesentliche Unterschiede in Bedeutung und Verhalten

Die Unterschiede zwischen v1 und v2 werden im Folgenden anhand der Datei `v2_test.go` nach Punkten geordnet dargestellt.

### 1. Abgleich von Feldnamen (Groß-/Kleinschreibung)

- **Verhalten in v1**: Beim Unmarshaling von JSON-Objektmitgliedern in Go-Struct-Felder wird **die Groß-/Kleinschreibung ignoriert (case-insensitive)**. Sowohl `"FirstName"` als auch `"firstname"` werden dem Feld `FirstName` zugeordnet.
- **Verhalten in v2**: Standardmäßig wird **die Groß-/Kleinschreibung berücksichtigt (case-sensitive)**, und nur exakt übereinstimmende Felder werden zugeordnet.
- **Grund der Änderung**: Ein Abgleich ohne Berücksichtigung der Groß-/Kleinschreibung kann unerwartetes Verhalten verursachen und führt beim Verarbeiten nicht passender Felder zu Leistungseinbußen. v2 übernimmt daher als Standard ein klareres und vorhersehbareres Verhalten.
- **Zugehörige Optionen**: In v2 kann die Groß-/Kleinschreibung-Ignorierung feldweise über das Tag-Attribut `json:"...,case:ignore"` explizit aktiviert werden oder global über die Option `json.MatchCaseInsensitiveNames(true)` angewendet werden.

### 2. Änderung der Bedeutung der Tag-Option `omitempty`

- **Verhalten in v1**: Felder werden anhand des „leeren Zustands" des Go-Wertes ausgelassen. Dabei bedeutet „leerer Zustand" `false`, `0`, `nil`-Zeiger bzw. `nil`-Interfaces sowie Arrays, Slices, Maps und Strings mit der Länge 0.
- **Verhalten in v2**: Felder werden anhand des „leeren Zustands" des kodierten JSON-Wertes ausgelassen. Das heißt, Felder werden ausgelassen, wenn sie zu `null`, `""`, `{}` oder `[]` kodiert werden.
- **Grund der Änderung**: Die Definition in v1 ist vom Typsystem von Go abhängig. v2 orientiert sich am JSON-Typsystem und bietet dadurch ein konsistenteres Verhalten. Beispielsweise wird in v1 der Wert `false` eines `bool`-Typs ausgelassen, in v2 dagegen nicht, da `false` ein nicht leerer JSON-Wert ist. In v2 kann die Option `omitzero` hinzugefügt werden, um das Verhalten zu ersetzen, bei dem `omitempty` in v1 auf `0` oder `false` angewendet wurde.
- **Zugehörige Optionen**: Wenn in v2 dasselbe Verhalten wie in v1 gewünscht ist, wird die Option `json.OmitEmptyWithLegacyDefinition(true)` verwendet.

### 3. Änderung des Verhaltens der Tag-Option `string`

- **Verhalten in v1**: Die Option wird auf Felder vom Typ Zahl, Boolean oder String angewendet. Der entsprechende Wert wird erneut innerhalb eines JSON-Strings kodiert (z. B. `int(42)` -> `"42"`). Auf Werte innerhalb zusammengesetzter Typen (Slices, Maps usw.) wird die Option nicht rekursiv angewendet.
- **Verhalten in v2**: Die Option wird **ausschließlich auf Zahlentypen angewendet** und **rekursiv angewendet**. Das heißt, auch Zahlen innerhalb eines Slices wie `[]int` werden alle als JSON-Strings kodiert.
- **Grund der Änderung**: Der Hauptzweck der Option `string` besteht darin, Zahlen als Strings darzustellen, um Präzisionsverluste bei 64-Bit-Ganzzahlen zu verhindern. Das Verhalten in v1 war eingeschränkt und wenig konsistent. v2 konzentriert sich auf diesen zentralen Zweck und erweitert das Verhalten rekursiv, um es nützlicher zu machen.
- **Zugehörige Optionen**: Mit der Option `json.StringifyWithLegacySemantics(true)` kann das Verhalten von v1 nachgeahmt werden.

### 4. Marshaling von `nil`-Slices und -Maps

- **Verhalten in v1**: `nil`-Slices und `nil`-Maps werden zu `null` marshaled.
- **Verhalten in v2**: Standardmäßig werden `nil`-Slices zu `[]` (leeres Array) und `nil`-Maps zu `{}` (leeres Objekt) marshaled.
- **Grund der Änderung**: `nil` ist ein Implementierungsdetail der Sprache Go, und es ist nicht wünschenswert, dies in einem sprachunabhängigen JSON-Format offenzulegen. Die Darstellungen `[]` und `{}` für leere Sammlungen sind allgemeiner verbreitet.
- **Zugehörige Optionen**: In v2 kann über die Optionen `json.FormatNilSliceAsNull(true)` oder `json.FormatNilMapAsNull(true)` ein Marshaling zu `null` wie in v1 erreicht werden.

### 5. Unmarshaling von Arrays

- **Verhalten in v1**: Beim Unmarshaling in ein Go-Array (`[N]T`) wird kein Fehler ausgelöst, selbst wenn die Länge des JSON-Arrays von der Länge des Go-Arrays abweicht. Ist das JSON-Array kürzer, werden die übrigen Elemente mit Null-Werten aufgefüllt; ist es länger, wird der Überschuss verworfen.
- **Verhalten in v2**: Die Länge des JSON-Arrays muss **genau mit der Länge des Go-Arrays übereinstimmen**. Andernfalls wird ein Fehler ausgelöst.
- **Grund der Änderung**: In Go ist die Länge eines Arrays fester Größe häufig von wesentlicher Bedeutung. Das Verhalten in v1 kann zu stillem Datenverlust führen. v2 erhöht die Korrektheit durch strengere Regeln.
- **Zugehörige Optionen**: Mit der Option `json.UnmarshalArrayFromAnyLength(true)` kann das Verhalten von v1 nachgeahmt werden.

### 6. Verarbeitung von `time.Duration`

- **Verhalten in v1**: `time.Duration` wird intern als `int64` behandelt und als JSON-Zahl in Nanosekunden kodiert.
- **Verhalten in v2**: Mithilfe der Methode `time.Duration.String()` wird als JSON-String in einem Format wie `"1h2m3s"` kodiert.
- **Grund der Änderung**: Nanosekunden als Zahlen sind schwer lesbar, während die standardisierte String-Darstellung von `time.Duration` nützlicher ist.
- **Zugehörige Optionen**: Über das Tag-Attribut `json:",format:nano"` oder die Option `json.FormatTimeWithLegacySemantics(true)` kann das Verhalten von v1 verwendet werden.

### 7. Verarbeitung ungültiger UTF-8-Sequenzen

- **Verhalten in v1**: Beim Marshaling und Unmarshaling werden ungültige UTF-8-Bytes innerhalb eines Strings **stillschweigend** durch das Unicode-Ersatzzeichen (`\uFFFD`) ersetzt.
- **Verhalten in v2**: Standardmäßig wird beim Auftreten ungültigen UTF-8 ein **Fehler zurückgegeben**.
- **Grund der Änderung**: Ziel ist es, stille Datenbeschädigung zu verhindern und sich an den strengeren JSON-Standard (RFC 7493) zu halten.
- **Zugehörige Optionen**: Mit der Option `jsontext.AllowInvalidUTF8(true)` kann das Verhalten von v1 nachgeahmt werden.

### 8. Verarbeitung doppelter Objektmitgliedsnamen

- **Verhalten in v1**: Innerhalb eines JSON-Objekts ist es erlaubt, dass Mitglieder mit demselben Namen mehrfach vorkommen. Der zuletzt auftretende Wert überschreibt die vorherigen.
- **Verhalten in v2**: Standardmäßig wird bei doppelten Mitgliedsnamen ein **Fehler zurückgegeben**.
- **Grund der Änderung**: Der Standard RFC 8259 definiert das Verhalten bei doppelten Namen nicht, sodass sich Implementierungen unterschiedlich verhalten können. Dies kann eine Ursache für Sicherheitslücken sein. v2 lehnt doppelte Namen ausdrücklich ab, um Korrektheit und Sicherheit zu erhöhen.
- **Zugehörige Optionen**: Mit der Option `jsontext.AllowDuplicateNames(true)` kann das Verhalten von v1 nachgeahmt werden.

---

## Unterschiede in Implementierung und Architektur

- **v1**: Stützt sich stark auf `decodeState` in `decode.go` und den manuell geschriebenen Zustandsautomaten (State Machine) in `scanner.go`. Dies ist eine monolithische Struktur, in der Parsing-Logik und semantische Analyse stark miteinander gekoppelt sind.
- **v2**: Die Architektur ist stärker modularisiert.
  - **`encoding/json/jsontext`**: Stellt einen leistungsstarken, low-level JSON-Tokenizer (`Decoder`) und Encoder (`Encoder`) bereit. Dieses Paket konzentriert sich ausschließlich auf die syntaktischen Aspekte von JSON.
  - **`encoding/json/v2`**: Baut auf `jsontext` auf und übernimmt die semantische Umwandlung zwischen Go-Typen und JSON-Werten.
  - Durch diese Trennung sind syntaktische und semantische Analyse voneinander getrennt, was Klarheit und Leistung des Codes verbessert.

---

## Neue API und Funktionen in v2

v2 bietet über das System `json.Options` eine sehr flexible Steuerung.

- **`json.Options`**: Eine Menge von Optionen, die das Marshaling- bzw. Unmarshaling-Verhalten verändern.
- **`json.JoinOptions(...)`**: Führt mehrere Optionen zu einer einzigen zusammen.
- **`WithMarshalers` / `WithUnmarshalers`**: Eine leistungsstarke Funktion, mit der Serialisierungs- und Deserialisierungslogik für bestimmte Typen eingefügt werden kann, ohne dass die Interfaces `Marshaler` bzw. `Unmarshaler` implementiert werden müssen. Dies ist besonders nützlich bei der Verarbeitung von Typen aus externen Paketen.
- **Neue Optionen**: `RejectUnknownMembers`, `Deterministic(false)`, `FormatNilSliceAsNull` und weitere ermöglichen eine Verhaltenssteuerung, die in v1 nicht möglich war.

---

## Fazit

`encoding/json` v2 ist eine moderne Implementierung, die auf den Erfahrungen mit v1 aufbaut und **Korrektheit, Leistung und Flexibilität** deutlich verbessert. Das Standardverhalten ist strenger geworden, doch durch das ausgefeilte System der `Options` lässt sich das gesamte Verhalten von v1 vollständig nachbilden. Dadurch kann die Kompatibilität mit bestehendem Code gewahrt bleiben, während die Vorteile von v2 schrittweise übernommen werden.

- Für **neue Projekte** empfiehlt es sich, v2 als Standard zu verwenden.
- **Bestehende Projekte** können `jsonv1` unverändert weiterverwenden oder zu `jsonv2` migrieren und dabei über `DefaultOptionsV1()` das strengere Verhalten von v2 schrittweise einführen.
