- What happened: The Go team announced stable encoding/json/v2 and jsontext on 4 November 2026, shipping in Go 1.27.
- Who is affected: Every Go developer who handles JSON. Nothing breaks, because v1 stays supported.
- What to do now: Read the migration guide and trial v2 on one service with options that keep your old behaviour.
- Interview angle: Strict parsing, duplicate keys, nil versus empty, streaming decoders and API evolution.
Go encoding/json/v2 is a new, stricter JSON package that became generally available in Go 1.27. It rejects invalid UTF-8 and duplicate keys, matches names case-sensitively and unmarshals much faster. The old package still works, so you can migrate gradually.
Semantic versus syntactic JSON processing separates two jobs. Syntax means reading and writing valid JSON text as tokens. Semantics means mapping that text to Go types and back. Go encoding/json/v2 handles the semantic job, while jsontext handles syntax.
What changed in Go encoding/json/v2 and who is affected?
On 4 November 2026 the Go Blog published “A new Go API for JSON”. It says encoding/json/v2 and encoding/json/jsontext are stable in Go 1.27. They began as an experiment in Go 1.25. The Go 1.27 release notes also say the existing encoding/json is now backed by the v2 implementation.
Behaviour is preserved for v1 users, although error message text may differ. This is a library change, not a vulnerability. Its impact is mostly design and correctness.
| Behaviour | v1 (encoding/json) | v2 default |
|---|---|---|
| Invalid UTF-8 | Replaced with U+FFFD | Rejected with an error |
| Duplicate object names | Allowed | Rejected with an error |
| Field name matching | Case-insensitive | Case-sensitive |
| Nil slices and maps | Marshal as null |
Marshal as [] and {} |
| Unknown members | Ignored | Ignored (opt in to reject) |
Source: the package documentation and the announcement. The announcement also says time.Duration now returns an error unless you choose a format. The release notes list renames from the experiment, such as inline becoming embed.
How does Go encoding/json/v2 work?
The design splits the problem in two layers. A short flow shows how data moves:
- Your bytes or stream enter a
jsontextdecoder, which checks syntax and yields tokens. - The v2 package maps tokens to Go types, applying options and struct tags.
- Custom types can implement
UnmarshalJSONFromand read directly from the decoder. - Errors return a
SemanticErrorcarrying the byte offset and JSON pointer.
Per the Go team, the old MarshalJSON approach forces allocation and revalidation. The new MarshalJSONTo and UnmarshalJSONFrom methods avoid that and pass options down. The team reports unmarshal speedups of up to 10x in benchmarks and marshal speed roughly at parity.
The snippet below is illustrative. It shows how a stricter decode might look.
import "encoding/json/v2"
type Order struct {
ID string `json:"id"`
Notes string `json:"notes,omitzero"`
}
var o Order
err := json.Unmarshal(data, &o, json.RejectUnknownMembers(true))
// err wraps a SemanticError if the payload has fields you did not declare
Option and tag names come from the package documentation. Check them against your Go version before copying.
What should developers do right now?
- Detect: Run
go versionand list services that parse untrusted JSON. - Read: Study the migration guide linked from the announcement.
- Test: Upgrade to Go 1.27 in a branch and run your suite. Watch for changed error text.
- Trial v2: Switch one service, keeping v1 behaviours you rely on through options.
- Verify: Add tests for duplicate keys, casing, nil slices and large integers.
The release notes also mention a GOEXPERIMENT=nojsonv2 opt-out, which they expect to remove in a future release. Do not build long-term plans on it.
Interview questions this update prepares you for
Why is accepting duplicate keys in a JSON object risky?
The JSON spec, RFC 8259, expects member names to be unique, and parsers disagree when they are not. One may keep the first value and another the last. If a validator and the application parse differently, an attacker can pass the check with one value and trigger behaviour with another. Strict parsers reject duplicates, or report them as errors, so every component sees the same data.
Follow-up they may ask: How would you detect duplicates in a streaming parser without buffering the whole document?
What is the difference between a nil slice and an empty slice, and how does it show up in JSON?
A nil slice has no backing array, while an empty slice has length zero but is non-nil. In v1 a nil slice marshals as null and an empty one as []. That difference surprises API clients that expect an array. In v2, nil slices and maps marshal as [] and {} by default. Knowing the nil-versus-empty distinction is basic Go fluency.
Follow-up they may ask: When would returning null for a collection be the better API contract?
Why can a custom marshaler or unmarshaler cause quadratic or double-parse performance?
A method like UnmarshalJSON receives the raw bytes of one value. The decoder must first scan the whole value to find where it ends, then the method parses it again. Nested custom methods multiply that work. The Go team describes this as a reason for the new streaming-style methods. The general lesson is to avoid APIs that force a re-parse of data already read.
Follow-up they may ask: How does a streaming token decoder reduce memory use compared with decoding a whole value?
Is case-insensitive field matching a feature or a bug?
It is convenient but ambiguous. If a payload holds both ID and id, which one fills the field? It can also let differently cased keys slip past filters that compare names exactly. v2 matches names case-sensitively by default. You can opt back in with an option or a per-field tag. Strict, predictable matching is easier to reason about and to secure.
Follow-up they may ask: What changes for clients that send inconsistent casing, and how would you roll that out safely?
How can a standard library fix design mistakes without breaking existing users?
Go keeps a compatibility promise for version 1, so changing default behaviour in place is off the table. The team added a new package instead, and reimplemented v1 on top of v2 with options that preserve the old behaviour, per the announcement. Both versions interoperate. This pattern, a new versioned API plus a compatibility layer, applies to any public library you maintain.
Follow-up they may ask: What would you version in a REST API if you needed the same kind of break?
What happens when you decode a large integer into an any value in Go?
The v2 package documentation says decoding a number into any yields a float64 unless a custom unmarshaler intervenes. A float64 holds integers exactly only up to 253. Larger IDs lose precision silently. Decode into a typed field, use the string tag, or use a custom unmarshaler to preserve the value.
Follow-up they may ask: How would you design IDs so JavaScript clients never lose precision?
The NexGismo verdict: My recommendation is to adopt v2 on new services and migrate old ones only when you have tests for edge cases. The stricter defaults catch real bugs, but silent behaviour changes in production APIs deserve a staged rollout. This is opinion, not a benchmark result.
- The Go blog announced stable encoding/json/v2 and jsontext on 4 November 2026.
- Go 1.27 keeps v1 supported, and migration is not required.
- Invalid UTF-8 and duplicate object names are errors by default in v2.
- Nil slices and maps marshal as empty arrays and objects in v2.
- The Go team reports unmarshal gains of up to 10x in benchmarks.
Frequently Asked Questions
What is encoding/json/v2 in Go 1.27?
It is a new standard-library package for mapping Go values to JSON, with stricter defaults and first-class options. It ships next to encoding/json/jsontext, which handles raw JSON syntax. The older encoding/json package still works and is now backed by the v2 implementation.
Do I have to migrate from encoding/json to v2?
No. According to the Go 1.27 release notes, migration is not required and the v1 API remains supported. You can adopt v2 gradually, and options let v2 mimic v1 behaviour where your code depends on it.
Is encoding/json/v2 faster than v1?
The Go team says marshaling is roughly at parity and unmarshaling is significantly faster, with improvements of up to 10x in benchmarks. Treat that as a ceiling and benchmark your own payloads before relying on it.
Why does v2 reject duplicate object names?
Duplicate names make JSON ambiguous, because different parsers may keep the first or last value. Rejecting them by default removes that ambiguity. You can still allow them with a jsontext option if legacy data needs it.
Strict parsing is a small change with large effects on API contracts. Try v2 on one service this week and note what breaks. For related reading, see our API design and idempotency guide, writing maintainable code and Node.js security gotchas. Bookmark NexGismo for more interview-ready updates.
