deniz.in

Markets

Weather

Loading weather

· via Hacker News – Front Page (native)

Go 1.27 ships stable encoding/json/v2 with stricter, faster JSON handling

The Go team has declared encoding/json/v2 and encoding/json/jsontext stable in Go 1.27, replacing an API whose lenient parsing, nil-to-null marshaling and rigid interfaces could not be fixed under the compatibility promise.

Go 1.27 ships stable encoding/json/v2 with stricter, faster JSON handling

Go's JSON overhaul arrives in Go 1.27

The Go team has marked its reworked JSON stack as stable: with Go 1.27, the encoding/json/v2 and encoding/json/jsontext packages are generally available. The announcement came in a post on the official Go blog dated 4 November 2026, credited to Michael Pratt, Joe Tsai and five other contributors, and the story reached the front page of Hacker News. The post updates an earlier article from last year, when the design was still an experiment.

According to the post, JSON is the most heavily used data format on the internet, and Go has shipped JSON support in its standard library since before Go 1.0 — the team first wrote about it more than 15 years ago. After collecting feedback on the experiment, the team concluded that the original encoding/json package has problems that cannot be repaired in place.

The behavior flaws in encoding/json

The post groups the old package's shortcomings into behavior flaws and API or performance limits. On behavior:

  • Invalid UTF-8 is accepted, even though RFC 8259, the current Internet Standard for JSON, requires valid UTF-8. The old package lets such input through quietly, corrupting data in ways that surface later.
  • Objects with duplicate member names are accepted. RFC 8259 does not define what a duplicated name means, and the authors point to CVE-2017-12635 as a case where that ambiguity was exploited in an attack.
  • Nil slices and maps are marshaled as JSON null, which breaks interoperability with implementations that refuse null where an array or object is expected. A survey cited in the post found that most Go users would prefer an empty array or object by default.
  • When unmarshaling, member names are matched to struct fields without regard to letter case. The authors call this default surprising, a security risk and a performance drag.
  • MarshalJSON methods declared on pointer receivers are invoked inconsistently. The team regards this as a bug but says too many applications depend on the current behavior for it to change.

API and performance limits

The old API is also restrictive. Options such as DisallowUnknownFields exist only on the Encoder and Decoder types: they cannot be combined with the plain Marshal and Unmarshal functions, and they lose their effect once a custom UnmarshalJSON implementation takes over. Decoding with json.NewDecoder(r).Decode(v) fails to notice extra data after the JSON value. Formatting helpers like Compact, Indent and HTMLEscape can only write to a bytes.Buffer.

Performance is boxed in by the interface contracts. MarshalJSON must return an allocated []byte, and the package must verify and reformat that output. UnmarshalJSON requires a complete JSON value, so the package parses the value once just to locate its end, and the method parses it again. Recursive calls through Marshal or Unmarshal turn quadratic. And although Encoder and Decoder wrap io.Writer and io.Reader, entire values are buffered in memory; the token-reading method allocates heavily and there is no counterpart for writing tokens.

The team says it considered fixing encoding/json directly, but the problems are inherent consequences of the existing API and cannot be addressed within the Go 1 compatibility promise. Adding names like MarshalV2 to the same package would amount to growing a second API alongside the first, which is why the work landed in a separate package.

A two-layer replacement

The redesign splits JSON handling into two layers. encoding/json/jsontext handles pure syntax: encoding and decoding according to JSON's grammar. Its Encoder and Decoder sit directly on io.Writer and io.Reader, with methods for whole values (WriteValue, ReadValue) and individual tokens (WriteToken, ReadToken), plus Value, Token and Kind types. encoding/json/v2 builds on that foundation and owns the semantics: marshaling and unmarshaling, the mapping between JSON values and Go values.

The post argues that the safer defaults are to report invalid UTF-8 as an error and to reject duplicate member names rather than pick an arbitrary interpretation of ambiguous input.

Why it matters

Nearly every Go program that communicates over the network touches JSON, so the defaults of the standard library shape how a large ecosystem parses untrusted input. Rejecting invalid UTF-8 and duplicate names removes ambiguity that has already produced at least one documented vulnerability. Separating syntax from marshaling gives library authors a low-level, streaming-friendly foundation, and revising the interface contracts removes allocation and double-parsing penalties. The old package remains untouched under the compatibility promise, so the practical question for teams is when new code should move to v2.

  • #go
  • #json
  • #standard-library
  • #api-design

Related posts