{
  "@context": "https://schema.org",
  "@id": "https://aggr.aymericbeaumet.com/items/hnrss-org/2026-11-04-a-new-go-api-for-json/#webpage",
  "@type": [
    "WebPage",
    "ArchiveComponent"
  ],
  "articleBody": "Michael Pratt, Joe Tsai, Daniel Martí, Johan Brandhorst-Satzkorn, Roger Peppe, Chris Hines, and Damien Neil\\\n 4 November 2026\n\n## Introduction\n\n[JavaScript Object Notation (JSON)](https://datatracker.ietf.org/doc/html/rfc8259) is a simple data interchange format, the most popular data format used on the internet, and widely read and written by Go programs. Go has included support for JSON in the standard library since well before Go 1.0. We first we wrote about [support for JSON in Go](https://go.dev/blog/json) more than 15 years ago!\n\nOver time, packages evolve with the needs of their users, and `encoding/json` is no exception. Last year, we wrote about a [new experimental API for JSON](https://go.dev/blog/jsonv2-exp) designed to bring long-awaited improvements and fixes.\n\nAfter additional feedback and refinement, the new `encoding/json/v2` and `encoding/json/jsontext` packages are stable and generally available in Go 1.27! This post is an updated version of the [article introducing the experiment](https://go.dev/blog/jsonv2-exp), describing the problems with `encoding/json`, and an introduction to the new packages.\n\n## Problems with `encoding/json`\n\nOverall, `encoding/json` has held up well. The idea of marshaling and unmarshaling arbitrary Go types with some default representation in JSON, combined with the ability to customize the representation, has proven to be highly flexible. However, in the years since its introduction, various users have identified numerous shortcomings.\n\n### Behavior flaws\n\nThere are various behavioral flaws in `encoding/json`:\n\n- **Imprecise handling of JSON syntax**: Over the years, JSON has seen increased standardization in order for programs to properly communicate. Generally, decoders have become stricter at rejecting ambiguous inputs, to reduce the chance that two implementations will have different (successful) interpretations of a particular JSON value.\n\n  - `encoding/json` currently accepts invalid UTF-8, whereas the latest Internet Standard (RFC 8259) requires valid UTF-8. The default behavior should report an error in the presence of invalid UTF-8, instead of introducing silent data corruption, which may cause problems downstream.\n\n  - `encoding/json` currently accepts objects with duplicate member names. RFC 8259 does not specify how to handle duplicate names, so an implementation is free to choose an arbitrary value, merge the values, discard the values, or report an error. The presence of a duplicate name results in a JSON value without a universally agreed upon meaning. This could be [exploited by attackers in security applications](https://www.youtube.com/watch?v=avilmOcHKHE&t=1057s) and has been exploited before (as in [CVE-2017-12635](https://nvd.nist.gov/vuln/detail/CVE-2017-12635)). The default behavior should err on the side of safety and reject duplicate names.\n- **Leaking nilness of slices and maps**: JSON is often used to communicate with programs using JSON implementations that do not allow `null` to be unmarshaled into a data type expected to be a JSON array or object. Since `encoding/json` marshals a nil slice or map as a JSON `null`, this may lead to errors when unmarshaling by other implementations. [A survey](https://go.dev/issue/63397#discussioncomment-7201222) indicated that most Go users prefer that nil slices and maps are marshaled as an empty JSON array or object by default.\n\n- **Case-insensitive unmarshaling**: When unmarshaling, a JSON object member name is resolved to a Go struct field name using a case-insensitive match. This is a surprising default, a potential security vulnerability, and a performance limitation.\n\n- **Inconsistent calling of methods**: Due to an implementation detail, `MarshalJSON` methods declared on a pointer receiver are [inconsistently called by `encoding/json`](https://go.dev/issue/22967). While regarded as a bug, this cannot be fixed as too many applications depend on the current behavior.\n\n### API deficiencies\n\nThe API of `encoding/json` can be tricky or restrictive:\n\n- It is difficult to correctly unmarshal from an `io.Reader`. Users often write `json.NewDecoder(r).Decode(v)`, which fails to reject trailing junk at the end of the input.\n\n- Options can be set on the `Encoder` and `Decoder` types, but cannot be used with the `Marshal` and `Unmarshal` functions. Similarly, types implementing the `Marshaler` and `Unmarshaler` interfaces cannot make use of the options and there is no way to plumb options down the call stack. For example, the `Decoder.DisallowUnknownFields` option loses its effect when calling a custom `UnmarshalJSON` method.\n\n- The `Compact`, `Indent`, and `HTMLEscape` functions write to a `bytes.Buffer` instead of something more flexible like a `[]byte` or `io.Writer`. This limits the usability of those functions.\n\n### Performance limitations\n\nSetting aside internal implementation details, the public API commits it to certain performance limitations:\n\n- **MarshalJSON**: The `MarshalJSON` interface method forces the implementation to allocate the returned `[]byte`. Also, the semantics require that `encoding/json` verify that the result is valid JSON and also to reformat it to match the specified indentation.\n\n- **UnmarshalJSON**: The `UnmarshalJSON` interface method requires that a complete JSON value be provided (without any trailing data). This forces `encoding/json` to parse the JSON value to be unmarshaled in its entirety to determine where it ends before it can call `UnmarshalJSON`. Afterwards, the `UnmarshalJSON` method itself must parse the provided JSON value again.\n\n- **Lack of streaming**: Even though the `Encoder` and `Decoder` types operate on an `io.Writer` or `io.Reader`, they buffer the entire JSON value in memory. The `Decoder.Token` method for reading individual tokens is allocation-heavy and there is no corresponding API for writing tokens.\n\nFurthermore, if the implementation of a `MarshalJSON` or `UnmarshalJSON` method recursively calls the `Marshal` or `Unmarshal` function, then the performance becomes quadratic.\n\n## Trying to fix `encoding/json` directly\n\nIntroducing a new, incompatible major version of a package is a heavy consideration. If possible, we should try to fix the existing package.\n\nWhile it is relatively easy to add new features, it is difficult to change existing features. Unfortunately, these problems are inherent consequences of the existing API, making them practically impossible to fix within the [Go 1 compatibility promise](https://go.dev/doc/go1compat).\n\nWe could in principle declare separate names, such as `MarshalV2` or `UnmarshalV2`, but that is tantamount to creating a parallel namespace within the same package. This leads us to `encoding/json/v2` (henceforth called `v2`), where we can make these changes within a separate `v2` namespace in contrast to `encoding/json` (henceforth called `v1`).\n\n## Building on `encoding/json/jsontext`\n\nBefore discussing the `v2` API, we first introduce the [`encoding/json/jsontext`](https://go.dev/pkg/encoding/json/jsontext) package that lays the foundation for improvements to JSON in Go.\n\nJSON serialization in Go can be broken down into two primary components:\n\n- *syntactic functionality* that is concerned with processing JSON based on its grammar, and\n- *semantic functionality* that defines the relationship between JSON values and Go values.\n\nWe use the terms “encode” and “decode” to describe syntactic functionality and the terms “marshal” and “unmarshal” to describe semantic functionality. We aim to provide a clear distinction between functionality that is purely concerned with encoding versus that of marshaling.\n\n![](https://go.dev/blog/jsonv2/api.png)\n\nThis diagram provides an overview of this separation. Purple blocks represent types, while blue blocks represent functions or methods. The direction of the arrows approximately represents the flow of data. The bottom half of the diagram, implemented by the `jsontext` package, contains functionality that is only concerned with syntax, while the upper half, implemented by the `json/v2` package, contains functionality that assigns semantic meaning to syntactic data handled by the bottom half.\n\nThe basic API of `jsontext` is the following:\n\n```go\npackage jsontext\n\ntype Encoder struct { ... }\nfunc NewEncoder(io.Writer, ...Options) *Encoder\nfunc (*Encoder) WriteValue(Value) error\nfunc (*Encoder) WriteToken(Token) error\n\ntype Decoder struct { ... }\nfunc NewDecoder(io.Reader, ...Options) *Decoder\nfunc (*Decoder) ReadValue() (Value, error)\nfunc (*Decoder) ReadToken() (Token, error)\n\ntype Kind byte\ntype Value []byte\nfunc (Value) Kind() Kind\ntype Token struct { ... }\nfunc (Token) Kind() Kind\n```\n\nThe `jsontext` package provides functionality for interacting with JSON at a syntactic level and derives its name from [RFC 8259, section 2](https://datatracker.ietf.org/doc/html/rfc8259#section-2) where the grammar for JSON data is literally called `JSON-text`. Since it only interacts with JSON at a syntactic level, it does not depend on Go reflection.\n\nThe [`Encoder`](https://go.dev/pkg/encoding/json/jsontext#Encoder) and [`Decoder`](https://go.dev/pkg/encoding/json/jsontext#Decoder) provide support for encoding and decoding JSON values and tokens. The constructors [accept variadic options](https://go.dev/pkg/encoding/json/jsontext#Options) that affect the particular behavior of encoding and decoding. Unlike the `Encoder` and `Decoder` types declared in `v1`, the new types in `jsontext` avoid muddling the distinction between syntax and semantics and operate in a truly streaming manner.\n\nA JSON value is a complete unit of data and is represented in Go as [a named `[]byte`](https://go.dev/pkg/encoding/json/jsontext#Value). It is identical to [`RawMessage`](https://go.dev/pkg/encoding/json#RawMessage) in `v1`, which is now a type alias to `jsontext.Value`. A JSON value is syntactically composed of one or more JSON tokens. A JSON token is represented in Go as the [opaque `Token` type](https://go.dev/pkg/encoding/json/jsontext#Token) with constructors and accessor methods. It is analogous to [`Token`](https://go.dev/pkg/encoding/json#Token) in `v1` but is designed represent arbitrary JSON tokens without allocation.\n\nTo resolve the fundamental performance problems with the `MarshalJSON` and `UnmarshalJSON` interface methods, we need an efficient way of encoding and decoding JSON as a streaming sequence of tokens and values. In `v2`, we introduce the `MarshalJSONTo` and `UnmarshalJSONFrom` interface methods that operate on an `Encoder` or `Decoder`, allowing the methods’ implementations to process JSON in a purely streaming manner. Thus, the `json` package need not be responsible for validating or formatting a JSON value returned by `MarshalJSON`, nor would it need to be responsible for determining the boundaries of a JSON value provided to `UnmarshalJSON`. These responsibilities belong to the `Encoder` and `Decoder`.\n\n## Introducing `encoding/json/v2`\n\nBuilding on the `jsontext` package, we now introduce the [`encoding/json/v2`](https://go.dev/pkg/encoding/json/v2) package. It is designed to fix the problems in `v1`, while remaining familiar to users of the `v1` package. Our goal is that usages of `v1` will operate *mostly* the same if directly migrated to `v2`.\n\nHere, we primarily cover the high-level API of `v2`. For examples on how to use it, we encourage readers to study [the examples in the `v2` package](https://go.dev/pkg/encoding/json/v2#pkg-examples), the “[Working with JSON](https://go.dev/doc/tutorial/json)” tutorial, or read [Anton Zhiyanov’s blog covering the topic](https://antonz.org/go-json-v2/).\n\nThe basic API of `v2` is the following:\n\n```go\npackage json\n\nfunc Marshal(in any, opts ...Options) (out []byte, err error)\nfunc MarshalWrite(out io.Writer, in any, opts ...Options) error\nfunc MarshalEncode(out *jsontext.Encoder, in any, opts ...Options) error\n\nfunc Unmarshal(in []byte, out any, opts ...Options) error\nfunc UnmarshalRead(in io.Reader, out any, opts ...Options) error\nfunc UnmarshalDecode(in *jsontext.Decoder, out any, opts ...Options) error\n```\n\nThe [`Marshal`](https://go.dev/pkg/encoding/json/v2#Marshal) and [`Unmarshal`](https://go.dev/pkg/encoding/json/v2#Unmarshal) functions have a signature similar to `v1`, but accept options to configure their behavior. The [`MarshalWrite`](https://go.dev/pkg/encoding/json/v2#MarshalWrite) and [`UnmarshalRead`](https://go.dev/pkg/encoding/json/v2#UnmarshalRead) functions directly operate on an `io.Writer` or `io.Reader`, avoiding the need to temporarily construct an `Encoder` or `Decoder` just to write or read from such types. The [`MarshalEncode`](https://go.dev/pkg/encoding/json/v2#MarshalEncode) and [`UnmarshalDecode`](https://go.dev/pkg/encoding/json/v2#UnmarshalDecode) functions operate on a `jsontext.Encoder` and `jsontext.Decoder` and is actually the underlying implementation of the previously mentioned functions. Unlike `v1`, options are a first-class argument to each of the marshal and unmarshal functions, greatly extending the flexibility and configurability of `v2`. There are [several options available](https://go.dev/pkg/encoding/json/v2#Options) in `v2` which are not covered by this article.\n\n### Type-specified customization\n\nSimilar to `v1`, `v2` allows types to define their own JSON representation by satisfying particular interfaces.\n\n```go\ntype Marshaler interface {\n    MarshalJSON() ([]byte, error)\n}\ntype MarshalerTo interface {\n    MarshalJSONTo(*jsontext.Encoder) error\n}\n\ntype Unmarshaler interface {\n    UnmarshalJSON([]byte) error\n}\ntype UnmarshalerFrom interface {\n    UnmarshalJSONFrom(*jsontext.Decoder) error\n}\n```\n\nThe [`Marshaler`](https://go.dev/pkg/encoding/json/v2#Marshaler) and [`Unmarshaler`](https://go.dev/pkg/encoding/json/v2#Unmarshaler) interfaces are identical to those in `v1`. The new [`MarshalerTo`](https://go.dev/pkg/encoding/json/v2#MarshalerTo) and [`UnmarshalerFrom`](https://go.dev/pkg/encoding/json/v2#UnmarshalerFrom) interfaces allow a type to represent itself as JSON using a `jsontext.Encoder` or `jsontext.Decoder`. This allows options to be forwarded down the call stack, since options can be retrieved via the `Options` accessor method on the `Encoder` or `Decoder`.\n\nSee [the `OrderedObject` example](https://go.dev/pkg/encoding/json/v2#example-package-OrderedObject) for how to implement a custom type that maintains the ordering of JSON object members.\n\n### Caller-specified customization\n\nIn `v2`, the caller of `Marshal` and `Unmarshal` can also specify a custom JSON representation for any arbitrary type, where caller-specified functions take precedence over type-defined methods or the default representation for a particular type.\n\n```go\nfunc WithMarshalers(*Marshalers) Options\n\ntype Marshalers struct { ... }\nfunc MarshalFunc[T any](fn func(T) ([]byte, error)) *Marshalers\nfunc MarshalToFunc[T any](fn func(*jsontext.Encoder, T) error) *Marshalers\n\nfunc WithUnmarshalers(*Unmarshalers) Options\n\ntype Unmarshalers struct { ... }\nfunc UnmarshalFunc[T any](fn func([]byte, T) error) *Unmarshalers\nfunc UnmarshalFromFunc[T any](fn func(*jsontext.Decoder, T) error) *Unmarshalers\n```\n\n[`MarshalFunc`](https://go.dev/pkg/encoding/json/v2#MarshalFunc) and [`MarshalToFunc`](https://go.dev/pkg/encoding/json/v2#MarshalToFunc) construct a custom marshaler that can be passed to a `Marshal` call using `WithMarshalers` to override the marshaling of particular types. Similarly, [`UnmarshalFunc`](https://go.dev/pkg/encoding/json/v2#UnmarshalFunc) and [`UnmarshalFromFunc`](https://go.dev/pkg/encoding/json/v2#UnmarshalFromFunc) support similar customization for `Unmarshal`.\n\n[The `ProtoJSON` example](https://go.dev/pkg/encoding/json/v2#example-package-ProtoJSON) demonstrates how this feature allows serialization of all [`proto.Message`](https://go.dev/pkg/google.golang.org/protobuf/proto#Message) types to be handled by the [`protojson`](https://go.dev/pkg/google.golang.org/protobuf/encoding/protojson) package.\n\n### Behavior differences\n\nWhile `v2` aims to behave *mostly* the same as `v1`, its behavior has changed [in some ways](https://go.dev/pkg/encoding/json#hdr-Migrating_to_v2) to address problems in `v1`, most notably:\n\n- `v2` reports an error in the presence of invalid UTF-8.\n\n- `v2` reports an error if a JSON object contains a duplicate name.\n\n- `v2` marshals a nil Go slice or Go map as an empty JSON array or JSON object, respectively.\n\n- `v2` unmarshals a JSON object into a Go struct using a case-sensitive match from the JSON member name to the Go field name.\n\n- `v2` redefines the `omitempty` tag option to omit a field if it would have encoded as an “empty” JSON value (which are `null`, `\"\"`, `[]`, and `{}`).\n\n- `v2` reports an error when trying to serialize a `time.Duration`, which currently has [no default representation](https://go.dev/issue/71631), but provides options to let the caller decide.\n\nFor most behavior changes, there is a struct tag option or caller-specified option that can configure the behavior to operate under `v1` or `v2` semantics, or even other caller-determined behavior. See “[Migrating to v2](https://go.dev/pkg/encoding/json#hdr-Migrating_to_v2)” for more information.\n\n### Performance optimizations\n\nThe `Marshal` performance of `v2` is roughly at parity with `v1`. Sometimes it is slightly faster, but other times it is slightly slower. The `Unmarshal` performance of `v2` is significantly faster than `v1`, with benchmarks demonstrating improvements of up to 10x.\n\nIn order to obtain greater performance gains, existing implementations of [`Marshaler`](https://go.dev/pkg/encoding/json/v2#Marshaler) and [`Unmarshaler`](https://go.dev/pkg/encoding/json/v2#Unmarshaler) should migrate to also implement [`MarshalerTo`](https://go.dev/pkg/encoding/json/v2#MarshalerTo) and [`UnmarshalerFrom`](https://go.dev/pkg/encoding/json/v2#UnmarshalerFrom), so that they can benefit from processing JSON in a purely streaming manner. For example, recursive parsing of OpenAPI specifications in `UnmarshalJSON` methods significantly hurt performance in a particular service of Kubernetes (see [kubernetes/kube-openapi#315](https://github.com/kubernetes/kube-openapi/issues/315)), while switching to `UnmarshalJSONFrom` improved performance by orders of magnitude.\n\nFor more benchmark details, see the [`go-json-experiment/jsonbench`](https://github.com/go-json-experiment/jsonbench) repository.\n\nOn top of structural improvements, we’re continuing to invest in performance optimizations in the implementation for [cases](https://go.dev/issue/81315) where the `v2` implementation is slower than `v1`. You can expect additional improvements in Go 1.28.\n\n## Compatibility\n\nThe [Go 1 compatibility promise](https://go.dev/doc/go1compat) is a core guarantee from the Go project that allows developers to build applications that will stand the test of time and continue working on future versions of Go.\n\nThe original `encoding/json` and new `encoding/json/v2` packages are of course both subject to the Go 1 compatibility promise. Applications using `v1` will continue to be supported indefinitely and need not ever migrate to `v2`.\n\nThat said, when designing the `v2` packages we wanted to do better than keeping `v1` supported. We wanted to ensure that `v1` and `v2` are *compatible with one another* so that packages using different versions can work together. Doing so allows a smooth ecosystem migration rather than forcing users to use one version or another due to the choices of their dependencies.\n\nFor instance, suppose your package uses `v2` to marshal a struct containing a field with a type defined in another package. That package may use the original `v1` [`MarshalJSON`](https://go.dev/pkg/encoding/json/v2#Marshaler) method to define its marshaling behavior. No problem, `v2` supports [`MarshalJSON`](https://go.dev/pkg/encoding/json/v2#Marshaler) methods, so marshaling will continue working as expected.\n\nVice versa, suppose your package uses `v1` to marshal the same struct. The other package may use the `v2` [`MarshalJSONTo`](https://go.dev/pkg/encoding/json/v2#MarshalerTo) method for better performance. No problem here either, as the `v1` implementation supports `v2`’s [`MarshalJSONTo`](https://go.dev/pkg/encoding/json/v2#MarshalerTo) method under the hood.\n\nWe’ve achieved this by designing the new packages such that, under the hood, `v1` is implemented in terms of `v2`. As mentioned above, `v2` supports a variety of [`Options`](https://go.dev/pkg/encoding/json/v2#Options). Each behavior difference is controlled by one of the these options. Thus, `v1` can be implemented in terms of `v2` by passing the full set of `v1` compatibility options.\n\nWith this approach, backwards-compatible additions such as new methods (e.g., [`MarshalJSONTo`](https://go.dev/pkg/encoding/json/v2#MarshalerTo)) or new struct tags (e.g., [`embed`](https://go.dev/pkg/encoding/json/v2#hdr-JSON_Representation_of_Go_structs)) are automatically supported to the benefit of existing `v1` users.\n\n## Migration\n\nImplementing `v1` in terms of `v2` options has the additional benefit of allowing gradual migration of callsites.\n\n`encoding/json` [documents](https://go.dev/pkg/encoding/json#hdr-Migrating_to_v2) the full set of behavior differences between `v1` and `v2`, and which option controls that behaviors.\n\nMany applications can migrate directly to `v2` with no visible behavior changes, but applications that depend on `v1` behavior can use the options to change only the behaviors they depend on.\n\nFor example, we can select all `v2` behavior except for allowing duplicate names:\n\n```go\njson.Marshal(..., json.DefaultOptionsV2(), jsontext.AllowDuplicateName(true))\n```\n\nAlternatively, we can select all `v1` behavior except for disallowing duplicate names:\n\n```go\njson.Marshal(..., jsonv1.DefaultOptionsV1(), jsontext.AllowDuplicateName(false))\n```\n\nThe [`encoding/json/v2` Migration Guide](https://go.dev/doc/jsonv2-migration) goes into more detail, covering a variety of scenarios for approaching migration of an application from `v1` to `v2`.\n\n## More information\n\nFor additional references on working with the new JSON packages, see:\n\n- The “[Working with JSON](https://go.dev/doc/tutorial/json)” tutorial,\n- The [`encoding/json/v2`](https://go.dev/pkg/encoding/json/v2) and [`encoding/json/jsontext`](https://go.dev/pkg/encoding/json/jsontext) package documentation,\n- The [`encoding/json`](https://go.dev/pkg/encoding/json#hdr-Migrating_to_v2) package’s `v2` comparison documentation,\n- and the [`encoding/json/v2` Migration Guide](https://go.dev/doc/jsonv2-migration).\n\n## A long time coming\n\nThe planning for a new major version of `encoding/json` spanned years. In late 2020, spurred on by the inability to fix issues in the current package, Daniel Martí (one of the maintainers of `encoding/json`) first drafted his thoughts on [what a hypothetical `v2` package should look like](https://docs.google.com/document/d/1WQGoM44HLinH4NGBEv5drGlw5_RNW-GP7DdGEpm7Y3o). Separately, after previous work on the [Go API for Protocol Buffers](https://go.dev/blog/protobuf-apiv2), Joe Tsai was disappointed that [the `protojson` package](https://go.dev/pkg/google.golang.org/protobuf/encoding/protojson) needed to use a custom JSON implementation because `encoding/json` was neither capable of adhering to the stricter JSON standard that the Protocol Buffer specification required, nor of efficiently serializing JSON in a streaming manner.\n\nBelieving a brighter future for JSON was both beneficial and achievable, Daniel and Joe joined forces to brainstorm on `v2` and [started to build a prototype](https://github.com/go-json-experiment/json) (with the initial code being a polished version of the JSON serialization logic from the Go protobuf module). Over time, a few others (Roger Peppe, Chris Hines, Johan Brandhorst-Satzkorn, Damien Neil, Austin Clements, and Michael Pratt) joined the effort by providing design review, code review, and regression testing. Many of the early discussions are publicly available in our [recorded meetings](https://www.youtube.com/playlist?list=PLZgrQPcV8W8EChkaAvv-3NUu6PYmnGG3b) and [meeting notes](https://docs.google.com/document/d/1rovrOTd-wTawGMPPlPuKhwXaYBg9VszTXR9AQQL5LfI). Later discussions are available in our [`encoding/json/v2` working group meeting notes](https://go.dev/issue/76406).\n\nThis work has been public since the beginning, and we increasingly involved the wider Go community, first with a [GopherCon talk](https://www.youtube.com/watch?v=avilmOcHKHE) and [discussion posted in late 2023](https://go.dev/issue/63397), [formal proposal posted in early 2025](https://go.dev/issue/71497), and finally [adopting `encoding/json/v2` as a Go experiment in Go 1.25](https://go.dev/issue/71845) for wider-scale testing by all Go users.\n\nThe `v2` effort took about 6 years to reach this point, incorporating feedback from many contributors and also gaining valuable empirical experience from use in production settings.\n\nIt’s worth noting that it’s largely been developed and promoted by people not employed by Google, demonstrating that the Go project is a collaborative endeavor with a thriving global community dedicated to improving the Go ecosystem.\n\nThank you to everyone that contributed!\n",
  "authors": [
    "olexsmir"
  ],
  "captured_at": "2026-11-04T21:49:17.908350494+00:00",
  "category": "news",
  "content_html": "<p>Michael Pratt, Joe Tsai, Daniel Martí, Johan Brandhorst-Satzkorn, Roger Peppe, Chris Hines, and Damien Neil<br />\n4 November 2026</p>\n<h2>Introduction</h2>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://datatracker.ietf.org/doc/html/rfc8259\">JavaScript Object Notation (JSON)</a> is a simple data interchange format, the most popular data format used on the internet, and widely read and written by Go programs. Go has included support for JSON in the standard library since well before Go 1.0. We first we wrote about <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/blog/json\">support for JSON in Go</a> more than 15 years ago!</p>\n<p>Over time, packages evolve with the needs of their users, and <code>encoding/json</code> is no exception. Last year, we wrote about a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/blog/jsonv2-exp\">new experimental API for JSON</a> designed to bring long-awaited improvements and fixes.</p>\n<p>After additional feedback and refinement, the new <code>encoding/json/v2</code> and <code>encoding/json/jsontext</code> packages are stable and generally available in Go 1.27! This post is an updated version of the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/blog/jsonv2-exp\">article introducing the experiment</a>, describing the problems with <code>encoding/json</code>, and an introduction to the new packages.</p>\n<h2>Problems with <code>encoding/json</code></h2>\n<p>Overall, <code>encoding/json</code> has held up well. The idea of marshaling and unmarshaling arbitrary Go types with some default representation in JSON, combined with the ability to customize the representation, has proven to be highly flexible. However, in the years since its introduction, various users have identified numerous shortcomings.</p>\n<h3>Behavior flaws</h3>\n<p>There are various behavioral flaws in <code>encoding/json</code>:</p>\n<ul>\n<li>\n<p><strong>Imprecise handling of JSON syntax</strong>: Over the years, JSON has seen increased standardization in order for programs to properly communicate. Generally, decoders have become stricter at rejecting ambiguous inputs, to reduce the chance that two implementations will have different (successful) interpretations of a particular JSON value.</p>\n<ul>\n<li>\n<p><code>encoding/json</code> currently accepts invalid UTF-8, whereas the latest Internet Standard (RFC 8259) requires valid UTF-8. The default behavior should report an error in the presence of invalid UTF-8, instead of introducing silent data corruption, which may cause problems downstream.</p>\n</li>\n<li>\n<p><code>encoding/json</code> currently accepts objects with duplicate member names. RFC 8259 does not specify how to handle duplicate names, so an implementation is free to choose an arbitrary value, merge the values, discard the values, or report an error. The presence of a duplicate name results in a JSON value without a universally agreed upon meaning. This could be <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.youtube.com/watch?v=avilmOcHKHE&amp;t=1057s\">exploited by attackers in security applications</a> and has been exploited before (as in <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://nvd.nist.gov/vuln/detail/CVE-2017-12635\">CVE-2017-12635</a>). The default behavior should err on the side of safety and reject duplicate names.</p>\n</li>\n</ul>\n</li>\n<li>\n<p><strong>Leaking nilness of slices and maps</strong>: JSON is often used to communicate with programs using JSON implementations that do not allow <code>null</code> to be unmarshaled into a data type expected to be a JSON array or object. Since <code>encoding/json</code> marshals a nil slice or map as a JSON <code>null</code>, this may lead to errors when unmarshaling by other implementations. <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/issue/63397#discussioncomment-7201222\">A survey</a> indicated that most Go users prefer that nil slices and maps are marshaled as an empty JSON array or object by default.</p>\n</li>\n<li>\n<p><strong>Case-insensitive unmarshaling</strong>: When unmarshaling, a JSON object member name is resolved to a Go struct field name using a case-insensitive match. This is a surprising default, a potential security vulnerability, and a performance limitation.</p>\n</li>\n<li>\n<p><strong>Inconsistent calling of methods</strong>: Due to an implementation detail, <code>MarshalJSON</code> methods declared on a pointer receiver are <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/issue/22967\">inconsistently called by <code>encoding/json</code></a>. While regarded as a bug, this cannot be fixed as too many applications depend on the current behavior.</p>\n</li>\n</ul>\n<h3>API deficiencies</h3>\n<p>The API of <code>encoding/json</code> can be tricky or restrictive:</p>\n<ul>\n<li>\n<p>It is difficult to correctly unmarshal from an <code>io.Reader</code>. Users often write <code>json.NewDecoder(r).Decode(v)</code>, which fails to reject trailing junk at the end of the input.</p>\n</li>\n<li>\n<p>Options can be set on the <code>Encoder</code> and <code>Decoder</code> types, but cannot be used with the <code>Marshal</code> and <code>Unmarshal</code> functions. Similarly, types implementing the <code>Marshaler</code> and <code>Unmarshaler</code> interfaces cannot make use of the options and there is no way to plumb options down the call stack. For example, the <code>Decoder.DisallowUnknownFields</code> option loses its effect when calling a custom <code>UnmarshalJSON</code> method.</p>\n</li>\n<li>\n<p>The <code>Compact</code>, <code>Indent</code>, and <code>HTMLEscape</code> functions write to a <code>bytes.Buffer</code> instead of something more flexible like a <code>[]byte</code> or <code>io.Writer</code>. This limits the usability of those functions.</p>\n</li>\n</ul>\n<h3>Performance limitations</h3>\n<p>Setting aside internal implementation details, the public API commits it to certain performance limitations:</p>\n<ul>\n<li>\n<p><strong>MarshalJSON</strong>: The <code>MarshalJSON</code> interface method forces the implementation to allocate the returned <code>[]byte</code>. Also, the semantics require that <code>encoding/json</code> verify that the result is valid JSON and also to reformat it to match the specified indentation.</p>\n</li>\n<li>\n<p><strong>UnmarshalJSON</strong>: The <code>UnmarshalJSON</code> interface method requires that a complete JSON value be provided (without any trailing data). This forces <code>encoding/json</code> to parse the JSON value to be unmarshaled in its entirety to determine where it ends before it can call <code>UnmarshalJSON</code>. Afterwards, the <code>UnmarshalJSON</code> method itself must parse the provided JSON value again.</p>\n</li>\n<li>\n<p><strong>Lack of streaming</strong>: Even though the <code>Encoder</code> and <code>Decoder</code> types operate on an <code>io.Writer</code> or <code>io.Reader</code>, they buffer the entire JSON value in memory. The <code>Decoder.Token</code> method for reading individual tokens is allocation-heavy and there is no corresponding API for writing tokens.</p>\n</li>\n</ul>\n<p>Furthermore, if the implementation of a <code>MarshalJSON</code> or <code>UnmarshalJSON</code> method recursively calls the <code>Marshal</code> or <code>Unmarshal</code> function, then the performance becomes quadratic.</p>\n<h2>Trying to fix <code>encoding/json</code> directly</h2>\n<p>Introducing a new, incompatible major version of a package is a heavy consideration. If possible, we should try to fix the existing package.</p>\n<p>While it is relatively easy to add new features, it is difficult to change existing features. Unfortunately, these problems are inherent consequences of the existing API, making them practically impossible to fix within the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/doc/go1compat\">Go 1 compatibility promise</a>.</p>\n<p>We could in principle declare separate names, such as <code>MarshalV2</code> or <code>UnmarshalV2</code>, but that is tantamount to creating a parallel namespace within the same package. This leads us to <code>encoding/json/v2</code> (henceforth called <code>v2</code>), where we can make these changes within a separate <code>v2</code> namespace in contrast to <code>encoding/json</code> (henceforth called <code>v1</code>).</p>\n<h2>Building on <code>encoding/json/jsontext</code></h2>\n<p>Before discussing the <code>v2</code> API, we first introduce the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/jsontext\"><code>encoding/json/jsontext</code></a> package that lays the foundation for improvements to JSON in Go.</p>\n<p>JSON serialization in Go can be broken down into two primary components:</p>\n<ul>\n<li><em>syntactic functionality</em> that is concerned with processing JSON based on its grammar, and</li>\n<li><em>semantic functionality</em> that defines the relationship between JSON values and Go values.</li>\n</ul>\n<p>We use the terms “encode” and “decode” to describe syntactic functionality and the terms “marshal” and “unmarshal” to describe semantic functionality. We aim to provide a clear distinction between functionality that is purely concerned with encoding versus that of marshaling.</p>\n<p><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://go.dev/blog/jsonv2/api.png\" alt=\"\" loading=\"eager\" decoding=\"async\" fetchpriority=\"high\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></p>\n<p>This diagram provides an overview of this separation. Purple blocks represent types, while blue blocks represent functions or methods. The direction of the arrows approximately represents the flow of data. The bottom half of the diagram, implemented by the <code>jsontext</code> package, contains functionality that is only concerned with syntax, while the upper half, implemented by the <code>json/v2</code> package, contains functionality that assigns semantic meaning to syntactic data handled by the bottom half.</p>\n<p>The basic API of <code>jsontext</code> is the following:</p>\n<pre><code class=\"language-go\"><span class=\"code-snippet\" data-language=\"Go\"><span class=\"syntax-source syntax-go\"><span class=\"syntax-keyword syntax-control syntax-go\">package</span> jsontext\n\n<span class=\"syntax-meta syntax-struct syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">type</span> <span class=\"syntax-entity syntax-name syntax-struct syntax-go\">Encoder</span> <span class=\"syntax-storage syntax-type syntax-go\">struct</span> <span class=\"syntax-meta syntax-block syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-block syntax-begin syntax-go\">{</span> ... </span><span class=\"syntax-meta syntax-block syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-block syntax-end syntax-go\">}</span></span></span>\n<span class=\"syntax-meta syntax-function syntax-declaration syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">func</span> <span class=\"syntax-entity syntax-name syntax-function syntax-go\">NewEncoder</span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\">io.Writer<span class=\"syntax-punctuation syntax-separator syntax-go\">,</span> ...Options</span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span></span><span class=\"syntax-meta syntax-function syntax-go\"> </span><span class=\"syntax-meta syntax-function syntax-return-type syntax-go\"><span class=\"syntax-keyword syntax-operator syntax-go\">*</span>Encoder</span>\n<span class=\"syntax-meta syntax-function syntax-declaration syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">func</span> <span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span><span class=\"syntax-keyword syntax-operator syntax-go\">*</span>Encoder<span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span> <span class=\"syntax-entity syntax-name syntax-function syntax-go\">WriteValue</span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\">Value</span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span></span><span class=\"syntax-meta syntax-function syntax-go\"> </span><span class=\"syntax-meta syntax-function syntax-return-type syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">error</span></span>\n<span class=\"syntax-meta syntax-function syntax-declaration syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">func</span> <span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span><span class=\"syntax-keyword syntax-operator syntax-go\">*</span>Encoder<span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span> <span class=\"syntax-entity syntax-name syntax-function syntax-go\">WriteToken</span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\">Token</span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span></span><span class=\"syntax-meta syntax-function syntax-go\"> </span><span class=\"syntax-meta syntax-function syntax-return-type syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">error</span></span>\n\n<span class=\"syntax-meta syntax-struct syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">type</span> <span class=\"syntax-entity syntax-name syntax-struct syntax-go\">Decoder</span> <span class=\"syntax-storage syntax-type syntax-go\">struct</span> <span class=\"syntax-meta syntax-block syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-block syntax-begin syntax-go\">{</span> ... </span><span class=\"syntax-meta syntax-block syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-block syntax-end syntax-go\">}</span></span></span>\n<span class=\"syntax-meta syntax-function syntax-declaration syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">func</span> <span class=\"syntax-entity syntax-name syntax-function syntax-go\">NewDecoder</span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\">io.Reader<span class=\"syntax-punctuation syntax-separator syntax-go\">,</span> ...Options</span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span></span><span class=\"syntax-meta syntax-function syntax-go\"> </span><span class=\"syntax-meta syntax-function syntax-return-type syntax-go\"><span class=\"syntax-keyword syntax-operator syntax-go\">*</span>Decoder</span>\n<span class=\"syntax-meta syntax-function syntax-declaration syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">func</span> <span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span><span class=\"syntax-keyword syntax-operator syntax-go\">*</span>Decoder<span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span> <span class=\"syntax-entity syntax-name syntax-function syntax-go\">ReadValue</span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span></span><span class=\"syntax-meta syntax-function syntax-go\"> </span><span class=\"syntax-meta syntax-function syntax-return-type syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span></span></span><span class=\"syntax-meta syntax-function syntax-return-type syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\">Value<span class=\"syntax-punctuation syntax-separator syntax-go\">,</span> <span class=\"syntax-storage syntax-type syntax-go\">error</span><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span></span>\n<span class=\"syntax-meta syntax-function syntax-declaration syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">func</span> <span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span><span class=\"syntax-keyword syntax-operator syntax-go\">*</span>Decoder<span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span> <span class=\"syntax-entity syntax-name syntax-function syntax-go\">ReadToken</span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span></span><span class=\"syntax-meta syntax-function syntax-go\"> </span><span class=\"syntax-meta syntax-function syntax-return-type syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span></span></span><span class=\"syntax-meta syntax-function syntax-return-type syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\">Token<span class=\"syntax-punctuation syntax-separator syntax-go\">,</span> <span class=\"syntax-storage syntax-type syntax-go\">error</span><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span></span>\n\n<span class=\"syntax-meta syntax-type syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">type</span> <span class=\"syntax-entity syntax-name syntax-type syntax-go\">Kind</span> <span class=\"syntax-storage syntax-type syntax-go\">byte</span></span>\n<span class=\"syntax-meta syntax-type syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">type</span> <span class=\"syntax-entity syntax-name syntax-type syntax-go\">Value</span> <span class=\"syntax-meta syntax-brackets syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-brackets syntax-begin syntax-go\">[</span><span class=\"syntax-punctuation syntax-definition syntax-brackets syntax-end syntax-go\">]</span></span><span class=\"syntax-storage syntax-type syntax-go\">byte</span></span>\n<span class=\"syntax-meta syntax-function syntax-declaration syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">func</span> <span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span>Value<span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span> <span class=\"syntax-entity syntax-name syntax-function syntax-go\">Kind</span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span></span><span class=\"syntax-meta syntax-function syntax-go\"> </span><span class=\"syntax-meta syntax-function syntax-return-type syntax-go\">Kind</span>\n<span class=\"syntax-meta syntax-struct syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">type</span> <span class=\"syntax-entity syntax-name syntax-struct syntax-go\">Token</span> <span class=\"syntax-storage syntax-type syntax-go\">struct</span> <span class=\"syntax-meta syntax-block syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-block syntax-begin syntax-go\">{</span> ... </span><span class=\"syntax-meta syntax-block syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-block syntax-end syntax-go\">}</span></span></span>\n<span class=\"syntax-meta syntax-function syntax-declaration syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">func</span> <span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span>Token<span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span> <span class=\"syntax-entity syntax-name syntax-function syntax-go\">Kind</span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span></span><span class=\"syntax-meta syntax-function syntax-go\"> </span><span class=\"syntax-meta syntax-function syntax-return-type syntax-go\">Kind</span>\n</span></span></code></pre>\n<p>The <code>jsontext</code> package provides functionality for interacting with JSON at a syntactic level and derives its name from <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://datatracker.ietf.org/doc/html/rfc8259#section-2\">RFC 8259, section 2</a> where the grammar for JSON data is literally called <code>JSON-text</code>. Since it only interacts with JSON at a syntactic level, it does not depend on Go reflection.</p>\n<p>The <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/jsontext#Encoder\"><code>Encoder</code></a> and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/jsontext#Decoder\"><code>Decoder</code></a> provide support for encoding and decoding JSON values and tokens. The constructors <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/jsontext#Options\">accept variadic options</a> that affect the particular behavior of encoding and decoding. Unlike the <code>Encoder</code> and <code>Decoder</code> types declared in <code>v1</code>, the new types in <code>jsontext</code> avoid muddling the distinction between syntax and semantics and operate in a truly streaming manner.</p>\n<p>A JSON value is a complete unit of data and is represented in Go as <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/jsontext#Value\">a named <code>[]byte</code></a>. It is identical to <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json#RawMessage\"><code>RawMessage</code></a> in <code>v1</code>, which is now a type alias to <code>jsontext.Value</code>. A JSON value is syntactically composed of one or more JSON tokens. A JSON token is represented in Go as the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/jsontext#Token\">opaque <code>Token</code> type</a> with constructors and accessor methods. It is analogous to <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json#Token\"><code>Token</code></a> in <code>v1</code> but is designed represent arbitrary JSON tokens without allocation.</p>\n<p>To resolve the fundamental performance problems with the <code>MarshalJSON</code> and <code>UnmarshalJSON</code> interface methods, we need an efficient way of encoding and decoding JSON as a streaming sequence of tokens and values. In <code>v2</code>, we introduce the <code>MarshalJSONTo</code> and <code>UnmarshalJSONFrom</code> interface methods that operate on an <code>Encoder</code> or <code>Decoder</code>, allowing the methods’ implementations to process JSON in a purely streaming manner. Thus, the <code>json</code> package need not be responsible for validating or formatting a JSON value returned by <code>MarshalJSON</code>, nor would it need to be responsible for determining the boundaries of a JSON value provided to <code>UnmarshalJSON</code>. These responsibilities belong to the <code>Encoder</code> and <code>Decoder</code>.</p>\n<h2>Introducing <code>encoding/json/v2</code></h2>\n<p>Building on the <code>jsontext</code> package, we now introduce the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/v2\"><code>encoding/json/v2</code></a> package. It is designed to fix the problems in <code>v1</code>, while remaining familiar to users of the <code>v1</code> package. Our goal is that usages of <code>v1</code> will operate <em>mostly</em> the same if directly migrated to <code>v2</code>.</p>\n<p>Here, we primarily cover the high-level API of <code>v2</code>. For examples on how to use it, we encourage readers to study <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/v2#pkg-examples\">the examples in the <code>v2</code> package</a>, the “<a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/doc/tutorial/json\">Working with JSON</a>” tutorial, or read <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://antonz.org/go-json-v2/\">Anton Zhiyanov’s blog covering the topic</a>.</p>\n<p>The basic API of <code>v2</code> is the following:</p>\n<pre><code class=\"language-go\"><span class=\"code-snippet\" data-language=\"Go\"><span class=\"syntax-source syntax-go\"><span class=\"syntax-keyword syntax-control syntax-go\">package</span> json\n\n<span class=\"syntax-meta syntax-function syntax-declaration syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">func</span> <span class=\"syntax-entity syntax-name syntax-function syntax-go\">Marshal</span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-variable syntax-parameter syntax-go\">in</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"> any<span class=\"syntax-punctuation syntax-separator syntax-go\">,</span> </span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-variable syntax-parameter syntax-go\">opts</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"> ...Options</span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span></span><span class=\"syntax-meta syntax-function syntax-go\"> </span><span class=\"syntax-meta syntax-function syntax-return-type syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span></span></span><span class=\"syntax-meta syntax-function syntax-return-type syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\">out <span class=\"syntax-meta syntax-brackets syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-brackets syntax-begin syntax-go\">[</span><span class=\"syntax-punctuation syntax-definition syntax-brackets syntax-end syntax-go\">]</span></span><span class=\"syntax-storage syntax-type syntax-go\">byte</span><span class=\"syntax-punctuation syntax-separator syntax-go\">,</span> err <span class=\"syntax-storage syntax-type syntax-go\">error</span><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span></span>\n<span class=\"syntax-meta syntax-function syntax-declaration syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">func</span> <span class=\"syntax-entity syntax-name syntax-function syntax-go\">MarshalWrite</span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-variable syntax-parameter syntax-go\">out</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"> io.Writer<span class=\"syntax-punctuation syntax-separator syntax-go\">,</span> </span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-variable syntax-parameter syntax-go\">in</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"> any<span class=\"syntax-punctuation syntax-separator syntax-go\">,</span> </span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-variable syntax-parameter syntax-go\">opts</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"> ...Options</span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span></span><span class=\"syntax-meta syntax-function syntax-go\"> </span><span class=\"syntax-meta syntax-function syntax-return-type syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">error</span></span>\n<span class=\"syntax-meta syntax-function syntax-declaration syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">func</span> <span class=\"syntax-entity syntax-name syntax-function syntax-go\">MarshalEncode</span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-variable syntax-parameter syntax-go\">out</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"> <span class=\"syntax-keyword syntax-operator syntax-go\">*</span>jsontext.Encoder<span class=\"syntax-punctuation syntax-separator syntax-go\">,</span> </span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-variable syntax-parameter syntax-go\">in</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"> any<span class=\"syntax-punctuation syntax-separator syntax-go\">,</span> </span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-variable syntax-parameter syntax-go\">opts</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"> ...Options</span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span></span><span class=\"syntax-meta syntax-function syntax-go\"> </span><span class=\"syntax-meta syntax-function syntax-return-type syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">error</span></span>\n\n<span class=\"syntax-meta syntax-function syntax-declaration syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">func</span> <span class=\"syntax-entity syntax-name syntax-function syntax-go\">Unmarshal</span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-variable syntax-parameter syntax-go\">in</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"> <span class=\"syntax-meta syntax-brackets syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-brackets syntax-begin syntax-go\">[</span><span class=\"syntax-punctuation syntax-definition syntax-brackets syntax-end syntax-go\">]</span></span><span class=\"syntax-storage syntax-type syntax-go\">byte</span><span class=\"syntax-punctuation syntax-separator syntax-go\">,</span> </span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-variable syntax-parameter syntax-go\">out</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"> any<span class=\"syntax-punctuation syntax-separator syntax-go\">,</span> </span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-variable syntax-parameter syntax-go\">opts</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"> ...Options</span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span></span><span class=\"syntax-meta syntax-function syntax-go\"> </span><span class=\"syntax-meta syntax-function syntax-return-type syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">error</span></span>\n<span class=\"syntax-meta syntax-function syntax-declaration syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">func</span> <span class=\"syntax-entity syntax-name syntax-function syntax-go\">UnmarshalRead</span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-variable syntax-parameter syntax-go\">in</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"> io.Reader<span class=\"syntax-punctuation syntax-separator syntax-go\">,</span> </span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-variable syntax-parameter syntax-go\">out</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"> any<span class=\"syntax-punctuation syntax-separator syntax-go\">,</span> </span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-variable syntax-parameter syntax-go\">opts</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"> ...Options</span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span></span><span class=\"syntax-meta syntax-function syntax-go\"> </span><span class=\"syntax-meta syntax-function syntax-return-type syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">error</span></span>\n<span class=\"syntax-meta syntax-function syntax-declaration syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">func</span> <span class=\"syntax-entity syntax-name syntax-function syntax-go\">UnmarshalDecode</span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-variable syntax-parameter syntax-go\">in</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"> <span class=\"syntax-keyword syntax-operator syntax-go\">*</span>jsontext.Decoder<span class=\"syntax-punctuation syntax-separator syntax-go\">,</span> </span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-variable syntax-parameter syntax-go\">out</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"> any<span class=\"syntax-punctuation syntax-separator syntax-go\">,</span> </span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-variable syntax-parameter syntax-go\">opts</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"> ...Options</span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span></span><span class=\"syntax-meta syntax-function syntax-go\"> </span><span class=\"syntax-meta syntax-function syntax-return-type syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">error</span></span>\n</span></span></code></pre>\n<p>The <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/v2#Marshal\"><code>Marshal</code></a> and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/v2#Unmarshal\"><code>Unmarshal</code></a> functions have a signature similar to <code>v1</code>, but accept options to configure their behavior. The <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/v2#MarshalWrite\"><code>MarshalWrite</code></a> and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/v2#UnmarshalRead\"><code>UnmarshalRead</code></a> functions directly operate on an <code>io.Writer</code> or <code>io.Reader</code>, avoiding the need to temporarily construct an <code>Encoder</code> or <code>Decoder</code> just to write or read from such types. The <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/v2#MarshalEncode\"><code>MarshalEncode</code></a> and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/v2#UnmarshalDecode\"><code>UnmarshalDecode</code></a> functions operate on a <code>jsontext.Encoder</code> and <code>jsontext.Decoder</code> and is actually the underlying implementation of the previously mentioned functions. Unlike <code>v1</code>, options are a first-class argument to each of the marshal and unmarshal functions, greatly extending the flexibility and configurability of <code>v2</code>. There are <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/v2#Options\">several options available</a> in <code>v2</code> which are not covered by this article.</p>\n<h3>Type-specified customization</h3>\n<p>Similar to <code>v1</code>, <code>v2</code> allows types to define their own JSON representation by satisfying particular interfaces.</p>\n<pre><code class=\"language-go\"><span class=\"code-snippet\" data-language=\"Go\"><span class=\"syntax-source syntax-go\"><span class=\"syntax-meta syntax-type syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">type</span> <span class=\"syntax-entity syntax-name syntax-type syntax-go\">Marshaler</span> <span class=\"syntax-storage syntax-type syntax-go\">interface</span> {</span>\n    <span class=\"syntax-meta syntax-function-call syntax-go\"><span class=\"syntax-variable syntax-function syntax-go\">MarshalJSON</span><span class=\"syntax-meta syntax-function-call syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span></span></span></span><span class=\"syntax-meta syntax-function-call syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span></span> <span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span><span class=\"syntax-meta syntax-brackets syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-brackets syntax-begin syntax-go\">[</span><span class=\"syntax-punctuation syntax-definition syntax-brackets syntax-end syntax-go\">]</span></span><span class=\"syntax-storage syntax-type syntax-go\">byte</span><span class=\"syntax-punctuation syntax-separator syntax-go\">,</span> <span class=\"syntax-storage syntax-type syntax-go\">error</span><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span>\n}\n<span class=\"syntax-meta syntax-type syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">type</span> <span class=\"syntax-entity syntax-name syntax-type syntax-go\">MarshalerTo</span> <span class=\"syntax-storage syntax-type syntax-go\">interface</span> {</span>\n    <span class=\"syntax-meta syntax-function-call syntax-go\"><span class=\"syntax-variable syntax-function syntax-go\">MarshalJSONTo</span><span class=\"syntax-meta syntax-function-call syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span></span></span></span><span class=\"syntax-meta syntax-function-call syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-keyword syntax-operator syntax-go\">*</span>jsontext<span class=\"syntax-punctuation syntax-accessor syntax-go\">.</span><span class=\"syntax-variable syntax-other syntax-member syntax-go\">Encoder</span></span><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span></span> <span class=\"syntax-storage syntax-type syntax-go\">error</span>\n}\n\n<span class=\"syntax-meta syntax-type syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">type</span> <span class=\"syntax-entity syntax-name syntax-type syntax-go\">Unmarshaler</span> <span class=\"syntax-storage syntax-type syntax-go\">interface</span> {</span>\n    <span class=\"syntax-meta syntax-function-call syntax-go\"><span class=\"syntax-variable syntax-function syntax-go\">UnmarshalJSON</span><span class=\"syntax-meta syntax-function-call syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span></span></span></span><span class=\"syntax-meta syntax-function-call syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-meta syntax-brackets syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-brackets syntax-begin syntax-go\">[</span><span class=\"syntax-punctuation syntax-definition syntax-brackets syntax-end syntax-go\">]</span></span><span class=\"syntax-storage syntax-type syntax-go\">byte</span></span><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span></span> <span class=\"syntax-storage syntax-type syntax-go\">error</span>\n}\n<span class=\"syntax-meta syntax-type syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">type</span> <span class=\"syntax-entity syntax-name syntax-type syntax-go\">UnmarshalerFrom</span> <span class=\"syntax-storage syntax-type syntax-go\">interface</span> {</span>\n    <span class=\"syntax-meta syntax-function-call syntax-go\"><span class=\"syntax-variable syntax-function syntax-go\">UnmarshalJSONFrom</span><span class=\"syntax-meta syntax-function-call syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span></span></span></span><span class=\"syntax-meta syntax-function-call syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-keyword syntax-operator syntax-go\">*</span>jsontext<span class=\"syntax-punctuation syntax-accessor syntax-go\">.</span><span class=\"syntax-variable syntax-other syntax-member syntax-go\">Decoder</span></span><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span></span> <span class=\"syntax-storage syntax-type syntax-go\">error</span>\n}\n</span></span></code></pre>\n<p>The <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/v2#Marshaler\"><code>Marshaler</code></a> and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/v2#Unmarshaler\"><code>Unmarshaler</code></a> interfaces are identical to those in <code>v1</code>. The new <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/v2#MarshalerTo\"><code>MarshalerTo</code></a> and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/v2#UnmarshalerFrom\"><code>UnmarshalerFrom</code></a> interfaces allow a type to represent itself as JSON using a <code>jsontext.Encoder</code> or <code>jsontext.Decoder</code>. This allows options to be forwarded down the call stack, since options can be retrieved via the <code>Options</code> accessor method on the <code>Encoder</code> or <code>Decoder</code>.</p>\n<p>See <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/v2#example-package-OrderedObject\">the <code>OrderedObject</code> example</a> for how to implement a custom type that maintains the ordering of JSON object members.</p>\n<h3>Caller-specified customization</h3>\n<p>In <code>v2</code>, the caller of <code>Marshal</code> and <code>Unmarshal</code> can also specify a custom JSON representation for any arbitrary type, where caller-specified functions take precedence over type-defined methods or the default representation for a particular type.</p>\n<pre><code class=\"language-go\"><span class=\"code-snippet\" data-language=\"Go\"><span class=\"syntax-source syntax-go\"><span class=\"syntax-meta syntax-function syntax-declaration syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">func</span> <span class=\"syntax-entity syntax-name syntax-function syntax-go\">WithMarshalers</span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-keyword syntax-operator syntax-go\">*</span>Marshalers</span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span></span><span class=\"syntax-meta syntax-function syntax-go\"> </span><span class=\"syntax-meta syntax-function syntax-return-type syntax-go\">Options</span>\n\n<span class=\"syntax-meta syntax-struct syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">type</span> <span class=\"syntax-entity syntax-name syntax-struct syntax-go\">Marshalers</span> <span class=\"syntax-storage syntax-type syntax-go\">struct</span> <span class=\"syntax-meta syntax-block syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-block syntax-begin syntax-go\">{</span> ... </span><span class=\"syntax-meta syntax-block syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-block syntax-end syntax-go\">}</span></span></span>\n<span class=\"syntax-storage syntax-type syntax-go\">func</span> <span class=\"syntax-entity syntax-name syntax-function syntax-go\">MarshalFunc</span><span class=\"syntax-meta syntax-brackets syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-brackets syntax-begin syntax-go\">[</span>T any<span class=\"syntax-punctuation syntax-definition syntax-brackets syntax-end syntax-go\">]</span></span><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span>fn <span class=\"syntax-meta syntax-function syntax-declaration syntax-anonymous syntax-go\"><span class=\"syntax-meta syntax-function syntax-declaration syntax-anonymous syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">func</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\">T</span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span></span><span class=\"syntax-meta syntax-function syntax-go\"> </span><span class=\"syntax-meta syntax-function syntax-return-type syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span></span></span><span class=\"syntax-meta syntax-function syntax-return-type syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-meta syntax-brackets syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-brackets syntax-begin syntax-go\">[</span><span class=\"syntax-punctuation syntax-definition syntax-brackets syntax-end syntax-go\">]</span></span><span class=\"syntax-storage syntax-type syntax-go\">byte</span><span class=\"syntax-punctuation syntax-separator syntax-go\">,</span> <span class=\"syntax-storage syntax-type syntax-go\">error</span><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span></span>) *Marshalers\n<span class=\"syntax-storage syntax-type syntax-go\">func</span> MarshalToFunc<span class=\"syntax-meta syntax-brackets syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-brackets syntax-begin syntax-go\">[</span>T any<span class=\"syntax-punctuation syntax-definition syntax-brackets syntax-end syntax-go\">]</span></span><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span>fn <span class=\"syntax-meta syntax-function syntax-declaration syntax-anonymous syntax-go\"><span class=\"syntax-meta syntax-function syntax-declaration syntax-anonymous syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">func</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-keyword syntax-operator syntax-go\">*</span>jsontext.Encoder<span class=\"syntax-punctuation syntax-separator syntax-go\">,</span> T</span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span></span><span class=\"syntax-meta syntax-function syntax-go\"> </span><span class=\"syntax-meta syntax-function syntax-return-type syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">error</span></span><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span> <span class=\"syntax-keyword syntax-operator syntax-go\">*</span>Marshalers\n\n<span class=\"syntax-storage syntax-type syntax-go\">func</span> <span class=\"syntax-meta syntax-function-call syntax-go\"><span class=\"syntax-variable syntax-function syntax-go\">WithUnmarshalers</span><span class=\"syntax-meta syntax-function-call syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span></span></span></span><span class=\"syntax-meta syntax-function-call syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-keyword syntax-operator syntax-go\">*</span>Unmarshalers</span><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span></span> Options\n\n<span class=\"syntax-storage syntax-type syntax-go\">type</span> Unmarshalers <span class=\"syntax-storage syntax-type syntax-go\">struct</span> { ... }\n<span class=\"syntax-storage syntax-type syntax-go\">func</span> UnmarshalFunc<span class=\"syntax-meta syntax-brackets syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-brackets syntax-begin syntax-go\">[</span>T any<span class=\"syntax-punctuation syntax-definition syntax-brackets syntax-end syntax-go\">]</span></span><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span>fn <span class=\"syntax-meta syntax-function syntax-declaration syntax-anonymous syntax-go\"><span class=\"syntax-meta syntax-function syntax-declaration syntax-anonymous syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">func</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-meta syntax-brackets syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-brackets syntax-begin syntax-go\">[</span><span class=\"syntax-punctuation syntax-definition syntax-brackets syntax-end syntax-go\">]</span></span><span class=\"syntax-storage syntax-type syntax-go\">byte</span><span class=\"syntax-punctuation syntax-separator syntax-go\">,</span> T</span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span></span><span class=\"syntax-meta syntax-function syntax-go\"> </span><span class=\"syntax-meta syntax-function syntax-return-type syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">error</span></span><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span> <span class=\"syntax-keyword syntax-operator syntax-go\">*</span>Unmarshalers\n<span class=\"syntax-storage syntax-type syntax-go\">func</span> UnmarshalFromFunc<span class=\"syntax-meta syntax-brackets syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-brackets syntax-begin syntax-go\">[</span>T any<span class=\"syntax-punctuation syntax-definition syntax-brackets syntax-end syntax-go\">]</span></span><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span>fn <span class=\"syntax-meta syntax-function syntax-declaration syntax-anonymous syntax-go\"><span class=\"syntax-meta syntax-function syntax-declaration syntax-anonymous syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">func</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span></span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-keyword syntax-operator syntax-go\">*</span>jsontext.Decoder<span class=\"syntax-punctuation syntax-separator syntax-go\">,</span> T</span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span></span><span class=\"syntax-meta syntax-function syntax-go\"> </span><span class=\"syntax-meta syntax-function syntax-return-type syntax-go\"><span class=\"syntax-storage syntax-type syntax-go\">error</span></span><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span> <span class=\"syntax-keyword syntax-operator syntax-go\">*</span>Unmarshalers\n</span></span></span></code></pre>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/v2#MarshalFunc\"><code>MarshalFunc</code></a> and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/v2#MarshalToFunc\"><code>MarshalToFunc</code></a> construct a custom marshaler that can be passed to a <code>Marshal</code> call using <code>WithMarshalers</code> to override the marshaling of particular types. Similarly, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/v2#UnmarshalFunc\"><code>UnmarshalFunc</code></a> and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/v2#UnmarshalFromFunc\"><code>UnmarshalFromFunc</code></a> support similar customization for <code>Unmarshal</code>.</p>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/v2#example-package-ProtoJSON\">The <code>ProtoJSON</code> example</a> demonstrates how this feature allows serialization of all <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/google.golang.org/protobuf/proto#Message\"><code>proto.Message</code></a> types to be handled by the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/google.golang.org/protobuf/encoding/protojson\"><code>protojson</code></a> package.</p>\n<h3>Behavior differences</h3>\n<p>While <code>v2</code> aims to behave <em>mostly</em> the same as <code>v1</code>, its behavior has changed <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json#hdr-Migrating_to_v2\">in some ways</a> to address problems in <code>v1</code>, most notably:</p>\n<ul>\n<li>\n<p><code>v2</code> reports an error in the presence of invalid UTF-8.</p>\n</li>\n<li>\n<p><code>v2</code> reports an error if a JSON object contains a duplicate name.</p>\n</li>\n<li>\n<p><code>v2</code> marshals a nil Go slice or Go map as an empty JSON array or JSON object, respectively.</p>\n</li>\n<li>\n<p><code>v2</code> unmarshals a JSON object into a Go struct using a case-sensitive match from the JSON member name to the Go field name.</p>\n</li>\n<li>\n<p><code>v2</code> redefines the <code>omitempty</code> tag option to omit a field if it would have encoded as an “empty” JSON value (which are <code>null</code>, <code>&quot;&quot;</code>, <code>[]</code>, and <code>{}</code>).</p>\n</li>\n<li>\n<p><code>v2</code> reports an error when trying to serialize a <code>time.Duration</code>, which currently has <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/issue/71631\">no default representation</a>, but provides options to let the caller decide.</p>\n</li>\n</ul>\n<p>For most behavior changes, there is a struct tag option or caller-specified option that can configure the behavior to operate under <code>v1</code> or <code>v2</code> semantics, or even other caller-determined behavior. See “<a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json#hdr-Migrating_to_v2\">Migrating to v2</a>” for more information.</p>\n<h3>Performance optimizations</h3>\n<p>The <code>Marshal</code> performance of <code>v2</code> is roughly at parity with <code>v1</code>. Sometimes it is slightly faster, but other times it is slightly slower. The <code>Unmarshal</code> performance of <code>v2</code> is significantly faster than <code>v1</code>, with benchmarks demonstrating improvements of up to 10x.</p>\n<p>In order to obtain greater performance gains, existing implementations of <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/v2#Marshaler\"><code>Marshaler</code></a> and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/v2#Unmarshaler\"><code>Unmarshaler</code></a> should migrate to also implement <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/v2#MarshalerTo\"><code>MarshalerTo</code></a> and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/v2#UnmarshalerFrom\"><code>UnmarshalerFrom</code></a>, so that they can benefit from processing JSON in a purely streaming manner. For example, recursive parsing of OpenAPI specifications in <code>UnmarshalJSON</code> methods significantly hurt performance in a particular service of Kubernetes (see <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/kubernetes/kube-openapi/issues/315\">kubernetes/kube-openapi#315</a>), while switching to <code>UnmarshalJSONFrom</code> improved performance by orders of magnitude.</p>\n<p>For more benchmark details, see the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/go-json-experiment/jsonbench\"><code>go-json-experiment/jsonbench</code></a> repository.</p>\n<p>On top of structural improvements, we’re continuing to invest in performance optimizations in the implementation for <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/issue/81315\">cases</a> where the <code>v2</code> implementation is slower than <code>v1</code>. You can expect additional improvements in Go 1.28.</p>\n<h2>Compatibility</h2>\n<p>The <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/doc/go1compat\">Go 1 compatibility promise</a> is a core guarantee from the Go project that allows developers to build applications that will stand the test of time and continue working on future versions of Go.</p>\n<p>The original <code>encoding/json</code> and new <code>encoding/json/v2</code> packages are of course both subject to the Go 1 compatibility promise. Applications using <code>v1</code> will continue to be supported indefinitely and need not ever migrate to <code>v2</code>.</p>\n<p>That said, when designing the <code>v2</code> packages we wanted to do better than keeping <code>v1</code> supported. We wanted to ensure that <code>v1</code> and <code>v2</code> are <em>compatible with one another</em> so that packages using different versions can work together. Doing so allows a smooth ecosystem migration rather than forcing users to use one version or another due to the choices of their dependencies.</p>\n<p>For instance, suppose your package uses <code>v2</code> to marshal a struct containing a field with a type defined in another package. That package may use the original <code>v1</code> <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/v2#Marshaler\"><code>MarshalJSON</code></a> method to define its marshaling behavior. No problem, <code>v2</code> supports <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/v2#Marshaler\"><code>MarshalJSON</code></a> methods, so marshaling will continue working as expected.</p>\n<p>Vice versa, suppose your package uses <code>v1</code> to marshal the same struct. The other package may use the <code>v2</code> <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/v2#MarshalerTo\"><code>MarshalJSONTo</code></a> method for better performance. No problem here either, as the <code>v1</code> implementation supports <code>v2</code>’s <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/v2#MarshalerTo\"><code>MarshalJSONTo</code></a> method under the hood.</p>\n<p>We’ve achieved this by designing the new packages such that, under the hood, <code>v1</code> is implemented in terms of <code>v2</code>. As mentioned above, <code>v2</code> supports a variety of <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/v2#Options\"><code>Options</code></a>. Each behavior difference is controlled by one of the these options. Thus, <code>v1</code> can be implemented in terms of <code>v2</code> by passing the full set of <code>v1</code> compatibility options.</p>\n<p>With this approach, backwards-compatible additions such as new methods (e.g., <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/v2#MarshalerTo\"><code>MarshalJSONTo</code></a>) or new struct tags (e.g., <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/v2#hdr-JSON_Representation_of_Go_structs\"><code>embed</code></a>) are automatically supported to the benefit of existing <code>v1</code> users.</p>\n<h2>Migration</h2>\n<p>Implementing <code>v1</code> in terms of <code>v2</code> options has the additional benefit of allowing gradual migration of callsites.</p>\n<p><code>encoding/json</code> <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json#hdr-Migrating_to_v2\">documents</a> the full set of behavior differences between <code>v1</code> and <code>v2</code>, and which option controls that behaviors.</p>\n<p>Many applications can migrate directly to <code>v2</code> with no visible behavior changes, but applications that depend on <code>v1</code> behavior can use the options to change only the behaviors they depend on.</p>\n<p>For example, we can select all <code>v2</code> behavior except for allowing duplicate names:</p>\n<pre><code class=\"language-go\"><span class=\"code-snippet\" data-language=\"Go\"><span class=\"syntax-source syntax-go\">json<span class=\"syntax-meta syntax-function-call syntax-method syntax-go\"><span class=\"syntax-punctuation syntax-accessor syntax-go\">.</span><span class=\"syntax-variable syntax-function syntax-go\">Marshal</span><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span></span></span><span class=\"syntax-meta syntax-function-call syntax-method syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\">...<span class=\"syntax-punctuation syntax-separator syntax-go\">,</span> json<span class=\"syntax-meta syntax-function-call syntax-method syntax-go\"><span class=\"syntax-punctuation syntax-accessor syntax-go\">.</span><span class=\"syntax-variable syntax-function syntax-go\">DefaultOptionsV2</span><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span></span></span><span class=\"syntax-meta syntax-function-call syntax-method syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span></span><span class=\"syntax-punctuation syntax-separator syntax-go\">,</span> jsontext<span class=\"syntax-meta syntax-function-call syntax-method syntax-go\"><span class=\"syntax-punctuation syntax-accessor syntax-go\">.</span><span class=\"syntax-variable syntax-function syntax-go\">AllowDuplicateName</span><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span></span></span><span class=\"syntax-meta syntax-function-call syntax-method syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-constant syntax-language syntax-go\">true</span></span><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span></span></span><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span></span>\n</span></span></code></pre>\n<p>Alternatively, we can select all <code>v1</code> behavior except for disallowing duplicate names:</p>\n<pre><code class=\"language-go\"><span class=\"code-snippet\" data-language=\"Go\"><span class=\"syntax-source syntax-go\">json<span class=\"syntax-meta syntax-function-call syntax-method syntax-go\"><span class=\"syntax-punctuation syntax-accessor syntax-go\">.</span><span class=\"syntax-variable syntax-function syntax-go\">Marshal</span><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span></span></span><span class=\"syntax-meta syntax-function-call syntax-method syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\">...<span class=\"syntax-punctuation syntax-separator syntax-go\">,</span> jsonv1<span class=\"syntax-meta syntax-function-call syntax-method syntax-go\"><span class=\"syntax-punctuation syntax-accessor syntax-go\">.</span><span class=\"syntax-variable syntax-function syntax-go\">DefaultOptionsV1</span><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span></span></span><span class=\"syntax-meta syntax-function-call syntax-method syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span></span><span class=\"syntax-punctuation syntax-separator syntax-go\">,</span> jsontext<span class=\"syntax-meta syntax-function-call syntax-method syntax-go\"><span class=\"syntax-punctuation syntax-accessor syntax-go\">.</span><span class=\"syntax-variable syntax-function syntax-go\">AllowDuplicateName</span><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-begin syntax-go\">(</span></span></span><span class=\"syntax-meta syntax-function-call syntax-method syntax-go\"><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-constant syntax-language syntax-go\">false</span></span><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span></span></span><span class=\"syntax-meta syntax-group syntax-go\"><span class=\"syntax-punctuation syntax-definition syntax-group syntax-end syntax-go\">)</span></span></span>\n</span></span></code></pre>\n<p>The <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/doc/jsonv2-migration\"><code>encoding/json/v2</code> Migration Guide</a> goes into more detail, covering a variety of scenarios for approaching migration of an application from <code>v1</code> to <code>v2</code>.</p>\n<h2>More information</h2>\n<p>For additional references on working with the new JSON packages, see:</p>\n<ul>\n<li>The “<a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/doc/tutorial/json\">Working with JSON</a>” tutorial,</li>\n<li>The <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/v2\"><code>encoding/json/v2</code></a> and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json/jsontext\"><code>encoding/json/jsontext</code></a> package documentation,</li>\n<li>The <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/encoding/json#hdr-Migrating_to_v2\"><code>encoding/json</code></a> package’s <code>v2</code> comparison documentation,</li>\n<li>and the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/doc/jsonv2-migration\"><code>encoding/json/v2</code> Migration Guide</a>.</li>\n</ul>\n<h2>A long time coming</h2>\n<p>The planning for a new major version of <code>encoding/json</code> spanned years. In late 2020, spurred on by the inability to fix issues in the current package, Daniel Martí (one of the maintainers of <code>encoding/json</code>) first drafted his thoughts on <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://docs.google.com/document/d/1WQGoM44HLinH4NGBEv5drGlw5_RNW-GP7DdGEpm7Y3o\">what a hypothetical <code>v2</code> package should look like</a>. Separately, after previous work on the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/blog/protobuf-apiv2\">Go API for Protocol Buffers</a>, Joe Tsai was disappointed that <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/pkg/google.golang.org/protobuf/encoding/protojson\">the <code>protojson</code> package</a> needed to use a custom JSON implementation because <code>encoding/json</code> was neither capable of adhering to the stricter JSON standard that the Protocol Buffer specification required, nor of efficiently serializing JSON in a streaming manner.</p>\n<p>Believing a brighter future for JSON was both beneficial and achievable, Daniel and Joe joined forces to brainstorm on <code>v2</code> and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/go-json-experiment/json\">started to build a prototype</a> (with the initial code being a polished version of the JSON serialization logic from the Go protobuf module). Over time, a few others (Roger Peppe, Chris Hines, Johan Brandhorst-Satzkorn, Damien Neil, Austin Clements, and Michael Pratt) joined the effort by providing design review, code review, and regression testing. Many of the early discussions are publicly available in our <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.youtube.com/playlist?list=PLZgrQPcV8W8EChkaAvv-3NUu6PYmnGG3b\">recorded meetings</a> and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://docs.google.com/document/d/1rovrOTd-wTawGMPPlPuKhwXaYBg9VszTXR9AQQL5LfI\">meeting notes</a>. Later discussions are available in our <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/issue/76406\"><code>encoding/json/v2</code> working group meeting notes</a>.</p>\n<p>This work has been public since the beginning, and we increasingly involved the wider Go community, first with a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.youtube.com/watch?v=avilmOcHKHE\">GopherCon talk</a> and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/issue/63397\">discussion posted in late 2023</a>, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/issue/71497\">formal proposal posted in early 2025</a>, and finally <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://go.dev/issue/71845\">adopting <code>encoding/json/v2</code> as a Go experiment in Go 1.25</a> for wider-scale testing by all Go users.</p>\n<p>The <code>v2</code> effort took about 6 years to reach this point, incorporating feedback from many contributors and also gaining valuable empirical experience from use in production settings.</p>\n<p>It’s worth noting that it’s largely been developed and promoted by people not employed by Google, demonstrating that the Go project is a collaborative endeavor with a thriving global community dedicated to improving the Go ecosystem.</p>\n<p>Thank you to everyone that contributed!</p>\n",
  "content_markdown": "Michael Pratt, Joe Tsai, Daniel Martí, Johan Brandhorst-Satzkorn, Roger Peppe, Chris Hines, and Damien Neil\\\n 4 November 2026\n\n## Introduction\n\n[JavaScript Object Notation (JSON)](https://datatracker.ietf.org/doc/html/rfc8259) is a simple data interchange format, the most popular data format used on the internet, and widely read and written by Go programs. Go has included support for JSON in the standard library since well before Go 1.0. We first we wrote about [support for JSON in Go](https://go.dev/blog/json) more than 15 years ago!\n\nOver time, packages evolve with the needs of their users, and `encoding/json` is no exception. Last year, we wrote about a [new experimental API for JSON](https://go.dev/blog/jsonv2-exp) designed to bring long-awaited improvements and fixes.\n\nAfter additional feedback and refinement, the new `encoding/json/v2` and `encoding/json/jsontext` packages are stable and generally available in Go 1.27! This post is an updated version of the [article introducing the experiment](https://go.dev/blog/jsonv2-exp), describing the problems with `encoding/json`, and an introduction to the new packages.\n\n## Problems with `encoding/json`\n\nOverall, `encoding/json` has held up well. The idea of marshaling and unmarshaling arbitrary Go types with some default representation in JSON, combined with the ability to customize the representation, has proven to be highly flexible. However, in the years since its introduction, various users have identified numerous shortcomings.\n\n### Behavior flaws\n\nThere are various behavioral flaws in `encoding/json`:\n\n- **Imprecise handling of JSON syntax**: Over the years, JSON has seen increased standardization in order for programs to properly communicate. Generally, decoders have become stricter at rejecting ambiguous inputs, to reduce the chance that two implementations will have different (successful) interpretations of a particular JSON value.\n\n  - `encoding/json` currently accepts invalid UTF-8, whereas the latest Internet Standard (RFC 8259) requires valid UTF-8. The default behavior should report an error in the presence of invalid UTF-8, instead of introducing silent data corruption, which may cause problems downstream.\n\n  - `encoding/json` currently accepts objects with duplicate member names. RFC 8259 does not specify how to handle duplicate names, so an implementation is free to choose an arbitrary value, merge the values, discard the values, or report an error. The presence of a duplicate name results in a JSON value without a universally agreed upon meaning. This could be [exploited by attackers in security applications](https://www.youtube.com/watch?v=avilmOcHKHE&t=1057s) and has been exploited before (as in [CVE-2017-12635](https://nvd.nist.gov/vuln/detail/CVE-2017-12635)). The default behavior should err on the side of safety and reject duplicate names.\n- **Leaking nilness of slices and maps**: JSON is often used to communicate with programs using JSON implementations that do not allow `null` to be unmarshaled into a data type expected to be a JSON array or object. Since `encoding/json` marshals a nil slice or map as a JSON `null`, this may lead to errors when unmarshaling by other implementations. [A survey](https://go.dev/issue/63397#discussioncomment-7201222) indicated that most Go users prefer that nil slices and maps are marshaled as an empty JSON array or object by default.\n\n- **Case-insensitive unmarshaling**: When unmarshaling, a JSON object member name is resolved to a Go struct field name using a case-insensitive match. This is a surprising default, a potential security vulnerability, and a performance limitation.\n\n- **Inconsistent calling of methods**: Due to an implementation detail, `MarshalJSON` methods declared on a pointer receiver are [inconsistently called by `encoding/json`](https://go.dev/issue/22967). While regarded as a bug, this cannot be fixed as too many applications depend on the current behavior.\n\n### API deficiencies\n\nThe API of `encoding/json` can be tricky or restrictive:\n\n- It is difficult to correctly unmarshal from an `io.Reader`. Users often write `json.NewDecoder(r).Decode(v)`, which fails to reject trailing junk at the end of the input.\n\n- Options can be set on the `Encoder` and `Decoder` types, but cannot be used with the `Marshal` and `Unmarshal` functions. Similarly, types implementing the `Marshaler` and `Unmarshaler` interfaces cannot make use of the options and there is no way to plumb options down the call stack. For example, the `Decoder.DisallowUnknownFields` option loses its effect when calling a custom `UnmarshalJSON` method.\n\n- The `Compact`, `Indent`, and `HTMLEscape` functions write to a `bytes.Buffer` instead of something more flexible like a `[]byte` or `io.Writer`. This limits the usability of those functions.\n\n### Performance limitations\n\nSetting aside internal implementation details, the public API commits it to certain performance limitations:\n\n- **MarshalJSON**: The `MarshalJSON` interface method forces the implementation to allocate the returned `[]byte`. Also, the semantics require that `encoding/json` verify that the result is valid JSON and also to reformat it to match the specified indentation.\n\n- **UnmarshalJSON**: The `UnmarshalJSON` interface method requires that a complete JSON value be provided (without any trailing data). This forces `encoding/json` to parse the JSON value to be unmarshaled in its entirety to determine where it ends before it can call `UnmarshalJSON`. Afterwards, the `UnmarshalJSON` method itself must parse the provided JSON value again.\n\n- **Lack of streaming**: Even though the `Encoder` and `Decoder` types operate on an `io.Writer` or `io.Reader`, they buffer the entire JSON value in memory. The `Decoder.Token` method for reading individual tokens is allocation-heavy and there is no corresponding API for writing tokens.\n\nFurthermore, if the implementation of a `MarshalJSON` or `UnmarshalJSON` method recursively calls the `Marshal` or `Unmarshal` function, then the performance becomes quadratic.\n\n## Trying to fix `encoding/json` directly\n\nIntroducing a new, incompatible major version of a package is a heavy consideration. If possible, we should try to fix the existing package.\n\nWhile it is relatively easy to add new features, it is difficult to change existing features. Unfortunately, these problems are inherent consequences of the existing API, making them practically impossible to fix within the [Go 1 compatibility promise](https://go.dev/doc/go1compat).\n\nWe could in principle declare separate names, such as `MarshalV2` or `UnmarshalV2`, but that is tantamount to creating a parallel namespace within the same package. This leads us to `encoding/json/v2` (henceforth called `v2`), where we can make these changes within a separate `v2` namespace in contrast to `encoding/json` (henceforth called `v1`).\n\n## Building on `encoding/json/jsontext`\n\nBefore discussing the `v2` API, we first introduce the [`encoding/json/jsontext`](https://go.dev/pkg/encoding/json/jsontext) package that lays the foundation for improvements to JSON in Go.\n\nJSON serialization in Go can be broken down into two primary components:\n\n- *syntactic functionality* that is concerned with processing JSON based on its grammar, and\n- *semantic functionality* that defines the relationship between JSON values and Go values.\n\nWe use the terms “encode” and “decode” to describe syntactic functionality and the terms “marshal” and “unmarshal” to describe semantic functionality. We aim to provide a clear distinction between functionality that is purely concerned with encoding versus that of marshaling.\n\n![](https://go.dev/blog/jsonv2/api.png)\n\nThis diagram provides an overview of this separation. Purple blocks represent types, while blue blocks represent functions or methods. The direction of the arrows approximately represents the flow of data. The bottom half of the diagram, implemented by the `jsontext` package, contains functionality that is only concerned with syntax, while the upper half, implemented by the `json/v2` package, contains functionality that assigns semantic meaning to syntactic data handled by the bottom half.\n\nThe basic API of `jsontext` is the following:\n\n```go\npackage jsontext\n\ntype Encoder struct { ... }\nfunc NewEncoder(io.Writer, ...Options) *Encoder\nfunc (*Encoder) WriteValue(Value) error\nfunc (*Encoder) WriteToken(Token) error\n\ntype Decoder struct { ... }\nfunc NewDecoder(io.Reader, ...Options) *Decoder\nfunc (*Decoder) ReadValue() (Value, error)\nfunc (*Decoder) ReadToken() (Token, error)\n\ntype Kind byte\ntype Value []byte\nfunc (Value) Kind() Kind\ntype Token struct { ... }\nfunc (Token) Kind() Kind\n```\n\nThe `jsontext` package provides functionality for interacting with JSON at a syntactic level and derives its name from [RFC 8259, section 2](https://datatracker.ietf.org/doc/html/rfc8259#section-2) where the grammar for JSON data is literally called `JSON-text`. Since it only interacts with JSON at a syntactic level, it does not depend on Go reflection.\n\nThe [`Encoder`](https://go.dev/pkg/encoding/json/jsontext#Encoder) and [`Decoder`](https://go.dev/pkg/encoding/json/jsontext#Decoder) provide support for encoding and decoding JSON values and tokens. The constructors [accept variadic options](https://go.dev/pkg/encoding/json/jsontext#Options) that affect the particular behavior of encoding and decoding. Unlike the `Encoder` and `Decoder` types declared in `v1`, the new types in `jsontext` avoid muddling the distinction between syntax and semantics and operate in a truly streaming manner.\n\nA JSON value is a complete unit of data and is represented in Go as [a named `[]byte`](https://go.dev/pkg/encoding/json/jsontext#Value). It is identical to [`RawMessage`](https://go.dev/pkg/encoding/json#RawMessage) in `v1`, which is now a type alias to `jsontext.Value`. A JSON value is syntactically composed of one or more JSON tokens. A JSON token is represented in Go as the [opaque `Token` type](https://go.dev/pkg/encoding/json/jsontext#Token) with constructors and accessor methods. It is analogous to [`Token`](https://go.dev/pkg/encoding/json#Token) in `v1` but is designed represent arbitrary JSON tokens without allocation.\n\nTo resolve the fundamental performance problems with the `MarshalJSON` and `UnmarshalJSON` interface methods, we need an efficient way of encoding and decoding JSON as a streaming sequence of tokens and values. In `v2`, we introduce the `MarshalJSONTo` and `UnmarshalJSONFrom` interface methods that operate on an `Encoder` or `Decoder`, allowing the methods’ implementations to process JSON in a purely streaming manner. Thus, the `json` package need not be responsible for validating or formatting a JSON value returned by `MarshalJSON`, nor would it need to be responsible for determining the boundaries of a JSON value provided to `UnmarshalJSON`. These responsibilities belong to the `Encoder` and `Decoder`.\n\n## Introducing `encoding/json/v2`\n\nBuilding on the `jsontext` package, we now introduce the [`encoding/json/v2`](https://go.dev/pkg/encoding/json/v2) package. It is designed to fix the problems in `v1`, while remaining familiar to users of the `v1` package. Our goal is that usages of `v1` will operate *mostly* the same if directly migrated to `v2`.\n\nHere, we primarily cover the high-level API of `v2`. For examples on how to use it, we encourage readers to study [the examples in the `v2` package](https://go.dev/pkg/encoding/json/v2#pkg-examples), the “[Working with JSON](https://go.dev/doc/tutorial/json)” tutorial, or read [Anton Zhiyanov’s blog covering the topic](https://antonz.org/go-json-v2/).\n\nThe basic API of `v2` is the following:\n\n```go\npackage json\n\nfunc Marshal(in any, opts ...Options) (out []byte, err error)\nfunc MarshalWrite(out io.Writer, in any, opts ...Options) error\nfunc MarshalEncode(out *jsontext.Encoder, in any, opts ...Options) error\n\nfunc Unmarshal(in []byte, out any, opts ...Options) error\nfunc UnmarshalRead(in io.Reader, out any, opts ...Options) error\nfunc UnmarshalDecode(in *jsontext.Decoder, out any, opts ...Options) error\n```\n\nThe [`Marshal`](https://go.dev/pkg/encoding/json/v2#Marshal) and [`Unmarshal`](https://go.dev/pkg/encoding/json/v2#Unmarshal) functions have a signature similar to `v1`, but accept options to configure their behavior. The [`MarshalWrite`](https://go.dev/pkg/encoding/json/v2#MarshalWrite) and [`UnmarshalRead`](https://go.dev/pkg/encoding/json/v2#UnmarshalRead) functions directly operate on an `io.Writer` or `io.Reader`, avoiding the need to temporarily construct an `Encoder` or `Decoder` just to write or read from such types. The [`MarshalEncode`](https://go.dev/pkg/encoding/json/v2#MarshalEncode) and [`UnmarshalDecode`](https://go.dev/pkg/encoding/json/v2#UnmarshalDecode) functions operate on a `jsontext.Encoder` and `jsontext.Decoder` and is actually the underlying implementation of the previously mentioned functions. Unlike `v1`, options are a first-class argument to each of the marshal and unmarshal functions, greatly extending the flexibility and configurability of `v2`. There are [several options available](https://go.dev/pkg/encoding/json/v2#Options) in `v2` which are not covered by this article.\n\n### Type-specified customization\n\nSimilar to `v1`, `v2` allows types to define their own JSON representation by satisfying particular interfaces.\n\n```go\ntype Marshaler interface {\n    MarshalJSON() ([]byte, error)\n}\ntype MarshalerTo interface {\n    MarshalJSONTo(*jsontext.Encoder) error\n}\n\ntype Unmarshaler interface {\n    UnmarshalJSON([]byte) error\n}\ntype UnmarshalerFrom interface {\n    UnmarshalJSONFrom(*jsontext.Decoder) error\n}\n```\n\nThe [`Marshaler`](https://go.dev/pkg/encoding/json/v2#Marshaler) and [`Unmarshaler`](https://go.dev/pkg/encoding/json/v2#Unmarshaler) interfaces are identical to those in `v1`. The new [`MarshalerTo`](https://go.dev/pkg/encoding/json/v2#MarshalerTo) and [`UnmarshalerFrom`](https://go.dev/pkg/encoding/json/v2#UnmarshalerFrom) interfaces allow a type to represent itself as JSON using a `jsontext.Encoder` or `jsontext.Decoder`. This allows options to be forwarded down the call stack, since options can be retrieved via the `Options` accessor method on the `Encoder` or `Decoder`.\n\nSee [the `OrderedObject` example](https://go.dev/pkg/encoding/json/v2#example-package-OrderedObject) for how to implement a custom type that maintains the ordering of JSON object members.\n\n### Caller-specified customization\n\nIn `v2`, the caller of `Marshal` and `Unmarshal` can also specify a custom JSON representation for any arbitrary type, where caller-specified functions take precedence over type-defined methods or the default representation for a particular type.\n\n```go\nfunc WithMarshalers(*Marshalers) Options\n\ntype Marshalers struct { ... }\nfunc MarshalFunc[T any](fn func(T) ([]byte, error)) *Marshalers\nfunc MarshalToFunc[T any](fn func(*jsontext.Encoder, T) error) *Marshalers\n\nfunc WithUnmarshalers(*Unmarshalers) Options\n\ntype Unmarshalers struct { ... }\nfunc UnmarshalFunc[T any](fn func([]byte, T) error) *Unmarshalers\nfunc UnmarshalFromFunc[T any](fn func(*jsontext.Decoder, T) error) *Unmarshalers\n```\n\n[`MarshalFunc`](https://go.dev/pkg/encoding/json/v2#MarshalFunc) and [`MarshalToFunc`](https://go.dev/pkg/encoding/json/v2#MarshalToFunc) construct a custom marshaler that can be passed to a `Marshal` call using `WithMarshalers` to override the marshaling of particular types. Similarly, [`UnmarshalFunc`](https://go.dev/pkg/encoding/json/v2#UnmarshalFunc) and [`UnmarshalFromFunc`](https://go.dev/pkg/encoding/json/v2#UnmarshalFromFunc) support similar customization for `Unmarshal`.\n\n[The `ProtoJSON` example](https://go.dev/pkg/encoding/json/v2#example-package-ProtoJSON) demonstrates how this feature allows serialization of all [`proto.Message`](https://go.dev/pkg/google.golang.org/protobuf/proto#Message) types to be handled by the [`protojson`](https://go.dev/pkg/google.golang.org/protobuf/encoding/protojson) package.\n\n### Behavior differences\n\nWhile `v2` aims to behave *mostly* the same as `v1`, its behavior has changed [in some ways](https://go.dev/pkg/encoding/json#hdr-Migrating_to_v2) to address problems in `v1`, most notably:\n\n- `v2` reports an error in the presence of invalid UTF-8.\n\n- `v2` reports an error if a JSON object contains a duplicate name.\n\n- `v2` marshals a nil Go slice or Go map as an empty JSON array or JSON object, respectively.\n\n- `v2` unmarshals a JSON object into a Go struct using a case-sensitive match from the JSON member name to the Go field name.\n\n- `v2` redefines the `omitempty` tag option to omit a field if it would have encoded as an “empty” JSON value (which are `null`, `\"\"`, `[]`, and `{}`).\n\n- `v2` reports an error when trying to serialize a `time.Duration`, which currently has [no default representation](https://go.dev/issue/71631), but provides options to let the caller decide.\n\nFor most behavior changes, there is a struct tag option or caller-specified option that can configure the behavior to operate under `v1` or `v2` semantics, or even other caller-determined behavior. See “[Migrating to v2](https://go.dev/pkg/encoding/json#hdr-Migrating_to_v2)” for more information.\n\n### Performance optimizations\n\nThe `Marshal` performance of `v2` is roughly at parity with `v1`. Sometimes it is slightly faster, but other times it is slightly slower. The `Unmarshal` performance of `v2` is significantly faster than `v1`, with benchmarks demonstrating improvements of up to 10x.\n\nIn order to obtain greater performance gains, existing implementations of [`Marshaler`](https://go.dev/pkg/encoding/json/v2#Marshaler) and [`Unmarshaler`](https://go.dev/pkg/encoding/json/v2#Unmarshaler) should migrate to also implement [`MarshalerTo`](https://go.dev/pkg/encoding/json/v2#MarshalerTo) and [`UnmarshalerFrom`](https://go.dev/pkg/encoding/json/v2#UnmarshalerFrom), so that they can benefit from processing JSON in a purely streaming manner. For example, recursive parsing of OpenAPI specifications in `UnmarshalJSON` methods significantly hurt performance in a particular service of Kubernetes (see [kubernetes/kube-openapi#315](https://github.com/kubernetes/kube-openapi/issues/315)), while switching to `UnmarshalJSONFrom` improved performance by orders of magnitude.\n\nFor more benchmark details, see the [`go-json-experiment/jsonbench`](https://github.com/go-json-experiment/jsonbench) repository.\n\nOn top of structural improvements, we’re continuing to invest in performance optimizations in the implementation for [cases](https://go.dev/issue/81315) where the `v2` implementation is slower than `v1`. You can expect additional improvements in Go 1.28.\n\n## Compatibility\n\nThe [Go 1 compatibility promise](https://go.dev/doc/go1compat) is a core guarantee from the Go project that allows developers to build applications that will stand the test of time and continue working on future versions of Go.\n\nThe original `encoding/json` and new `encoding/json/v2` packages are of course both subject to the Go 1 compatibility promise. Applications using `v1` will continue to be supported indefinitely and need not ever migrate to `v2`.\n\nThat said, when designing the `v2` packages we wanted to do better than keeping `v1` supported. We wanted to ensure that `v1` and `v2` are *compatible with one another* so that packages using different versions can work together. Doing so allows a smooth ecosystem migration rather than forcing users to use one version or another due to the choices of their dependencies.\n\nFor instance, suppose your package uses `v2` to marshal a struct containing a field with a type defined in another package. That package may use the original `v1` [`MarshalJSON`](https://go.dev/pkg/encoding/json/v2#Marshaler) method to define its marshaling behavior. No problem, `v2` supports [`MarshalJSON`](https://go.dev/pkg/encoding/json/v2#Marshaler) methods, so marshaling will continue working as expected.\n\nVice versa, suppose your package uses `v1` to marshal the same struct. The other package may use the `v2` [`MarshalJSONTo`](https://go.dev/pkg/encoding/json/v2#MarshalerTo) method for better performance. No problem here either, as the `v1` implementation supports `v2`’s [`MarshalJSONTo`](https://go.dev/pkg/encoding/json/v2#MarshalerTo) method under the hood.\n\nWe’ve achieved this by designing the new packages such that, under the hood, `v1` is implemented in terms of `v2`. As mentioned above, `v2` supports a variety of [`Options`](https://go.dev/pkg/encoding/json/v2#Options). Each behavior difference is controlled by one of the these options. Thus, `v1` can be implemented in terms of `v2` by passing the full set of `v1` compatibility options.\n\nWith this approach, backwards-compatible additions such as new methods (e.g., [`MarshalJSONTo`](https://go.dev/pkg/encoding/json/v2#MarshalerTo)) or new struct tags (e.g., [`embed`](https://go.dev/pkg/encoding/json/v2#hdr-JSON_Representation_of_Go_structs)) are automatically supported to the benefit of existing `v1` users.\n\n## Migration\n\nImplementing `v1` in terms of `v2` options has the additional benefit of allowing gradual migration of callsites.\n\n`encoding/json` [documents](https://go.dev/pkg/encoding/json#hdr-Migrating_to_v2) the full set of behavior differences between `v1` and `v2`, and which option controls that behaviors.\n\nMany applications can migrate directly to `v2` with no visible behavior changes, but applications that depend on `v1` behavior can use the options to change only the behaviors they depend on.\n\nFor example, we can select all `v2` behavior except for allowing duplicate names:\n\n```go\njson.Marshal(..., json.DefaultOptionsV2(), jsontext.AllowDuplicateName(true))\n```\n\nAlternatively, we can select all `v1` behavior except for disallowing duplicate names:\n\n```go\njson.Marshal(..., jsonv1.DefaultOptionsV1(), jsontext.AllowDuplicateName(false))\n```\n\nThe [`encoding/json/v2` Migration Guide](https://go.dev/doc/jsonv2-migration) goes into more detail, covering a variety of scenarios for approaching migration of an application from `v1` to `v2`.\n\n## More information\n\nFor additional references on working with the new JSON packages, see:\n\n- The “[Working with JSON](https://go.dev/doc/tutorial/json)” tutorial,\n- The [`encoding/json/v2`](https://go.dev/pkg/encoding/json/v2) and [`encoding/json/jsontext`](https://go.dev/pkg/encoding/json/jsontext) package documentation,\n- The [`encoding/json`](https://go.dev/pkg/encoding/json#hdr-Migrating_to_v2) package’s `v2` comparison documentation,\n- and the [`encoding/json/v2` Migration Guide](https://go.dev/doc/jsonv2-migration).\n\n## A long time coming\n\nThe planning for a new major version of `encoding/json` spanned years. In late 2020, spurred on by the inability to fix issues in the current package, Daniel Martí (one of the maintainers of `encoding/json`) first drafted his thoughts on [what a hypothetical `v2` package should look like](https://docs.google.com/document/d/1WQGoM44HLinH4NGBEv5drGlw5_RNW-GP7DdGEpm7Y3o). Separately, after previous work on the [Go API for Protocol Buffers](https://go.dev/blog/protobuf-apiv2), Joe Tsai was disappointed that [the `protojson` package](https://go.dev/pkg/google.golang.org/protobuf/encoding/protojson) needed to use a custom JSON implementation because `encoding/json` was neither capable of adhering to the stricter JSON standard that the Protocol Buffer specification required, nor of efficiently serializing JSON in a streaming manner.\n\nBelieving a brighter future for JSON was both beneficial and achievable, Daniel and Joe joined forces to brainstorm on `v2` and [started to build a prototype](https://github.com/go-json-experiment/json) (with the initial code being a polished version of the JSON serialization logic from the Go protobuf module). Over time, a few others (Roger Peppe, Chris Hines, Johan Brandhorst-Satzkorn, Damien Neil, Austin Clements, and Michael Pratt) joined the effort by providing design review, code review, and regression testing. Many of the early discussions are publicly available in our [recorded meetings](https://www.youtube.com/playlist?list=PLZgrQPcV8W8EChkaAvv-3NUu6PYmnGG3b) and [meeting notes](https://docs.google.com/document/d/1rovrOTd-wTawGMPPlPuKhwXaYBg9VszTXR9AQQL5LfI). Later discussions are available in our [`encoding/json/v2` working group meeting notes](https://go.dev/issue/76406).\n\nThis work has been public since the beginning, and we increasingly involved the wider Go community, first with a [GopherCon talk](https://www.youtube.com/watch?v=avilmOcHKHE) and [discussion posted in late 2023](https://go.dev/issue/63397), [formal proposal posted in early 2025](https://go.dev/issue/71497), and finally [adopting `encoding/json/v2` as a Go experiment in Go 1.25](https://go.dev/issue/71845) for wider-scale testing by all Go users.\n\nThe `v2` effort took about 6 years to reach this point, incorporating feedback from many contributors and also gaining valuable empirical experience from use in production settings.\n\nIt’s worth noting that it’s largely been developed and promoted by people not employed by Google, demonstrating that the Go project is a collaborative endeavor with a thriving global community dedicated to improving the Go ecosystem.\n\nThank you to everyone that contributed!\n",
  "dateCreated": "2026-11-04T21:49:17.908350494+00:00",
  "date_created": "2026-11-04T17:52:11+00:00",
  "date_modified": "2026-11-04T17:52:11+00:00",
  "date_published": "2026-11-04T17:52:11+00:00",
  "description": "Michael Pratt, Joe Tsai, Daniel Martí, Johan Brandhorst-Satzkorn, Roger Peppe, Chris Hines, and Damien Neil 4 November 2026 Introduction JavaScript Object Notation (JSON) is a simple data interchange format, the most popular data format…",
  "external_url": "https://go.dev/blog/jsonv2",
  "git": {
    "commit": "20068632b65d2830a2d54f25ca1d90d697bea2c0",
    "path": "items/hnrss-org/2026/11/2026-11-04-a-new-go-api-for-json.md",
    "permalink": "https://github.com/aymericbeaumet/aggr-instance/blob/20068632b65d2830a2d54f25ca1d90d697bea2c0/items/hnrss-org/2026/11/2026-11-04-a-new-go-api-for-json.md"
  },
  "id": "urn:aggr:item:11f31f6d87530af38bcb1fa8ecac75182eb10226",
  "isBasedOn": {
    "@id": "https://go.dev/blog/jsonv2"
  },
  "mainEntity": {
    "@id": "https://go.dev/blog/jsonv2",
    "@type": "CreativeWork",
    "archivedAt": {
      "@id": "https://aggr.aymericbeaumet.com/items/hnrss-org/2026-11-04-a-new-go-api-for-json/#webpage"
    },
    "author": [
      {
        "@type": "Person",
        "name": "olexsmir"
      }
    ],
    "dateModified": "2026-11-04T17:52:11+00:00",
    "datePublished": "2026-11-04T17:52:11+00:00",
    "headline": "A new Go API for JSON",
    "image": {
      "@type": "ImageObject",
      "caption": null,
      "height": 90,
      "url": "https://go.dev/doc/gopher/gopher5logo.jpg",
      "width": 160
    },
    "timeRequired": "PT15M",
    "url": "https://go.dev/blog/jsonv2",
    "wordCount": 3184
  },
  "name": "Archived snapshot: A new Go API for JSON",
  "preview": {
    "alt": null,
    "color": null,
    "height": 90,
    "placeholder": {
      "data_url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=",
      "hash": ""
    },
    "url": "https://go.dev/doc/gopher/gopher5logo.jpg",
    "width": 160
  },
  "reading_minutes": 15,
  "replicated_at": null,
  "source": "hnrss-org",
  "source_name": "Hacker News: Front Page",
  "summary": null,
  "tags": [],
  "temporalCoverage": "2026-11-04T21:49:17.908350494+00:00",
  "title": "A new Go API for JSON",
  "url": "https://aggr.aymericbeaumet.com/items/hnrss-org/2026-11-04-a-new-go-api-for-json/",
  "word_count": 3184
}