encoding/json vs encoding/json/v2 in Go 1.27: Key Differences Explained

Go 1.27 introduces encoding/json/v2, a stricter alternative to the standard encoding/json package that rejects invalid UTF-8 and duplicate keys while changing nil serialization defaults to enforce JSON spec compliance.

The Go standard library's approach to JSON handling underwent a significant evolution with the Go 1.27 release. According to the JetBrains/go-modern-guidelines repository, the new encoding/json/v2 module provides the same public API as its predecessor but enforces stricter conformance to the JSON specification, fundamentally altering how edge cases like invalid UTF-8 sequences and duplicate object keys are processed.

Critical Behavioral Differences Between encoding/json Versions

The v2 package maintains API compatibility with v1 but implements breaking behavioral changes in four critical areas.

Strict UTF-8 Validation

In the legacy encoding/json, invalid UTF-8 sequences are silently accepted and replaced during marshaling. The encoding/json/v2 implementation rejects invalid UTF-8 content entirely, returning a validation error rather than producing non-compliant JSON output.

Duplicate Key Rejection

While the v1 package keeps the last occurrence of duplicate object keys without warning, encoding/json/v2 treats duplicate keys as an error condition. This prevents silent data loss when unmarshaling objects with repeated fields.

Nil Serialization Changes

The v1 package serializes nil slices and maps as null values. Conversely, encoding/json/v2 marshals nil slices as empty arrays [] and nil maps as empty objects {}, eliminating ambiguity between absent and empty collections.

Enhanced Type Validation

encoding/json permits many Go types that lack clean JSON representations. The v2 package reports explicit errors for types that cannot sensibly serialize to JSON, catching encoding issues at the API boundary rather than during transmission.

Practical Code Comparison

The following examples demonstrate how identical structs produce different JSON output between versions.

Legacy encoding/json Behavior

import "encoding/json"

type User struct {
    Name string
    Tags []string // nil slice encodes as null
}

func main() {
    u := User{Name: "Alice"}
    // Marshals Tags as null
    data, _ := json.Marshal(u)
    fmt.Println(string(data)) // {"Name":"Alice","Tags":null}
}

New encoding/json/v2 Behavior

import json "encoding/json/v2"

type User struct {
    Name string
    Tags []string // nil slice encodes as empty array
}

func main() {
    u := User{Name: "Alice"}
    // Marshals Tags as []
    data, err := json.Marshal(u)
    if err != nil {
        log.Fatalf("marshal error: %v", err)
    }
    fmt.Println(string(data)) // {"Name":"Alice","Tags":[]}
}

Migration Strategy and Compatibility

The import path changes from import "encoding/json" to import "encoding/json/v2" (commonly aliased as json). The guidelines in internal/guidelines/guidelines.json explicitly recommend adopting v2 for new code only, as simple import changes alter wire formats for nil values and may cause previously working systems to error on invalid UTF-8 or duplicates.

For gradual migration, the v2 package provides compatibility helpers such as jsonv1.DefaultOptionsV1() to retain prior behavior during transition periods. Existing codebases should only migrate after deliberate planning, as the stricter defaults may break API contracts with consumers expecting null for empty collections.

Source Files and Implementation Details

The JetBrains/go-modern-guidelines repository documents these differences across three key locations:

  • internal/guidelines/guidelines.json — Contains the core recommendation to prefer encoding/json/v2 for new projects while cautioning against hasty migration of existing code.
  • FEATURES.md — Lists practical implications of the behavioral changes, including wire format differences and error handling modifications.
  • CHANGELOG.md — Documents the addition of v2 guidance and tracks version-specific implementation details.

Summary

  • encoding/json/v2 rejects invalid UTF-8 sequences rather than silently replacing them with replacement characters.
  • Duplicate object keys generate errors instead of silently overwriting earlier values.
  • Nil slices and maps serialize as empty collections ([] and {}) rather than null.
  • Stricter type validation prevents marshaling of Go types that lack sensible JSON representations.
  • New projects should default to v2 immediately, while existing code requires planned migration using compatibility helpers.
  • The public API remains unchanged, but behavioral defaults differ significantly enough to affect API consumers.

Frequently Asked Questions

Can I use encoding/json/v2 in Go versions earlier than 1.27?

No, encoding/json/v2 was introduced specifically in Go 1.27. Earlier Go versions must continue using the legacy encoding/json package or third-party alternatives, as the v2 module relies on runtime features and standard library changes available only in 1.27+.

Will existing code break if I change the import to encoding/json/v2?

Yes, changing the import path without additional configuration will alter serialization behavior. The v2 package uses stricter defaults that may cause previously working code to return errors for invalid UTF-8 or duplicate keys, and nil values will serialize differently, potentially breaking API contracts.

How do I maintain v1 behavior while using the v2 package?

The encoding/json/v2 module provides compatibility helpers such as jsonv1.DefaultOptionsV1() that configure the v2 encoder to mimic legacy behavior. However, the repository guidelines recommend using these only during explicit migration periods rather than as a permanent solution for new development.

Should I migrate existing production code to encoding/json/v2 immediately?

According to internal/guidelines/guidelines.json, you should adopt encoding/json/v2 for new code only. Existing codebases should migrate only after deliberate planning and comprehensive testing, as the wire format changes for nil values and stricter validation may affect downstream API consumers.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →