Go 1.26 Shipped Without JSON v2 — Here’s the Real Reason Why

The most important standard library rewrite in Go’s history is still experimental — and that’s by design.

分享
Go 1.26 Shipped Without JSON v2 — Here’s the Real Reason Why

On February 10, 2026, Go 1.26 landed with a bang. The Green Tea garbage collector is now the default. The go fix tool got a comprehensive modernization overhaul. Cgo call overhead dropped by 30%. Heap address randomization arrived for 64-bit platforms. It was, by any measure, a milestone release.

And yet, the feature many developers had been waiting for — encoding/json/v2 — was conspicuously absent from the stable API. It remains behind the GOEXPERIMENT=jsonv2 flag, still experimental, still not ready.

If you’ve been tracking this from the sidelines, you might be frustrated. Five years of development, and it’s still not done? But if you dig into the tracking issue #76406 and the related proposal #71497, the picture becomes clear: this delay isn’t about slow engineering. It’s about API perfectionism, a grueling backward compatibility challenge, and a memory regression that almost derailed the entire project.

Let’s break it down.

The Big Picture: Why JSON v2 Matters

The current encoding/json (let's call it v1) has served Go developers faithfully for over a decade. But its design flaws have been accumulating like technical debt in a startup that never refactored. Here's the short list:

  • It silently accepts invalid UTF-8 in JSON strings
  • It allows duplicate keys without complaint
  • Custom Marshaler implementations can't access configuration options
  • There’s no way to reject trailing data after a valid JSON document
  • The “streaming” API is largely a fiction — under the hood, it buffers everything

These aren’t minor annoyances. In production environments, they’re security attack vectors and silent data corruption risks. RFC 8259 compliance? Not quite.

JSON v2, led by Joe Tsai and Daniel Martí, is the most ambitious standard library rewrite since generics landed. It aims to fix all of the above — and deliver massive performance improvements along the way.

A New Architecture: Syntax vs. Semantics

One of v2’s most elegant design decisions is the strict separation of JSON processing into two layers:

  • encoding/json/jsontext — The syntactic layer. Handles pure JSON tokenizing, parsing, and encoding. No reflection, no Go types. Think of it as a high-performance JSON scanner.
  • encoding/json/v2 — The semantic layer. Maps between Go types and JSON data, built on top of jsontext.

This separation is powerful. If you only need to validate or transform raw JSON without touching Go structs, you can use jsontext directly — no reflection overhead, true streaming, minimal allocations.

Here’s how the two versions compare:

That last row is worth emphasizing. Libraries like Sonic (ByteDance) achieve blazing speed by leaning heavily on unsafe. JSON v2 reaches comparable performance without compromising memory safety. That's the standard library's promise: you don't trade correctness for speed.

Why It Didn’t Ship: The Four Blockers

So if v2 is this good, why isn’t it in Go 1.26? The tracking issue #76406 reveals four distinct battlefronts.

The “Forever API” Constraint

In Go’s standard library philosophy, once an API exits the experimental phase and enters encoding/json, it falls under the Go 1 compatibility promise. Forever. No take-backs.

Joe Tsai has been explicit about this: performance bugs can be fixed later, but API design flaws can accumulate into decades of technical debt.

The current audit is focused on jsontext's public interface. As the foundation layer, its design must balance high performance with ergonomics — particularly around Token handling and optimal interaction patterns with io.Reader/io.Writer.

There’s also the contentious time.Duration debate. V1 serializes durations as nanosecond integers — widely adopted but terrible for cross-language interoperability. V2 leans toward Go-style strings like "1h2m3s", but this has sparked fierce discussions about standardization. Neither camp is backing down, and the API signature remains in flux.

The “Perfect Fidelity” v1 Shim

Here’s the ambitious plan: once v2 lands, the Go team wants to maintain only one codebase. The existing encoding/json would become a thin wrapper (shim) around the v2 engine.

The catch? The v2 engine must perfectly replicate every behavior of v1 — including the bugs, the undocumented quirks, and the edge cases that thousands of production applications accidentally depend on.

For example, v1 has specific inconsistencies when handling pointer-receiver Marshalers on non-addressable values. Many existing applications unknowingly rely on this behavior.

Replicating these subtle, hard-to-document behavioral regressions inside a fundamentally different engine is an extraordinary engineering challenge. The team is currently “burning down” the list of micro-behavioral deviations discovered through large-scale testing. Progress is steady, but the tail is long.

The Memory Regression Bomb: Issue #75026

This was the most alarming blocker. During testing in Go 1.25 and 1.26, issue #75026 reported a catastrophic memory-allocation regression in specific map-serialization scenarios.

The numbers speak for themselves:

https://github.com/golang/go/issues/75026

Yes, you’re reading that correctly. A 39x increase in memory allocation for a common map encoding pattern.

Profiling revealed that 92.98% of allocations were concentrated in bytes.growSlice, pointing to a severe buffer management defect when handling complex object trees or specific map structures. While the total number of allocations (Allocs/op) actually decreased, the per-allocation size exploded, putting enormous pressure on the GC.

If this regression had shipped as the default behavior for v1 users, it would have been catastrophic. Fixing these extreme-case allocation paths is currently the highest priority work item.

4. Ecosystem Maturity and the Union Type Debate

The JSON v2 project has been in development for five years. While it’s been validated in many production environments, it still needs broader ecosystem stress testing before becoming the default. The Project 50 tracker shows that out of 44 sub-tasks, roughly 18 remain open or require further audit.

Additionally, there’s an ongoing debate about whether v2 should support union (sum) types for JSON deserialization. Some developers argue this is table stakes for modern JSON processing. The Go team’s position? Wait for a proper language-level sum type proposal (like #57644) to mature first, rather than baking a temporary, incompatible implementation into the JSON package.

This is classic Go pragmatism: don’t solve language problems at the library level.

The Features That Make v2 Worth the Wait

Despite the delays, the experimental version already showcases features that will fundamentally change how we handle JSON in Go.

omitzero — Finally, Sane Omission Logic

V1’s omitempty has confused developers for years. Is time.Time{} empty? Is false empty? The answers are inconsistent and often surprising.

V2 introduces omitzero, which evaluates strictly against Go's zero value semantics and supports a custom IsZero() bool interface:

type Event struct { 
    Name      string    `json:"name"` 
    StartTime time.Time `json:"start_time,omitzero"` 
    EndTime   time.Time `json:"end_time,omitzero"` 
}

This is particularly useful for PATCH-style APIs where you need to serialize only explicitly modified fields, without accidentally omitting fields that happen to hold a type’s default zero value.

inline and unknown — Flexible Data Modeling

Two new struct tags solve long-standing pain points:

  • inline: Flatten a nested struct or map into the parent JSON object — without anonymous embedding. This is huge for APIs with dynamic key-value pairs.
  • unknown: Designate a field (typically map[string]jsontext.Value) to capture all JSON members not defined in the struct. No more "double deserialization" overhead.
type Config struct { 
    Version  int                          `json:"version"` 
    Name     string                       `json:"name"` 
    Extra    map[string]jsontext.Value    `json:",unknown"` 
}

format — Built-In Encoding Customization

The format option enables per-field customization for encoding:

  • Custom Base64 or Hex encoding for []byte fields
  • Custom time.Time layout strings
  • No more writing bespoke Marshaler/Unmarshaler implementations for common formatting needs

Performance: The Benchmark Story

Based on the jsonbench evaluation suite, here's how v2 stacks up against the competition:

The key insight: v2 achieves its performance gains through iterative, linear parsing rather than v1’s per-byte virtual function scanning. And it does this without a single unsafe.Pointer. That's not just fast — that's responsibly fast.

The Road Ahead: When Will v2 Land?

Based on the current burn-down rate and the developer activity on #76406, here’s the realistic timeline:

First Half of 2026:

  1. Fix Issue #75026 — Resolve the map encoding memory regression. This is the single most critical prerequisite.
  2. API Finalization — Complete the audit of all public functions in jsontext, particularly the Encoder/Decoder state machine robustness in streaming scenarios.
  3. v1 Compatibility Layer — Ensure all known v1 behaviors (including the “buggy” ones) have corresponding configuration options in the v2 engine.

Go 1.27 (Expected August 2026):

This is widely considered the earliest and most likely window for JSON v2 to drop the experimental flag and enter the stable standard library. The tree reopening for the 1.27 development cycle will be the critical signal to watch.

The Go team also plans to ship modernization tools alongside the v2 release. The revamped go fix will offer one-click migration from v1 to v2 defaults — not just string replacement, but type-aware transformations. For example, it could detect hand-rolled Base64 conversion logic and suggest replacing it with v2's format:base64 struct tag.

Summary

  1. JSON v2’s delay is not a failure — it’s discipline. The Go team refuses to ship an API with design flaws that would become permanent technical debt under the Go 1 compatibility guarantee.
  2. The architecture is sound. The syntax/semantics split via jsontext and v2 is an elegant and future-proof design.
  3. Performance is already proven. Up to 10x faster unmarshaling, 3.6x faster marshaling — all without unsafe.
  4. The memory regression (#75026) is the key blocker. A 39x allocation increase in map encoding must be resolved before v2 can become the default.
  5. Go 1.27 (August 2026) is the target. The community should plan accordingly.

For developers, here’s my practical advice:

  • Internal tools: Go ahead and try GOEXPERIMENT=jsonv2 in non-critical systems. The omitzero and unknown features alone can simplify your code significantly. File bug reports — the team needs them.
  • Performance-sensitive apps: If JSON unmarshaling is your CPU bottleneck, v2’s experimental build may already outperform v1 — and it’s safer than third-party unsafe libraries.
  • Public libraries: Stick with encoding/json (v1) for now. The v2 API is still subject to breaking changes, and your users won't appreciate the instability.

JSON v2 will be the most significant library-level evolution in Go since generics. It doesn’t just make things faster — it closes a decade-long gap in Go’s data interchange standards compliance. As blockers are cleared one by one, a safer, faster, and more flexible era of JSON processing is coming with Go 1.27.

The wait is almost over. And it will have been worth it.

References

  • Go Issue #76406 — JSON v2 Tracking Issue
  • Go Proposal #71497 — encoding/json/v2 proposal
  • Go Issue #75026 — Memory allocation regression in jsonv2 experiment
  • Go Issue #57644 — Sum types language proposal
  • Go 1.26 Release Notes