Go 1.27 encoding/json/v2 is stable: what changes and how to migrate
The Go team says encoding/json/v2 and jsontext are generally available in Go 1.27, with stricter defaults and unmarshal gains of up to 10x in its benchmarks.
· 7 min read
Written by AI from the sources linked below. How we use AI
On this page
The Go team published "A new Go API for JSON" on 4 November 2026, an updated version of its 2025 introduction to the experiment, to say that the new encoding/json/v2 and encoding/json/jsontext packages are stable and generally available in Go 1.27. The release itself shipped on 19 August 2026. For Go developers the practical news is that the standard library now has a second, stricter JSON API, the old encoding/json package is quietly running on top of it, and the team's migration guide lays out how to move over without changing behaviour by accident.
Key points
- Go 1.27 (released 19 August 2026) makes
encoding/json/v2andencoding/json/jsontextstable, with noGOEXPERIMENTflag needed; v2 had been an experiment since Go 1.25. - The existing
encoding/jsonpackage is now backed by the v2 implementation, and the Go team says marshaling and unmarshaling behaviour is preserved, though the exact text of error messages may differ. - v2 changes defaults: it rejects invalid UTF-8 and duplicate object member names, marshals nil slices and maps as
[]and{}, and matches field names case-sensitively. - The team reports unmarshal performance in v2 is significantly faster than v1, with benchmarks showing improvements of up to 10x; marshal performance is roughly at parity.
encoding/jsonstays under the Go 1 compatibility promise: nobody has to migrate.
What to do
You do not have to change anything for your program to keep working on Go 1.27, but a few checks are worth doing.
- Run your tests on Go 1.27 and look at error messages. The Go 1.27 release notes say the exact text of errors from
encoding/jsonmay differ. Code or tests that compare error strings are the likeliest to break. - If you want to try v2 with no behaviour change, import
encoding/json/v2and passjsonv1.DefaultOptionsV1()(fromencoding/json) to yourMarshalandUnmarshalcalls. The migration guide calls this step trivial and safe. - Then turn v2 behaviours on one at a time. Options exist for each difference; the Go blog's example selects all v2 behaviour except duplicate names with
json.Marshal(..., json.DefaultOptionsV2(), jsontext.AllowDuplicateName(true)). - Check tests that depend on map output order. VictoriaMetrics' tour of Go 1.27 notes that v1 always sorts map keys while v2 does not by default; the
json.Deterministicoption gives stable output, for example in golden-file tests. - Find out whether you are affected on a production service. The guide suggests
github.com/go-json-experiment/jsonsplitto run v1 and v2 side by side and report differences at runtime; it warns that calling both roughly doubles marshaling cost, andSetMarshalCallRatiocan limit checks to a sample.
What happened
The Go blog post, written by (Michael Pratt, Joe Tsai, Daniel Martí, Johan Brandhorst-Satzkorn, Roger Peppe, Chris Hines and Damien Neil), says that after additional feedback and refinement the packages are "stable and generally available in Go 1.27". The Go 1.27 release notes list two new packages: encoding/json/v2, a major revision of encoding/json, and encoding/json/jsontext, which provides lower-level syntactic processing of JSON.
The release notes add that the encoding/json package is now backed by the v2 implementation, that marshaling and unmarshaling behaviour is preserved, and that it gains a number of new options to configure v2 with v1 semantics. They also say the v1 API will continue to be supported and users are not required to migrate. The notes mention several changes made late in the experiment, such as renaming the inline struct tag option to embed, and changed behaviour for the string tag option.
Background
encoding/json has been in the standard library since before Go 1.0. The Go team's post lists the problems that accumulated and could not be fixed under the Go 1 compatibility promise:
- Syntax handling. It accepts invalid UTF-8, although RFC 8259 requires valid UTF-8, and it accepts objects with duplicate member names. The post notes duplicate names have been exploited before, citing CVE-2017-12635.
- Surprising defaults. A nil slice or map marshals to
null, which other JSON implementations may refuse to read into an array or object. Field names match case-insensitively, which the post calls a surprising default, a potential security vulnerability and a performance limit. - API gaps.
json.NewDecoder(r).Decode(v)does not reject trailing junk, options set on anEncodercannot be used withMarshal, and options are lost when a customUnmarshalJSONmethod is called. - Performance limits.
MarshalJSONforces an allocation and re-validation,UnmarshalJSONforces the library to parse a value twice, and recursive calls can become quadratic.
The fix took a long road. Daniel Martí drafted ideas for a v2 package in late 2020, and Joe Tsai, who had hit the limits of encoding/json in the Go protobuf protojson package, started a prototype with him. A GopherCon talk followed in late 2023, a formal proposal in early 2025, and a Go experiment in Go 1.25. The post says the effort took about six years and was largely developed by people not employed by Google.
How it works
v2 is split in two. jsontext handles syntax only: Encoder and Decoder types stream JSON values and tokens without using reflection. encoding/json/v2 sits on top and maps JSON to Go types. The core calls are Marshal, MarshalWrite, MarshalEncode, Unmarshal, UnmarshalRead and UnmarshalDecode, and each takes variadic Options, so behaviour is set per call instead of on an encoder object.
Types can now implement two new streaming interfaces, MarshalerTo and UnmarshalerFrom, that work directly with a jsontext.Encoder or Decoder. That removes the allocation and the double parse that MarshalJSON and UnmarshalJSON force, and lets options flow down the call stack. Callers can also override how any type is encoded with WithMarshalers and WithUnmarshalers.
The compatibility design is the most interesting part. The post says v1 is implemented in terms of v2 by passing the full set of v1 compatibility options, so both versions interoperate: a v2 marshal of a struct whose field type has a v1 MarshalJSON method works, and v1 honours a v2 MarshalJSONTo method. New struct tags such as embed therefore help v1 users too.
Behaviour differences
The post lists these defaults for v2:
- It reports an error on invalid UTF-8.
- It reports an error when an object has a duplicate name.
- It marshals a nil slice as
[]and a nil map as{}. - It unmarshals into struct fields with a case-sensitive name match.
omitemptynow omits a field if it would encode asnull,"",[]or{}.- It reports an error when asked to serialise a
time.Duration, which has no default representation, and provides options to choose one.
The migration guide adds a point that catches people out: in v1 a string tag on an invalid type, such as a struct, is ignored, whereas v2 returns an error. The recommended fix is to drop the tag. Its example shows Pet{Name: "Remi"} marshalling as {"Name":"Remi","Nicknames":null} in v1 and {"Name":"Remi","Nicknames":[]} in v2.
What it means
The headline claim for speed is concrete: the Go team says v2 unmarshal is significantly faster, with benchmarks showing up to 10x. Marshal is at parity, sometimes slightly faster and sometimes slightly slower, and the team expects more improvements in Go 1.28. The 10x figure is the team's reported best case from its own benchmarks (the go-json-experiment/jsonbench repository), not a promise for every workload. The post's strongest example is a Kubernetes service in which recursive parsing of OpenAPI specifications inside UnmarshalJSON hurt performance badly, and switching to UnmarshalJSONFrom improved it by orders of magnitude (see kubernetes/kube-openapi#315).
For API authors, the safety changes matter more than speed. Rejecting duplicate names and invalid UTF-8 and matching names case-sensitively close off classes of parser-differential bugs, where two systems read the same JSON differently. Those are the defaults you get only once you opt into v2; v1 on Go 1.27 keeps its old behaviour.
For library maintainers, the worry is mixed codebases. The compatibility design means you can adopt MarshalJSONTo without breaking users who still call v1.
What we don't know yet
- How quickly the ecosystem moves. The team says nobody has to migrate, so adoption depends on whether frameworks and generated code change their defaults.
- Whether the error-message changes in
encoding/jsonwill break real programs; the release notes say only that the text may differ. - Real-world unmarshal gains. The 10x figure is a best case from the team's benchmarks; independent numbers on typical payloads are not in the sources we read.
- The exact list of the 1.27 late changes to options and tags beyond those the release notes mention.
What to watch next
The Go team says to expect additional v2 performance work in Go 1.28. Watch the issues referenced in the release notes for further option and tag changes, and watch whether major libraries start implementing MarshalerTo and UnmarshalerFrom. If you maintain a service, the jsonsplit approach in the migration guide is the safest way to learn what would change before you flip the switch.
Sources
Written by
The official account of the people who run audiotale.net: announcements, conversation starters and the newsroom's drafts. Posts written with AI say so. Every other account here is a real person.
Read next
Hugging Face Model Integration Contracts: tiny CPU fixtures caught the UMT5 attention bug that lived in Transformers main for two months
Hugging Face proposes pinned, shrunk model fixtures that run on cheap CPUs for every pull request, after a UMT5 regression went unnoticed from 30 July to 28 September 2026.
audiotale.net team7 min read
Kimi K3.1 Pro open weights: 2.8T MoE, 1M context, licence terms and Moonshot's own benchmarks
Moonshot has published the weights of its 2.8-trillion-parameter model on Hugging Face under a custom licence; every benchmark so far is the company's own.
audiotale.net team6 min read
Linux 7.4-rc1: record 19,671-commit merge window, Torvalds on AI fixes
Torvalds says AI-found fixes now arriving in the merge window, not late in the rc weeks, are a 'much healthier model'; the tree grew by about 455,000 lines of code.
audiotale.net team6 min read