The official blog post frames v2 as addressing specific, long-standing pain points: mandatory HTML escaping, inability to reject unknown fields from Unmarshal, silent acceptance of duplicate keys (a documented security hazard), and inflexible time formatting. The Go team argues these aren't cosmetic issues but have cost real engineering time and caused real CVEs across the ecosystem.
By submitting the Go blog post to HN and attracting 278 points, the submitter signals that this rewrite is significant news for the Go community. The high engagement reflects widespread recognition among Go developers that encoding/json's warts have been a daily friction for over a decade.
While the 2-3x marshal and 2-9x unmarshal speedups grab attention, the Go team emphasizes that the gains come from a fundamental redesign: a byte-slice tokenizer without intermediate allocations, clean separation between syntactic and semantic processing, and removal of reflection from the hot path. This architecture — not micro-optimizations — is what enables both the speed and the new API flexibility.
Rather than breaking the Go 1 compatibility promise, the team re-implements v1 as a thin shim delegating to v2 under a compatibility mode. Existing code keeps compiling and behaving identically, while new code opts into v2 semantics explicitly via import — a pattern that respects Go's famously strict backward compatibility guarantees while still allowing meaningful evolution.
The Go team published an official blog post introducing `encoding/json/v2`, a ground-up rewrite of the package that virtually every production Go service depends on. This is not a patch release. It's a parallel package that reimagines JSON handling in Go after 15 years of accumulated workarounds, performance complaints, and API regrets. The package was proposed in discussion #71497, developed in a public experimental repo, and is now landing in the standard library behind the `GOEXPERIMENT=jsonv2` flag with a target of stable availability in an upcoming Go release.
The headline numbers, drawn from the proposal's benchmarks: marshaling is roughly 2-3x faster than v1 for typical payloads, and unmarshaling is 2-9x faster depending on structure depth and type complexity. The gains come from a redesigned tokenizer that operates on byte slices without intermediate allocations, a cleaner separation between syntactic and semantic processing, and the removal of reflection from the hot path for common types.
The v1 package isn't going away. Instead, `encoding/json` v1 is being re-implemented as a thin shim that delegates to v2 under a compatibility mode. Existing code continues to compile and behave identically. New code that imports `encoding/json/v2` opts into the new semantics and the new API surface.
The `encoding/json` package has been one of Go's most-complained-about standard library components for over a decade. The complaints aren't academic — they're the kind of thing that costs engineering teams real time. You cannot turn off HTML escaping without wrapping an `Encoder`. You cannot reject unknown fields without a separate method call that only works on the decoder, not on `Unmarshal`. You cannot customize time formatting without implementing `MarshalJSON` on every type that embeds a `time.Time`. Duplicate object keys are silently accepted, which has been the root cause of multiple security advisories across the Go ecosystem.
v2 addresses each of these with an `Options` pattern — functional options passed to `Marshal` and `Unmarshal` that compose cleanly. Want to reject unknown fields? `json.RejectUnknownMembers(true)`. Want to marshal with indentation? `jsontext.WithIndent(" ")`. Want a specific time format? `json.FormatTimeWithLegacySemantics(false)` plus a time-specific option. The old pattern of wrapping encoders/decoders in builder chains, or worse, defining custom types just to get one knob, is gone.
The performance story deserves a closer look. The v2 proposal cites benchmarks against `encoding/json` v1, `jsoniter`, `goccy/go-json`, and `segmentio/encoding`. v2 beats v1 across the board, and for marshaling it approaches `goccy/go-json` — the fastest third-party library most Go teams reach for. For unmarshaling deeply nested structures, v2 is often faster than all of them because it avoids the reflection-heavy decode paths the others still rely on for edge cases. If you've been running `jsoniter` or `goccy/go-json` in production purely for the speedup, v2 removes the main reason to carry a non-stdlib dependency for JSON.
The semantic changes are the part that will trip teams up on migration. `time.Time` now serializes to RFC 3339 with nanoseconds by default, where v1 used the `MarshalJSON` method's RFC 3339 variant without sub-second precision in some paths. `nil` slices marshal to `null` instead of `[]` by default — a change that reverses one of the most-asked-about v1 behaviors, but one that will break clients expecting empty arrays. Duplicate object keys in incoming JSON cause an error by default, which is the correct behavior per RFC 8259 but will reject inputs from sloppy upstream systems. Each of these has an option to restore v1 semantics, but the defaults are different.
Community reaction on the Hacker News thread has been uncharacteristically positive for a Go announcement. The top comments focus on how long this has taken — Daniel Martí, Russ Cox, and Joe Tsai have been iterating on this for years in public — and on relief that the Go team is willing to break from the original API's defaults when the original defaults were wrong. The subtext is that Go, famously allergic to breaking changes, has found a path to meaningful improvement via parallel packages rather than version bumps. That pattern, if it holds, matters as much as the JSON changes themselves.
If you maintain a Go service, the migration calculus is simple for now: do nothing. v1 continues to work, backed by v2's implementation, which means you get some of the performance improvements for free when you upgrade Go. Benchmark your hot paths after the Go version bump — some workloads will see 20-40% throughput improvements on JSON-heavy endpoints with zero code changes.
When you're ready to opt in, pick one service with a well-defined JSON surface and migrate it. The import path changes from `encoding/json` to `encoding/json/v2`, and you'll want to audit three things: time serialization (are clients parsing RFC 3339 with nanoseconds?), empty slice handling (does any downstream consumer rely on `[]` instead of `null`?), and duplicate key tolerance (does your API accept merged payloads from legacy clients?). The `FormatNilSliceAsNull(false)` and `AllowDuplicateNames(true)` options restore v1 behavior if you need a staged rollout.
For library authors, v2 is where you want to be. The `MarshalerV2` and `UnmarshalerV2` interfaces give you access to the tokenizer directly, which means you can write custom marshalers that don't allocate an intermediate `[]byte`. If you ship a JSON-adjacent library (query builders, schema validators, config parsers) and you currently take a `json.RawMessage` or `[]byte`, consider adding a v2-aware path. The performance delta is large enough that users will notice.
The deeper story here is about stdlib governance. Go's team has historically defended the standard library's stability to a fault, which meant that known-bad APIs calcified for a decade. v2 proves there's a middle path: keep the old package working as a compatibility layer, put the new design behind a new import path, and let the ecosystem migrate on its own schedule. If `encoding/json/v2` lands cleanly, expect the same pattern applied to `net/http` (the `http.Handler` shape has its own list of regrets) and `database/sql` (which has been begging for a v2 since context support landed). The JSON rewrite isn't just a faster parser — it's a template for how Go evolves its most entrenched APIs without breaking the production code that depends on them.
To preserve the order when processing JSON a workaround has been introduced:https://news.ycombinator.com/item?id=44893833Still searching the true solution:https://github.com/golang/go/issues/27179#issuecomment-53634...A proposal of the missing datatype fo
> "JavaScript Object Notation (JSON) is a simple data interchange format [...]"Clearly it isn't, otherwise every language would have sorted it out by... a long time ago.
I generally like `encoding/json/v2` and I'm glad they did it.I wish they'd left `MarshalIndent` in there though. The equivalent code is super complicated and non-obvious, so it's a pain to migrate, and it's a pain when you're banging out some quick code and want to
I used the jsontext package at my company when we needed to make a parser for challenging clients (specific errors for each kind of mistake) while still keeping performance, and it works like a charm.There is no introspection and speed is superb. I would definitely recommend jsontext as a parser for
Top 10 dev stories every morning at 8am UTC. AI-curated. Retro terminal HTML email.
I'm working on better SIMD support in Dart and one of the motivations for that work is specializing parser specifications based on schema specifications of that language.Or more concretely, if we had a parser generator + json parser specification that can generate efficient simdjson style parse