json

package
v0.0.0-...-dc4e619 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 11, 2024 License: Apache-2.0, BSD-3-Clause Imports: 20 Imported by: 0

README

JSON Serialization (v2)

GoDev Build Status

This module hosts an experimental implementation of v2 encoding/json. The API is unstable and breaking changes will regularly be made. Do not depend on this in publicly available modules.

Goals and objectives

  • Mostly backwards compatible: If possible, v2 should aim to be mostly compatible with v1 in terms of both API and default behavior to ease migration. For example, the Marshal and Unmarshal functions are the most widely used declarations in the v1 package. It seems sensible for equivalent functionality in v2 to be named the same and have the same signature. Behaviorally, we should aim for 95% to 99% backwards compatibility. We do not aim for 100% compatibility since we want the freedom to break certain behaviors that are now considered to have been a mistake. We may provide options that can bring the v2 implementation to 100% compatibility, but it will not be the default.

  • More flexible: There is a long list of feature requests. We should aim to provide the most flexible features that addresses most usages. We do not want to over fit the v2 API to handle every possible use case. Ideally, the features provided should be orthogonal in nature such that any combination of features results in as few surprising edge cases as possible.

  • More performant: JSON serialization is widely used and any bit of extra performance gains will be greatly appreciated. Some rarely used behaviors of v1 may be dropped in favor of better performance. For example, despite Encoder and Decoder operating on an io.Writer and io.Reader, they do not operate in a truly streaming manner, leading to a loss in performance. The v2 implementation should aim to be truly streaming by default (see #33714).

  • Easy to use (hard to misuse): The v2 API should aim to make the common case easy and the less common case at least possible. The API should avoid behavior that goes contrary to user expectation, which may result in subtle bugs (see #36225).

  • v1 and v2 maintainability: Since the v1 implementation must stay forever, it would be beneficial if v1 could be implemented under the hood with v2, allowing for less maintenance burden in the future. This probably implies that behavioral changes in v2 relative to v1 need to be exposed as options.

  • Avoid unsafe: Standard library packages generally avoid the use of package unsafe even if it could provide a performance boost. We aim to preserve this property.

Expectations

While this module aims to possibly be the v2 implementation of encoding/json, there is no guarantee that this outcome will occur. As with any major change to the Go standard library, this will eventually go through the Go proposal process. At the present moment, this is still in the design and experimentation phase and is not ready for a formal proposal.

There are several possible outcomes from this experiment:

  1. We determine that a v2 encoding/json would not provide sufficient benefit over the existing v1 encoding/json package. Thus, we abandon this effort.
  2. We propose a v2 encoding/json design, but it is rejected in favor of some other design that is considered superior.
  3. We propose a v2 encoding/json design, but rather than adding an entirely new v2 encoding/json package, we decide to merge its functionality into the existing v1 encoding/json package.
  4. We propose a v2 encoding/json design and it is accepted, resulting in its addition to the standard library.
  5. Some other unforeseen outcome (among the infinite number of possibilities).

Development

This module is primarily developed by @dsnet, @mvdan, and @johanbrandhorst with feedback provided by @rogpeppe, @ChrisHines, and @rsc.

Discussion about semantics occur semi-regularly, where a record of past meetings can be found here.

Design overview

This package aims to provide a clean separation between syntax and semantics. Syntax deals with the structural representation of JSON (as specified in RFC 4627, RFC 7159, RFC 7493, RFC 8259, and RFC 8785). Semantics deals with the meaning of syntactic data as usable application data.

The Encoder and Decoder types are streaming tokenizers concerned with the packing or parsing of JSON data. They operate on Token and RawValue types which represent the common data structures that are representable in JSON. Encoder and Decoder do not aim to provide any interpretation of the data.

Functions like Marshal, MarshalFull, MarshalNext, Unmarshal, UnmarshalFull, and UnmarshalNext provide semantic meaning by correlating any arbitrary Go type with some JSON representation of that type (as stored in data types like []byte, io.Writer, io.Reader, Encoder, or Decoder).

API overview

This diagram provides a high-level overview of the v2 json package. Purple blocks represent types, while blue blocks represent functions or methods. The arrows and their direction represent the approximate flow of data. The bottom half of the diagram contains functionality that is only concerned with syntax, while the upper half contains functionality that assigns semantic meaning to syntactic data handled by the bottom half.

In contrast to v1 encoding/json, options are represented as separate types rather than being setter methods on the Encoder or Decoder types.

Behavior changes

The v2 json package changes the default behavior of Marshal and Unmarshal relative to the v1 json package to be more sensible. Some of these behavior changes have options and workarounds to opt into behavior similar to what v1 provided.

This table shows an overview of the changes:

v1 v2 Details
JSON object members are unmarshaled into a Go struct using a case-insensitive name match. JSON object members are unmarshaled into a Go struct using a case-sensitive name match. CaseSensitivity
When marshaling a Go struct, a struct field marked as omitempty is omitted if the field value is an empty Go value, which is defined as false, 0, a nil pointer, a nil interface value, and any empty array, slice, map, or string. When marshaling a Go struct, a struct field marked as omitempty is omitted if the field value would encode as an empty JSON value, which is defined as a JSON null, or an empty JSON string, object, or array. OmitEmptyOption
The string option does affect Go bools. The string option does not affect Go bools. StringOption
The string option does not recursively affect sub-values of the Go field value. The string option does recursively affect sub-values of the Go field value. StringOption
The string option sometimes accepts a JSON null escaped within a JSON string. The string option never accepts a JSON null escaped within a JSON string. StringOption
A nil Go slice is marshaled as a JSON null. A nil Go slice is marshaled as an empty JSON array. NilSlicesAndMaps
A nil Go map is marshaled as a JSON null. A nil Go map is marshaled as an empty JSON object. NilSlicesAndMaps
A Go array may be unmarshaled from a JSON array of any length. A Go array must be unmarshaled from a JSON array of the same length. Arrays
A Go byte array is represented as a JSON array of JSON numbers. A Go byte array is represented as a Base64-encoded JSON string. ByteArrays
MarshalJSON and UnmarshalJSON methods declared on a pointer receiver are inconsistently called. MarshalJSON and UnmarshalJSON methods declared on a pointer receiver are consistently called. PointerReceiver
A Go map is marshaled in a deterministic order. A Go map is marshaled in a non-deterministic order. MapDeterminism
JSON strings are encoded with HTML-specific characters being escaped. JSON strings are encoded without any characters being escaped (unless necessary). EscapeHTML
When marshaling, invalid UTF-8 within a Go string are silently replaced. When marshaling, invalid UTF-8 within a Go string results in an error. InvalidUTF8
When unmarshaling, invalid UTF-8 within a JSON string are silently replaced. When unmarshaling, invalid UTF-8 within a JSON string results in an error. InvalidUTF8
When marshaling, an error does not occur if the output JSON value contains objects with duplicate names. When marshaling, an error does occur if the output JSON value contains objects with duplicate names. DuplicateNames
When unmarshaling, an error does not occur if the input JSON value contains objects with duplicate names. When unmarshaling, an error does occur if the input JSON value contains objects with duplicate names. DuplicateNames
Unmarshaling a JSON null into a non-empty Go value inconsistently clears the value or does nothing. Unmarshaling a JSON null into a non-empty Go value always clears the value. MergeNull
Unmarshaling a JSON value into a non-empty Go value follows inconsistent and bizarre behavior. Unmarshaling a JSON value into a non-empty Go value always merges if the input is an object, and otherwise replaces. MergeComposite
A time.Duration is represented as a JSON number containing the decimal number of nanoseconds. A time.Duration is represented as a JSON string containing the formatted duration (e.g., "1h2m3.456s"). TimeDurations
Unmarshaling a JSON number into a Go float beyond its representation results in an error. Unmarshaling a JSON number into a Go float beyond its representation uses the closest representable value (e.g., ±math.MaxFloat). MaxFloats
A Go struct with only unexported fields can be serialized. A Go struct with only unexported fields cannot be serialized. EmptyStructs
A Go struct that embeds an unexported struct type can sometimes be serialized. A Go struct that embeds an unexported struct type cannot be serialized. EmbedUnexported

See diff_test.go for details about every change.

Performance

One of the goals of the v2 module is to be more performant than v1.

Each of the charts below show the performance across several different JSON implementations:

  • JSONv1 is encoding/json at v1.18.2
  • JSONv2 is github.com/go-json-experiment/json at v0.0.0-20220524042235-dd8be80fc4a7
  • JSONIterator is github.com/json-iterator/go at v1.1.12
  • SegmentJSON is github.com/segmentio/encoding/json at v0.3.5
  • GoJSON is github.com/goccy/go-json at v0.9.7
  • SonicJSON is github.com/bytedance/sonic at v1.3.0

Benchmarks were run across various datasets:

  • CanadaGeometry is a GeoJSON (RFC 7946) representation of Canada. It contains many JSON arrays of arrays of two-element arrays of numbers.
  • CITMCatalog contains many JSON objects using numeric names.
  • SyntheaFHIR is sample JSON data from the healthcare industry. It contains many nested JSON objects with mostly string values, where the set of unique string values is relatively small.
  • TwitterStatus is the JSON response from the Twitter API. It contains a mix of all different JSON kinds, where string values are a mix of both single-byte ASCII and multi-byte Unicode.
  • GolangSource is a simple tree representing the Go source code. It contains many nested JSON objects, each with the same schema.
  • StringUnicode contains many strings with multi-byte Unicode runes.

All of the implementations other than JSONv1 and JSONv2 make extensive use of unsafe. As such, we expect those to generally be faster, but at the cost of memory and type safety. SonicJSON goes a step even further and uses just-in-time compilation to generate machine code specialized for the Go type being marshaled or unmarshaled. Also, SonicJSON does not validate JSON strings for valid UTF-8, and so gains a notable performance boost on datasets with multi-byte Unicode. Benchmarks are performed based on the default marshal and unmarshal behavior of each package. Note that JSONv2 aims to be safe and correct by default, which may not be the most performant strategy.

JSONv2 has several semantic changes relative to JSONv1 that impacts performance:

  1. When marshaling, JSONv2 no longer sorts the keys of a Go map. This will improve performance.
  2. When marshaling or unmarshaling, JSONv2 always checks to make sure JSON object names are unique. This will hurt performance, but is more correct.
  3. When marshaling or unmarshaling, JSONv2 always shallow copies the underlying value for a Go interface and shallow copies the key and value for entries in a Go map. This is done to keep the value as addressable so that JSONv2 can call methods and functions that operate on a pointer receiver. This will hurt performance, but is more correct.

All of the charts are unit-less since the values are normalized relative to JSONv1, which is why JSONv1 always has a value of 1. A lower value is better (i.e., runs faster).

Benchmarks were performed on an AMD Ryzen 9 5900X.

The code for the benchmarks is located at https://github.com/go-json-experiment/jsonbench.

Marshal Performance
Concrete types

Benchmark Marshal Concrete

  • This compares marshal performance when serializing from concrete types.
  • The JSONv1 implementation is close to optimal (without the use of unsafe).
  • Relative to JSONv1, JSONv2 is generally as fast or slightly faster.
  • Relative to JSONIterator, JSONv2 is up to 1.3x faster.
  • Relative to SegmentJSON, JSONv2 is up to 1.8x slower.
  • Relative to GoJSON, JSONv2 is up to 2.0x slower.
  • Relative to SonicJSON, JSONv2 is about 1.8x to 3.2x slower (ignoring StringUnicode since SonicJSON does not validate UTF-8).
  • For JSONv1 and JSONv2, marshaling from concrete types is mostly limited by the performance of Go reflection.
Interface types

Benchmark Marshal Interface

  • This compares marshal performance when serializing from any, map[string]any, and []any types.
  • Relative to JSONv1, JSONv2 is about 1.5x to 4.2x faster.
  • Relative to JSONIterator, JSONv2 is about 1.1x to 2.4x faster.
  • Relative to SegmentJSON, JSONv2 is about 1.2x to 1.8x faster.
  • Relative to GoJSON, JSONv2 is about 1.1x to 2.5x faster.
  • Relative to SonicJSON, JSONv2 is up to 1.5x slower (ignoring StringUnicode since SonicJSON does not validate UTF-8).
  • JSONv2 is faster than the alternatives. One advantange is because it does not sort the keys for a map[string]any, while alternatives (except SonicJSON and JSONIterator) do sort the keys.
RawValue types

Benchmark Marshal Rawvalue

  • This compares performance when marshaling from a json.RawValue. This mostly exercises the underlying encoder and hides the cost of Go reflection.
  • Relative to JSONv1, JSONv2 is about 3.5x to 7.8x faster.
  • JSONIterator is blazingly fast because it does not validate whether the raw value is valid and simply copies it to the output.
  • Relative to SegmentJSON, JSONv2 is about 1.5x to 2.7x faster.
  • Relative to GoJSON, JSONv2 is up to 2.2x faster.
  • Relative to SonicJSON, JSONv2 is up to 1.5x faster.
  • Aside from JSONIterator, JSONv2 is generally the fastest.
Unmarshal Performance
Concrete types

Benchmark Unmarshal Concrete

  • This compares unmarshal performance when deserializing into concrete types.
  • Relative to JSONv1, JSONv2 is about 1.8x to 5.7x faster.
  • Relative to JSONIterator, JSONv2 is about 1.1x to 1.6x slower.
  • Relative to SegmentJSON, JSONv2 is up to 2.5x slower.
  • Relative to GoJSON, JSONv2 is about 1.4x to 2.1x slower.
  • Relative to SonicJSON, JSONv2 is up to 4.0x slower (ignoring StringUnicode since SonicJSON does not validate UTF-8).
  • For JSONv1 and JSONv2, unmarshaling into concrete types is mostly limited by the performance of Go reflection.
Interface types

Benchmark Unmarshal Interface

  • This compares unmarshal performance when deserializing into any, map[string]any, and []any types.
  • Relative to JSONv1, JSONv2 is about 1.tx to 4.3x faster.
  • Relative to JSONIterator, JSONv2 is up to 1.5x faster.
  • Relative to SegmentJSON, JSONv2 is about 1.5 to 3.7x faster.
  • Relative to GoJSON, JSONv2 is up to 1.3x faster.
  • Relative to SonicJSON, JSONv2 is up to 1.5x slower (ignoring StringUnicode since SonicJSON does not validate UTF-8).
  • Aside from SonicJSON, JSONv2 is generally just as fast or faster than all the alternatives.
RawValue types

Benchmark Unmarshal Rawvalue

  • This compares performance when unmarshaling into a json.RawValue. This mostly exercises the underlying decoder and hides away most of the cost of Go reflection.
  • Relative to JSONv1, JSONv2 is about 8.3x to 17.0x faster.
  • Relative to JSONIterator, JSONv2 is up to 2.0x faster.
  • Relative to SegmentJSON, JSONv2 is up to 1.6x faster or 1.7x slower.
  • Relative to GoJSON, JSONv2 is up to 1.9x faster or 2.1x slower.
  • Relative to SonicJSON, JSONv2 is up to 2.0x faster (ignoring StringUnicode since SonicJSON does not validate UTF-8).
  • JSONv1 takes a lexical scanning approach, which performs a virtual function call for every byte of input. In contrast, JSONv2 makes heavy use of iterative and linear parsing logic (with extra complexity to resume parsing when encountering segmented buffers).
  • JSONv2 is comparable to the alternatives that use unsafe. Generally it is faster, but sometimes it is slower.

Documentation

Overview

Package json implements serialization of JSON as specified in RFC 4627, RFC 7159, RFC 7493, RFC 8259, and RFC 8785. JSON is a simple data interchange format that can represent primitive data types such as booleans, strings, and numbers, in addition to structured data types such as objects and arrays.

Terminology

This package uses the terms "encode" and "decode" for syntactic functionality that is concerned with processing JSON based on its grammar, and uses the terms "marshal" and "unmarshal" for semantic functionality that determines the meaning of JSON values as Go values and vice-versa. It aims to provide a clear distinction between functionality that is purely concerned with encoding versus that of marshaling. For example, one can directly encode a stream of JSON tokens without needing to marshal a concrete Go value representing them. Similarly, one can decode a stream of JSON tokens without needing to unmarshal them into a concrete Go value.

This package uses JSON terminology when discussing JSON, which may differ from related concepts in Go or elsewhere in computing literature.

  • A JSON "object" refers to an unordered collection of name/value members.
  • A JSON "array" refers to an ordered sequence of elements.
  • A JSON "value" refers to either a literal (i.e., null, false, or true), string, number, object, or array.

See RFC 8259 for more information.

Specifications

Relevant specifications include RFC 4627, RFC 7159, RFC 7493, RFC 8259, and RFC 8785. Each RFC is generally a stricter subset of another RFC. In increasing order of strictness:

  • RFC 4627 and RFC 7159 do not require (but recommend) the use of UTF-8 and also do not require (but recommend) that object names be unique.
  • RFC 8259 requires the use of UTF-8, but does not require (but recommends) that object names be unique.
  • RFC 7493 requires the use of UTF-8 and also requires that object names be unique.
  • RFC 8785 defines a canonical representation. It requires the use of UTF-8 and also requires that object names be unique and in a specific ordering. It specifies exactly how strings and numbers must be formatted.

The primary difference between RFC 4627 and RFC 7159 is that the former restricted top-level values to only JSON objects and arrays, while RFC 7159 and subsequent RFCs permit top-level values to additionally be JSON nulls, booleans, strings, or numbers.

By default, this package operates on RFC 7493, but can be configured to operate according to the other RFC specifications. RFC 7493 is a stricter subset of RFC 8259 and fully compliant with it. In particular, it makes specific choices about behavior that RFC 8259 leaves as undefined in order to ensure greater interoperability.

JSON Representation of Go structs

A Go struct is naturally represented as a JSON object, where each Go struct field corresponds with a JSON object member. When marshaling, all Go struct fields are recursively encoded in depth-first order as JSON object members except those that are ignored or omitted. When unmarshaling, JSON object members are recursively decoded into the corresponding Go struct fields. Object members that do not match any struct fields, also known as “unknown members”, are ignored by default or rejected if UnmarshalOptions.RejectUnknownMembers is specified.

The representation of each struct field can be customized in the "json" struct field tag, where the tag is a comma separated list of options. As a special case, if the entire tag is `json:"-"`, then the field is ignored with regard to its JSON representation.

The first option is the JSON object name override for the Go struct field. If the name is not specified, then the Go struct field name is used as the JSON object name. JSON names containing commas or quotes, or names identical to "" or "-", can be specified using a single-quoted string literal, where the syntax is identical to the Go grammar for a double-quoted string literal, but instead uses single quotes as the delimiters. By default, unmarshaling uses case-sensitive matching to identify the Go struct field associated with a JSON object name.

After the name, the following tag options are supported:

  • omitzero: When marshaling, the "omitzero" option specifies that the struct field should be omitted if the field value is zero as determined by the "IsZero() bool" method if present, otherwise based on whether the field is the zero Go value. This option has no effect when unmarshaling.

  • omitempty: When marshaling, the "omitempty" option specifies that the struct field should be omitted if the field value would have been encoded as a JSON null, empty string, empty object, or empty array. This option has no effect when unmarshaling.

  • string: The "string" option specifies that MarshalOptions.StringifyNumbers and UnmarshalOptions.StringifyNumbers be set when marshaling or unmarshaling a struct field value. This causes numeric types to be encoded as a JSON number within a JSON string, and to be decoded from either a JSON number or a JSON string containing a JSON number. This extra level of encoding is often necessary since many JSON parsers cannot precisely represent 64-bit integers.

  • nocase: When unmarshaling, the "nocase" option specifies that if the JSON object name does not exactly match the JSON name for any of the struct fields, then it attempts to match the struct field using a case-insensitive match that also ignores dashes and underscores. If multiple fields match, the first declared field in breadth-first order takes precedence. This option has no effect when marshaling.

  • inline: The "inline" option specifies that the JSON representable content of this field type is to be promoted as if they were specified in the parent struct. It is the JSON equivalent of Go struct embedding. A Go embedded field is implicitly inlined unless an explicit JSON name is specified. The inlined field must be a Go struct (that does not implement any JSON methods), RawValue, map[string]T, or an unnamed pointer to such types. When marshaling, inlined fields from a pointer type are omitted if it is nil. Inlined fields of type RawValue and map[string]T are called “inlined fallbacks” as they can represent all possible JSON object members not directly handled by the parent struct. Only one inlined fallback field may be specified in a struct, while many non-fallback fields may be specified. This option must not be specified with any other option (including the JSON name).

  • unknown: The "unknown" option is a specialized variant of the inlined fallback to indicate that this Go struct field contains any number of unknown JSON object members. The field type must be a RawValue, map[string]T, or an unnamed pointer to such types. If MarshalOptions.DiscardUnknownMembers is specified when marshaling, the contents of this field are ignored. If UnmarshalOptions.RejectUnknownMembers is specified when unmarshaling, any unknown object members are rejected regardless of whether an inlined fallback with the "unknown" option exists. This option must not be specified with any other option (including the JSON name).

  • format: The "format" option specifies a format flag used to specialize the formatting of the field value. The option is a key-value pair specified as "format:value" where the value must be either a literal consisting of letters and numbers (e.g., "format:RFC3339") or a single-quoted string literal (e.g., "format:'2006-01-02'"). The interpretation of the format flag is determined by the struct field type.

The "omitzero" and "omitempty" options are mostly semantically identical. The former is defined in terms of the Go type system, while the latter in terms of the JSON type system. Consequently they behave differently in some circumstances. For example, only a nil slice or map is omitted under "omitzero", while an empty slice or map is omitted under "omitempty" regardless of nilness. The "omitzero" option is useful for types with a well-defined zero value (e.g., netip.Addr) or have an IsZero method (e.g., time.Time).

Every Go struct corresponds to a list of JSON representable fields which is constructed by performing a breadth-first search over all struct fields (excluding unexported or ignored fields), where the search recursively descends into inlined structs. The set of non-inlined fields in a struct must have unique JSON names. If multiple fields all have the same JSON name, then the one at shallowest depth takes precedence and the other fields at deeper depths are excluded from the list of JSON representable fields. If multiple fields at the shallowest depth have the same JSON name, then all of those fields are excluded from the list. This is analogous to Go visibility rules for struct field selection with embedded struct types.

Marshaling or unmarshaling a non-empty struct without any JSON representable fields results in a SemanticError. Unexported fields must not have any `json` tags except for `json:"-"`.

Example (CaseSensitivity)

Unmarshal matches JSON object names with Go struct fields using a case-sensitive match, but can be configured to use a case-insensitive match with the "nocase" option. This permits unmarshaling from inputs that use naming conventions such as camelCase, snake_case, or kebab-case.

package main

import (
	"fmt"
	"log"

	"k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"
)

func main() {
	// JSON input using various naming conventions.
	const input = `[
		{"firstname": true},
		{"firstName": true},
		{"FirstName": true},
		{"FIRSTNAME": true},
		{"first_name": true},
		{"FIRST_NAME": true},
		{"first-name": true},
		{"FIRST-NAME": true},
		{"unknown": true}
	]`

	// Without "nocase", Unmarshal looks for an exact match.
	var withcase []struct {
		X bool `json:"firstName"`
	}
	if err := json.Unmarshal([]byte(input), &withcase); err != nil {
		log.Fatal(err)
	}
	fmt.Println(withcase) // exactly 1 match found

	// With "nocase", Unmarshal looks first for an exact match,
	// then for a case-insensitive match if none found.
	var nocase []struct {
		X bool `json:"firstName,nocase"`
	}
	if err := json.Unmarshal([]byte(input), &nocase); err != nil {
		log.Fatal(err)
	}
	fmt.Println(nocase) // 8 matches found

}
Output:

[{false} {true} {false} {false} {false} {false} {false} {false} {false}]
[{true} {true} {true} {true} {true} {true} {true} {true} {false}]
Example (FieldNames)

By default, JSON object names for Go struct fields are derived from the Go field name, but may be specified in the `json` tag. Due to JSON's heritage in JavaScript, the most common naming convention used for JSON object names is camelCase.

package main

import (
	"fmt"
	"log"

	"k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"
)

func main() {
	var value struct {
		// This field is explicitly ignored with the special "-" name.
		Ignored any `json:"-"`
		// No JSON name is not provided, so the Go field name is used.
		GoName any
		// A JSON name is provided without any special characters.
		JSONName any `json:"jsonName"`
		// No JSON name is not provided, so the Go field name is used.
		Option any `json:",nocase"`
		// An empty JSON name specified using an single-quoted string literal.
		Empty any `json:"''"`
		// A dash JSON name specified using an single-quoted string literal.
		Dash any `json:"'-'"`
		// A comma JSON name specified using an single-quoted string literal.
		Comma any `json:"','"`
		// JSON name with quotes specified using a single-quoted string literal.
		Quote any `json:"'\"\\''"`
		// An unexported field is always ignored.
		unexported any
	}

	b, err := json.Marshal(value)
	if err != nil {
		log.Fatal(err)
	}
	(*json.RawValue)(&b).Indent("", "\t") // indent for readability
	fmt.Println(string(b))

}
Output:

{
	"GoName": null,
	"jsonName": null,
	"Option": null,
	"": null,
	"-": null,
	",": null,
	"\"'": null
}
Example (FormatFlags)

The "format" tag option can be used to alter the formatting of certain types.

package main

import (
	"fmt"
	"log"
	"math"
	"time"

	"k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"
)

func main() {
	value := struct {
		BytesBase64    []byte         `json:",format:base64"`
		BytesHex       [8]byte        `json:",format:hex"`
		BytesArray     []byte         `json:",format:array"`
		FloatNonFinite float64        `json:",format:nonfinite"`
		MapEmitNull    map[string]any `json:",format:emitnull"`
		SliceEmitNull  []any          `json:",format:emitnull"`
		TimeDateOnly   time.Time      `json:",format:'2006-01-02'"`
		DurationNanos  time.Duration  `json:",format:nanos"`
	}{
		BytesBase64:    []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef},
		BytesHex:       [8]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef},
		BytesArray:     []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef},
		FloatNonFinite: math.NaN(),
		MapEmitNull:    nil,
		SliceEmitNull:  nil,
		TimeDateOnly:   time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
		DurationNanos:  time.Second + time.Millisecond + time.Microsecond + time.Nanosecond,
	}

	b, err := json.Marshal(&value)
	if err != nil {
		log.Fatal(err)
	}
	(*json.RawValue)(&b).Indent("", "\t") // indent for readability
	fmt.Println(string(b))

}
Output:

{
	"BytesBase64": "ASNFZ4mrze8=",
	"BytesHex": "0123456789abcdef",
	"BytesArray": [
		1,
		35,
		69,
		103,
		137,
		171,
		205,
		239
	],
	"FloatNonFinite": "NaN",
	"MapEmitNull": null,
	"SliceEmitNull": null,
	"TimeDateOnly": "2000-01-01",
	"DurationNanos": 1001001001
}
Example (InlinedFields)

JSON objects can be inlined within a parent object similar to how Go structs can be embedded within a parent struct. The inlining rules are similar to those of Go embedding, but operates upon the JSON namespace.

package main

import (
	"fmt"
	"log"
	"time"

	"k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"
)

func main() {
	// Base is embedded within Container.
	type Base struct {
		// ID is promoted into the JSON object for Container.
		ID string
		// Type is ignored due to presence of Container.Type.
		Type string
		// Time cancels out with Container.Inlined.Time.
		Time time.Time
	}
	// Other is embedded within Container.
	type Other struct{ Cost float64 }
	// Container embeds Base and Other.
	type Container struct {
		// Base is an embedded struct and is implicitly JSON inlined.
		Base
		// Type takes precedence over Base.Type.
		Type int
		// Inlined is a named Go field, but is explicitly JSON inlined.
		Inlined struct {
			// User is promoted into the JSON object for Container.
			User string
			// Time cancels out with Base.Time.
			Time string
		} `json:",inline"`
		// ID does not conflict with Base.ID since the JSON name is different.
		ID string `json:"uuid"`
		// Other is not JSON inlined since it has an explicit JSON name.
		Other `json:"other"`
	}

	// Format an empty Container to show what fields are JSON serializable.
	var input Container
	b, err := json.Marshal(&input)
	if err != nil {
		log.Fatal(err)
	}
	(*json.RawValue)(&b).Indent("", "\t") // indent for readability
	fmt.Println(string(b))

}
Output:

{
	"ID": "",
	"Type": 0,
	"User": "",
	"uuid": "",
	"other": {
		"Cost": 0
	}
}
Example (OmitFields)

Go struct fields can be omitted from the output depending on either the input Go value or the output JSON encoding of the value. The "omitzero" option omits a field if it is the zero Go value or implements a "IsZero() bool" method that reports true. The "omitempty" option omits a field if it encodes as an empty JSON value, which we define as a JSON null or empty JSON string, object, or array. In many cases, the behavior of "omitzero" and "omitempty" are equivalent. If both provide the desired effect, then using "omitzero" is preferred.

package main

import (
	"fmt"
	"log"
	"net/netip"
	"time"

	"k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"
)

func main() {
	type MyStruct struct {
		Foo string `json:",omitzero"`
		Bar []int  `json:",omitempty"`
		// Both "omitzero" and "omitempty" can be specified together,
		// in which case the field is omitted if either would take effect.
		// This omits the Baz field either if it is a nil pointer or
		// if it would have encoded as an empty JSON object.
		Baz *MyStruct `json:",omitzero,omitempty"`
	}

	// Demonstrate behavior of "omitzero".
	b, err := json.Marshal(struct {
		Bool         bool        `json:",omitzero"`
		Int          int         `json:",omitzero"`
		String       string      `json:",omitzero"`
		Time         time.Time   `json:",omitzero"`
		Addr         netip.Addr  `json:",omitzero"`
		Struct       MyStruct    `json:",omitzero"`
		SliceNil     []int       `json:",omitzero"`
		Slice        []int       `json:",omitzero"`
		MapNil       map[int]int `json:",omitzero"`
		Map          map[int]int `json:",omitzero"`
		PointerNil   *string     `json:",omitzero"`
		Pointer      *string     `json:",omitzero"`
		InterfaceNil any         `json:",omitzero"`
		Interface    any         `json:",omitzero"`
	}{
		// Bool is omitted since false is the zero value for a Go bool.
		Bool: false,
		// Int is omitted since 0 is the zero value for a Go int.
		Int: 0,
		// String is omitted since "" is the zero value for a Go string.
		String: "",
		// Time is omitted since time.Time.IsZero reports true.
		Time: time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC),
		// Addr is omitted since netip.Addr{} is the zero value for a Go struct.
		Addr: netip.Addr{},
		// Struct is NOT omitted since it is not the zero value for a Go struct.
		Struct: MyStruct{Bar: []int{}, Baz: new(MyStruct)},
		// SliceNil is omitted since nil is the zero value for a Go slice.
		SliceNil: nil,
		// Slice is NOT omitted since []int{} is not the zero value for a Go slice.
		Slice: []int{},
		// MapNil is omitted since nil is the zero value for a Go map.
		MapNil: nil,
		// Map is NOT omitted since map[int]int{} is not the zero value for a Go map.
		Map: map[int]int{},
		// PointerNil is omitted since nil is the zero value for a Go pointer.
		PointerNil: nil,
		// Pointer is NOT omitted since new(string) is not the zero value for a Go pointer.
		Pointer: new(string),
		// InterfaceNil is omitted since nil is the zero value for a Go interface.
		InterfaceNil: nil,
		// Interface is NOT omitted since (*string)(nil) is not the zero value for a Go interface.
		Interface: (*string)(nil),
	})
	if err != nil {
		log.Fatal(err)
	}
	(*json.RawValue)(&b).Indent("", "\t") // indent for readability
	fmt.Println("OmitZero:", string(b))   // outputs "Struct", "Slice", "Map", "Pointer", and "Interface"

	// Demonstrate behavior of "omitempty".
	b, err = json.Marshal(struct {
		Bool         bool        `json:",omitempty"`
		Int          int         `json:",omitempty"`
		String       string      `json:",omitempty"`
		Time         time.Time   `json:",omitempty"`
		Addr         netip.Addr  `json:",omitempty"`
		Struct       MyStruct    `json:",omitempty"`
		Slice        []int       `json:",omitempty"`
		Map          map[int]int `json:",omitempty"`
		PointerNil   *string     `json:",omitempty"`
		Pointer      *string     `json:",omitempty"`
		InterfaceNil any         `json:",omitempty"`
		Interface    any         `json:",omitempty"`
	}{
		// Bool is NOT omitted since false is not an empty JSON value.
		Bool: false,
		// Int is NOT omitted since 0 is not a empty JSON value.
		Int: 0,
		// String is omitted since "" is an empty JSON string.
		String: "",
		// Time is NOT omitted since this encodes as a non-empty JSON string.
		Time: time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC),
		// Addr is omitted since this encodes as an empty JSON string.
		Addr: netip.Addr{},
		// Struct is omitted since {} is an empty JSON object.
		Struct: MyStruct{Bar: []int{}, Baz: new(MyStruct)},
		// Slice is omitted since [] is an empty JSON array.
		Slice: []int{},
		// Map is omitted since {} is an empty JSON object.
		Map: map[int]int{},
		// PointerNil is ommited since null is an empty JSON value.
		PointerNil: nil,
		// Pointer is omitted since "" is an empty JSON string.
		Pointer: new(string),
		// InterfaceNil is omitted since null is an empty JSON value.
		InterfaceNil: nil,
		// Interface is omitted since null is an empty JSON value.
		Interface: (*string)(nil),
	})
	if err != nil {
		log.Fatal(err)
	}
	(*json.RawValue)(&b).Indent("", "\t") // indent for readability
	fmt.Println("OmitEmpty:", string(b))  // outputs "Bool", "Int", and "Time"

}
Output:

OmitZero: {
	"Struct": {},
	"Slice": [],
	"Map": {},
	"Pointer": "",
	"Interface": null
}
OmitEmpty: {
	"Bool": false,
	"Int": 0,
	"Time": "0001-01-01T00:00:00Z"
}
Example (OrderedObject)

The exact order of JSON object can be preserved through the use of a specialized type that implements MarshalerV2 and UnmarshalerV2.

package main

import (
	"fmt"
	"log"
	"reflect"

	"k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"
)

// OrderedObject is an ordered sequence of name/value members in a JSON object.
//
// RFC 8259 defines an object as an "unordered collection".
// JSON implementations need not make "ordering of object members visible"
// to applications nor will they agree on the semantic meaning of an object if
// "the names within an object are not unique". For maximum compatibility,
// applications should avoid relying on ordering or duplicity of object names.
type OrderedObject[V any] []ObjectMember[V]

// ObjectMember is a JSON object member.
type ObjectMember[V any] struct {
	Name  string
	Value V
}

// MarshalNextJSON encodes obj as a JSON object into enc.
func (obj *OrderedObject[V]) MarshalNextJSON(opts json.MarshalOptions, enc *json.Encoder) error {
	if err := enc.WriteToken(json.ObjectStart); err != nil {
		return err
	}
	for i := range *obj {
		member := &(*obj)[i]
		if err := opts.MarshalNext(enc, &member.Name); err != nil {
			return err
		}
		if err := opts.MarshalNext(enc, &member.Value); err != nil {
			return err
		}
	}
	if err := enc.WriteToken(json.ObjectEnd); err != nil {
		return err
	}
	return nil
}

// UnmarshalNextJSON decodes a JSON object from dec into obj.
func (obj *OrderedObject[V]) UnmarshalNextJSON(opts json.UnmarshalOptions, dec *json.Decoder) error {
	if k := dec.PeekKind(); k != '{' {
		return fmt.Errorf("expected object start, but encountered %v", k)
	}
	if _, err := dec.ReadToken(); err != nil {
		return err
	}
	for dec.PeekKind() != '}' {
		*obj = append(*obj, ObjectMember[V]{})
		member := &(*obj)[len(*obj)-1]
		if err := opts.UnmarshalNext(dec, &member.Name); err != nil {
			return err
		}
		if err := opts.UnmarshalNext(dec, &member.Value); err != nil {
			return err
		}
	}
	if _, err := dec.ReadToken(); err != nil {
		return err
	}
	return nil
}

// The exact order of JSON object can be preserved through the use of a
// specialized type that implements MarshalerV2 and UnmarshalerV2.
func main() {
	// Round-trip marshal and unmarshal an ordered object.
	// We expect the order and duplicity of JSON object members to be preserved.
	want := OrderedObject[string]{
		{"fizz", "buzz"},
		{"hello", "world"},
		{"fizz", "wuzz"},
	}
	b, err := json.MarshalOptions{}.Marshal(json.EncodeOptions{
		AllowDuplicateNames: true, // since the object contains "fizz" twice
	}, &want)
	if err != nil {
		log.Fatal(err)
	}
	var got OrderedObject[string]
	err = json.UnmarshalOptions{}.Unmarshal(json.DecodeOptions{
		AllowDuplicateNames: true, // since the object contains "fizz" twice
	}, b, &got)
	if err != nil {
		log.Fatal(err)
	}

	// Sanity check.
	if !reflect.DeepEqual(got, want) {
		log.Fatalf("roundtrip mismatch: got %v, want %v", got, want)
	}

	// Print the serialized JSON object.
	(*json.RawValue)(&b).Indent("", "\t") // indent for readability
	fmt.Println(string(b))

}
Output:

{
	"fizz": "buzz",
	"hello": "world",
	"fizz": "wuzz"
}
Example (ProtoJSON)

Some Go types have a custom JSON represention where the implementation is delegated to some external package. Consequentely, the "json" package will not know how to use that external implementation. For example, the "google.golang.org/protobuf/encoding/protojson" package implements JSON for all "google.golang.org/protobuf/proto".Message types. MarshalOptions.Marshalers and UnmarshalOptions.Unmarshalers can be used to configure "json" and "protojson" to cooperate together.

package main

import (
	"log"

	"k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"
)

func main() {
	// Let protoMessage be "google.golang.org/protobuf/proto".Message.
	type protoMessage interface{ ProtoReflect() }
	// Let foopbMyMessage be a concrete implementation of proto.Message.
	type foopbMyMessage struct{ protoMessage }
	// Let protojson be an import of "google.golang.org/protobuf/encoding/protojson".
	var protojson struct {
		Marshal   func(protoMessage) ([]byte, error)
		Unmarshal func([]byte, protoMessage) error
	}

	// This value mixes both non-proto.Message types and proto.Message types.
	// It should use the "json" package to handle non-proto.Message types and
	// should use the "protojson" package to handle proto.Message types.
	var value struct {
		// GoStruct does not implement proto.Message and
		// should use the default behavior of the "json" package.
		GoStruct struct {
			Name string
			Age  int
		}

		// ProtoMessage implements proto.Message and
		// should be handled using protojson.Marshal.
		ProtoMessage *foopbMyMessage
	}

	// Marshal using protojson.Marshal for proto.Message types.
	b, err := json.MarshalOptions{
		// Use protojson.Marshal as a type-specific marshaler.
		Marshalers: json.MarshalFuncV1(protojson.Marshal),
	}.Marshal(json.EncodeOptions{}, &value)
	if err != nil {
		log.Fatal(err)
	}

	// Unmarshal using protojson.Unmarshal for proto.Message types.
	err = json.UnmarshalOptions{
		// Use protojson.Unmarshal as a type-specific unmarshaler.
		Unmarshalers: json.UnmarshalFuncV1(protojson.Unmarshal),
	}.Unmarshal(json.DecodeOptions{}, b, &value)
	if err != nil {
		log.Fatal(err)
	}
}
Output:

Example (ServeHTTP)

When implementing HTTP endpoints, it is common to be operating with an io.Reader and an io.Writer. The UnmarshalFull and MarshalFull functions assist in operating on such input/output types. UnmarshalFull reads the entirety of the io.Reader to ensure that io.EOF is encountered without any unexpected bytes after the top-level JSON value.

package main

import (
	"net/http"
	"sync/atomic"

	"k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"
)

func main() {
	// Some global state maintained by the server.
	var n int64

	// The "add" endpoint accepts a POST request with a JSON object
	// containing a number to atomically add to the server's global counter.
	// It returns the updated value of the counter.
	http.HandleFunc("/api/add", func(w http.ResponseWriter, r *http.Request) {
		// Unmarshal the request from the client.
		var val struct{ N int64 }
		if err := json.UnmarshalFull(r.Body, &val); err != nil {
			// Inability to unmarshal the input suggests a client-side problem.
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		// Marshal a response from the server.
		val.N = atomic.AddInt64(&n, val.N)
		if err := json.MarshalFull(w, &val); err != nil {
			// Inability to marshal the output suggests a server-side problem.
			// This error is not always observable by the client since
			// json.MarshalFull may have already written to the output.
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	})
}
Output:

Example (StringReplace)

This example demonstrates the use of the Encoder and Decoder to parse and modify JSON without unmarshaling it into a concrete Go type.

package main

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"strings"

	"k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"
)

func main() {
	// Example input with non-idiomatic use of "Golang" instead of "Go".
	const input = `{
		"title": "Golang version 1 is released",
		"author": "Andrew Gerrand",
		"date": "2012-03-28",
		"text": "Today marks a major milestone in the development of the Golang programming language.",
		"otherArticles": [
			"Twelve Years of Golang",
			"The Laws of Reflection",
			"Learn Golang from your browser"
		]
	}`

	// Using a Decoder and Encoder, we can parse through every token,
	// check and modify the token if necessary, and
	// write the token to the output.
	var replacements []string
	in := strings.NewReader(input)
	dec := json.NewDecoder(in)
	out := new(bytes.Buffer)
	enc := json.EncodeOptions{Indent: "\t"}.NewEncoder(out) // indent for readability
	for {
		// Read a token from the input.
		tok, err := dec.ReadToken()
		if err != nil {
			if err == io.EOF {
				break
			}
			log.Fatal(err)
		}

		// Check whether the token contains the string "Golang" and
		// replace each occurence with "Go" instead.
		if tok.Kind() == '"' && strings.Contains(tok.String(), "Golang") {
			replacements = append(replacements, dec.StackPointer())
			tok = json.String(strings.ReplaceAll(tok.String(), "Golang", "Go"))
		}

		// Write the (possibly modified) token to the output.
		if err := enc.WriteToken(tok); err != nil {
			log.Fatal(err)
		}
	}

	// Print the list of replacements and the adjusted JSON output.
	if len(replacements) > 0 {
		fmt.Println(`Replaced "Golang" with "Go" in:`)
		for _, where := range replacements {
			fmt.Println("\t" + where)
		}
		fmt.Println()
	}
	fmt.Println("Result:", out.String())

}
Output:

Replaced "Golang" with "Go" in:
	/title
	/text
	/otherArticles/0
	/otherArticles/2

Result: {
	"title": "Go version 1 is released",
	"author": "Andrew Gerrand",
	"date": "2012-03-28",
	"text": "Today marks a major milestone in the development of the Go programming language.",
	"otherArticles": [
		"Twelve Years of Go",
		"The Laws of Reflection",
		"Learn Go from your browser"
	]
}
Example (TextMarshal)

If a type implements encoding.TextMarshaler and/or encoding.TextUnmarshaler, then the MarshalText and UnmarshalText methods are used to encode/decode the value to/from a JSON string.

package main

import (
	"fmt"
	"log"
	"net/netip"
	"reflect"

	"k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"
)

func main() {
	// Round-trip marshal and unmarshal a hostname map where the netip.Addr type
	// implements both encoding.TextMarshaler and encoding.TextUnmarshaler.
	want := map[netip.Addr]string{
		netip.MustParseAddr("192.168.0.100"): "carbonite",
		netip.MustParseAddr("192.168.0.101"): "obsidian",
		netip.MustParseAddr("192.168.0.102"): "diamond",
	}
	b, err := json.Marshal(&want)
	if err != nil {
		log.Fatal(err)
	}
	var got map[netip.Addr]string
	err = json.Unmarshal(b, &got)
	if err != nil {
		log.Fatal(err)
	}

	// Sanity check.
	if !reflect.DeepEqual(got, want) {
		log.Fatalf("roundtrip mismatch: got %v, want %v", got, want)
	}

	// Print the serialized JSON object. Canonicalize the JSON first since
	// Go map entries are not serialized in a deterministic order.
	(*json.RawValue)(&b).Canonicalize()
	(*json.RawValue)(&b).Indent("", "\t") // indent for readability
	fmt.Println(string(b))

}
Output:

{
	"192.168.0.100": "carbonite",
	"192.168.0.101": "obsidian",
	"192.168.0.102": "diamond"
}
Example (UnknownMembers)

Due to version skew, the set of JSON object members known at compile-time may differ from the set of members encountered at execution-time. As such, it may be useful to have finer grain handling of unknown members. This package supports preserving, rejecting, or discarding such members.

package main

import (
	"errors"
	"fmt"
	"log"

	"k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"
)

func main() {
	const input = `{
		"Name": "Teal",
		"Value": "#008080",
		"WebSafe": false
	}`
	type Color struct {
		Name  string
		Value string

		// Unknown is a Go struct field that holds unknown JSON object members.
		// It is marked as having this behavior with the "unknown" tag option.
		//
		// The type may be a RawValue or map[string]T.
		Unknown json.RawValue `json:",unknown"`
	}

	// By default, unknown members are stored in a Go field marked as "unknown"
	// or ignored if no such field exists.
	var color Color
	err := json.Unmarshal([]byte(input), &color)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("Unknown members:", string(color.Unknown))

	// Specifying UnmarshalOptions.RejectUnknownMembers causes
	// Unmarshal to reject the presence of any unknown members.
	err = json.UnmarshalOptions{
		RejectUnknownMembers: true,
	}.Unmarshal(json.DecodeOptions{}, []byte(input), new(Color))
	if err != nil {
		fmt.Println("Unmarshal error:", errors.Unwrap(err))
	}

	// By default, Marshal preserves unknown members stored in
	// a Go struct field marked as "unknown".
	b, err := json.Marshal(color)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("Output with unknown members:   ", string(b))

	// Specifying MarshalOptions.DiscardUnknownMembers causes
	// Marshal to discard any unknown members.
	b, err = json.MarshalOptions{
		DiscardUnknownMembers: true,
	}.Marshal(json.EncodeOptions{}, color)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("Output without unknown members:", string(b))

}
Output:

Unknown members: {"WebSafe":false}
Unmarshal error: unknown name "WebSafe"
Output with unknown members:    {"Name":"Teal","Value":"#008080","WebSafe":false}
Output without unknown members: {"Name":"Teal","Value":"#008080"}

Index

Examples

Constants

View Source
const Error = jsonError("json error")

Error matches errors returned by this package according to errors.Is.

View Source
const SkipFunc = jsonError("skip function")

SkipFunc may be returned by MarshalFuncV2 and UnmarshalFuncV2 functions.

Any function that returns SkipFunc must not cause observable side effects on the provided Encoder or Decoder. For example, it is permissible to call Decoder.PeekKind, but not permissible to call Decoder.ReadToken or Encoder.WriteToken since such methods mutate the state.

Variables

This section is empty.

Functions

func Marshal

func Marshal(in any) (out []byte, err error)

Marshal serializes a Go value as a []byte with default options. It is a thin wrapper over MarshalOptions.Marshal.

func MarshalFull

func MarshalFull(out io.Writer, in any) error

MarshalFull serializes a Go value into an io.Writer with default options. It is a thin wrapper over MarshalOptions.MarshalFull.

func Unmarshal

func Unmarshal(in []byte, out any) error

Unmarshal deserializes a Go value from a []byte with default options. It is a thin wrapper over UnmarshalOptions.Unmarshal.

func UnmarshalFull

func UnmarshalFull(in io.Reader, out any) error

UnmarshalFull deserializes a Go value from an io.Reader with default options. It is a thin wrapper over UnmarshalOptions.UnmarshalFull.

Types

type DecodeOptions

type DecodeOptions struct {

	// AllowDuplicateNames specifies that JSON objects may contain
	// duplicate member names. Disabling the duplicate name check may provide
	// computational and performance benefits, but breaks compliance with
	// RFC 7493, section 2.3. The input will still be compliant with RFC 8259,
	// which leaves the handling of duplicate names as unspecified behavior.
	AllowDuplicateNames bool

	// AllowInvalidUTF8 specifies that JSON strings may contain invalid UTF-8,
	// which will be mangled as the Unicode replacement character, U+FFFD.
	// This causes the decoder to break compliance with
	// RFC 7493, section 2.1, and RFC 8259, section 8.1.
	AllowInvalidUTF8 bool
	// contains filtered or unexported fields
}

DecodeOptions configures how JSON decoding operates. The zero value is equivalent to the default settings, which is compliant with both RFC 7493 and RFC 8259.

func (DecodeOptions) NewDecoder

func (o DecodeOptions) NewDecoder(r io.Reader) *Decoder

NewDecoder constructs a new streaming decoder reading from r configured with the provided options.

func (DecodeOptions) ResetDecoder

func (o DecodeOptions) ResetDecoder(d *Decoder, r io.Reader)

ResetDecoder resets a decoder such that it is reading afresh from r and configured with the provided options.

type Decoder

type Decoder struct {
	// contains filtered or unexported fields
}

Decoder is a streaming decoder for raw JSON tokens and values. It is used to read a stream of top-level JSON values, each separated by optional whitespace characters.

ReadToken and ReadValue calls may be interleaved. For example, the following JSON value:

{"name":"value","array":[null,false,true,3.14159],"object":{"k":"v"}}

can be parsed with the following calls (ignoring errors for brevity):

d.ReadToken() // {
d.ReadToken() // "name"
d.ReadToken() // "value"
d.ReadValue() // "array"
d.ReadToken() // [
d.ReadToken() // null
d.ReadToken() // false
d.ReadValue() // true
d.ReadToken() // 3.14159
d.ReadToken() // ]
d.ReadValue() // "object"
d.ReadValue() // {"k":"v"}
d.ReadToken() // }

The above is one of many possible sequence of calls and may not represent the most sensible method to call for any given token/value. For example, it is probably more common to call ReadToken to obtain a string token for object names.

func NewDecoder

func NewDecoder(r io.Reader) *Decoder

NewDecoder constructs a new streaming decoder reading from r.

If r is a bytes.Buffer, then the decoder parses directly from the buffer without first copying the contents to an intermediate buffer. Additional writes to the buffer must not occur while the decoder is in use.

func (*Decoder) InputOffset

func (d *Decoder) InputOffset() int64

InputOffset returns the current input byte offset. It gives the location of the next byte immediately after the most recently returned token or value. The number of bytes actually read from the underlying io.Reader may be more than this offset due to internal buffering effects.

func (*Decoder) PeekKind

func (d *Decoder) PeekKind() Kind

PeekKind retrieves the next token kind, but does not advance the read offset. It returns 0 if there are no more tokens.

func (*Decoder) ReadToken

func (d *Decoder) ReadToken() (Token, error)

ReadToken reads the next Token, advancing the read offset. The returned token is only valid until the next Peek, Read, or Skip call. It returns io.EOF if there are no more tokens.

func (*Decoder) ReadValue

func (d *Decoder) ReadValue() (RawValue, error)

ReadValue returns the next raw JSON value, advancing the read offset. The value is stripped of any leading or trailing whitespace. The returned value is only valid until the next Peek, Read, or Skip call and may not be mutated while the Decoder remains in use. If the decoder is currently at the end token for an object or array, then it reports a SyntacticError and the internal state remains unchanged. It returns io.EOF if there are no more values.

func (*Decoder) Reset

func (d *Decoder) Reset(r io.Reader)

Reset resets a decoder such that it is reading afresh from r but keep any pre-existing decoder options.

func (*Decoder) SkipValue

func (d *Decoder) SkipValue() error

SkipValue is semantically equivalent to calling ReadValue and discarding the result except that memory is not wasted trying to hold the entire result.

func (*Decoder) StackDepth

func (d *Decoder) StackDepth() int

StackDepth returns the depth of the state machine for read JSON data. Each level on the stack represents a nested JSON object or array. It is incremented whenever an ObjectStart or ArrayStart token is encountered and decremented whenever an ObjectEnd or ArrayEnd token is encountered. The depth is zero-indexed, where zero represents the top-level JSON value.

func (*Decoder) StackIndex

func (d *Decoder) StackIndex(i int) (Kind, int)

StackIndex returns information about the specified stack level. It must be a number between 0 and StackDepth, inclusive. For each level, it reports the kind:

  • 0 for a level of zero,
  • '{' for a level representing a JSON object, and
  • '[' for a level representing a JSON array.

It also reports the length of that JSON object or array. Each name and value in a JSON object is counted separately, so the effective number of members would be half the length. A complete JSON object must have an even length.

func (*Decoder) StackPointer

func (d *Decoder) StackPointer() string

StackPointer returns a JSON Pointer (RFC 6901) to the most recently read value. Object names are only present if AllowDuplicateNames is false, otherwise object members are represented using their index within the object.

func (*Decoder) UnreadBuffer

func (d *Decoder) UnreadBuffer() []byte

UnreadBuffer returns the data remaining in the unread buffer, which may contain zero or more bytes. The returned buffer must not be mutated while Decoder continues to be used. The buffer contents are valid until the next Peek, Read, or Skip call.

type EncodeOptions

type EncodeOptions struct {

	// AllowDuplicateNames specifies that JSON objects may contain
	// duplicate member names. Disabling the duplicate name check may provide
	// performance benefits, but breaks compliance with RFC 7493, section 2.3.
	// The output will still be compliant with RFC 8259,
	// which leaves the handling of duplicate names as unspecified behavior.
	AllowDuplicateNames bool

	// AllowInvalidUTF8 specifies that JSON strings may contain invalid UTF-8,
	// which will be mangled as the Unicode replacement character, U+FFFD.
	// This causes the encoder to break compliance with
	// RFC 7493, section 2.1, and RFC 8259, section 8.1.
	AllowInvalidUTF8 bool

	// EscapeRune reports whether the provided character should be escaped
	// as a hexadecimal Unicode codepoint (e.g., \ufffd).
	// If nil, the shortest and simplest encoding will be used,
	// which is also the formatting specified by RFC 8785, section 3.2.2.2.
	EscapeRune func(rune) bool

	// Indent (if non-empty) specifies that the encoder should emit multiline
	// output where each element in a JSON object or array begins on a new,
	// indented line beginning with the indent prefix followed by one or more
	// copies of indent according to the indentation nesting.
	// It may only be composed of space or tab characters.
	Indent string

	// IndentPrefix is prepended to each line within a JSON object or array.
	// The purpose of the indent prefix is to encode data that can more easily
	// be embedded inside other formatted JSON data.
	// It may only be composed of space or tab characters.
	// It is ignored if Indent is empty.
	IndentPrefix string
	// contains filtered or unexported fields
}

EncodeOptions configures how JSON encoding operates. The zero value is equivalent to the default settings, which is compliant with both RFC 7493 and RFC 8259.

Example (EscapeHTML)

Directly embedding JSON within HTML requires special handling for safety. Escape certain runes to prevent JSON directly treated as HTML from being able to perform <script> injection.

This example shows how to obtain equivalent behavior provided by the "encoding/json" package that is no longer directly supported by this package. Newly written code that intermix JSON and HTML should instead be using the "github.com/google/safehtml" module for safety purposes.

package main

import (
	"fmt"
	"log"

	"k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"
)

func main() {
	page := struct {
		Title string
		Body  string
	}{
		Title: "Example Embedded Javascript",
		Body:  `<script> console.log("Hello, world!"); </script>`,
	}

	b, err := json.MarshalOptions{}.Marshal(json.EncodeOptions{
		// Escape certain runes within a JSON string so that
		// JSON will be safe to directly embed inside HTML.
		EscapeRune: func(r rune) bool {
			switch r {
			case '&', '<', '>', '\u2028', '\u2029':
				return true
			default:
				return false
			}
		},
		// Indent the output for readability.
		Indent: "\t",
	}, &page)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(string(b))

}
Output:

{
	"Title": "Example Embedded Javascript",
	"Body": "\u003cscript\u003e console.log(\"Hello, world!\"); \u003c/script\u003e"
}

func (EncodeOptions) NewEncoder

func (o EncodeOptions) NewEncoder(w io.Writer) *Encoder

NewEncoder constructs a new streaming encoder writing to w configured with the provided options. It flushes the internal buffer when the buffer is sufficiently full or when a top-level value has been written.

If w is a bytes.Buffer, then the encoder appends directly into the buffer without copying the contents from an intermediate buffer.

func (EncodeOptions) ResetEncoder

func (o EncodeOptions) ResetEncoder(e *Encoder, w io.Writer)

ResetEncoder resets an encoder such that it is writing afresh to w and configured with the provided options.

type Encoder

type Encoder struct {
	// contains filtered or unexported fields
}

Encoder is a streaming encoder from raw JSON tokens and values. It is used to write a stream of top-level JSON values, each terminated with a newline character.

WriteToken and WriteValue calls may be interleaved. For example, the following JSON value:

{"name":"value","array":[null,false,true,3.14159],"object":{"k":"v"}}

can be composed with the following calls (ignoring errors for brevity):

e.WriteToken(ObjectStart)           // {
e.WriteToken(String("name"))        // "name"
e.WriteToken(String("value"))       // "value"
e.WriteValue(RawValue(`"array"`))   // "array"
e.WriteToken(ArrayStart)            // [
e.WriteToken(Null)                  // null
e.WriteToken(False)                 // false
e.WriteValue(RawValue("true"))      // true
e.WriteToken(Float(3.14159))        // 3.14159
e.WriteToken(ArrayEnd)              // ]
e.WriteValue(RawValue(`"object"`))  // "object"
e.WriteValue(RawValue(`{"k":"v"}`)) // {"k":"v"}
e.WriteToken(ObjectEnd)             // }

The above is one of many possible sequence of calls and may not represent the most sensible method to call for any given token/value. For example, it is probably more common to call WriteToken with a string for object names.

func NewEncoder

func NewEncoder(w io.Writer) *Encoder

NewEncoder constructs a new streaming encoder writing to w.

func (*Encoder) OutputOffset

func (e *Encoder) OutputOffset() int64

OutputOffset returns the current output byte offset. It gives the location of the next byte immediately after the most recently written token or value. The number of bytes actually written to the underlying io.Writer may be less than this offset due to internal buffering effects.

func (*Encoder) Reset

func (e *Encoder) Reset(w io.Writer)

Reset resets an encoder such that it is writing afresh to w but keeps any pre-existing encoder options.

func (*Encoder) StackDepth

func (e *Encoder) StackDepth() int

StackDepth returns the depth of the state machine for written JSON data. Each level on the stack represents a nested JSON object or array. It is incremented whenever an ObjectStart or ArrayStart token is encountered and decremented whenever an ObjectEnd or ArrayEnd token is encountered. The depth is zero-indexed, where zero represents the top-level JSON value.

func (*Encoder) StackIndex

func (e *Encoder) StackIndex(i int) (Kind, int)

StackIndex returns information about the specified stack level. It must be a number between 0 and StackDepth, inclusive. For each level, it reports the kind:

  • 0 for a level of zero,
  • '{' for a level representing a JSON object, and
  • '[' for a level representing a JSON array.

It also reports the length of that JSON object or array. Each name and value in a JSON object is counted separately, so the effective number of members would be half the length. A complete JSON object must have an even length.

func (*Encoder) StackPointer

func (e *Encoder) StackPointer() string

StackPointer returns a JSON Pointer (RFC 6901) to the most recently written value. Object names are only present if AllowDuplicateNames is false, otherwise object members are represented using their index within the object.

func (*Encoder) UnusedBuffer

func (e *Encoder) UnusedBuffer() []byte

UnusedBuffer returns a zero-length buffer with a possible non-zero capacity. This buffer is intended to be used to populate a RawValue being passed to an immediately succeeding WriteValue call.

Example usage:

b := d.UnusedBuffer()
b = append(b, '"')
b = appendString(b, v) // append the string formatting of v
b = append(b, '"')
... := d.WriteValue(b)

It is the user's responsibility to ensure that the value is valid JSON.

func (*Encoder) WriteToken

func (e *Encoder) WriteToken(t Token) error

WriteToken writes the next token and advances the internal write offset.

The provided token kind must be consistent with the JSON grammar. For example, it is an error to provide a number when the encoder is expecting an object name (which is always a string), or to provide an end object delimiter when the encoder is finishing an array. If the provided token is invalid, then it reports a SyntacticError and the internal state remains unchanged.

func (*Encoder) WriteValue

func (e *Encoder) WriteValue(v RawValue) error

WriteValue writes the next raw value and advances the internal write offset. The Encoder does not simply copy the provided value verbatim, but parses it to ensure that it is syntactically valid and reformats it according to how the Encoder is configured to format whitespace and strings.

The provided value kind must be consistent with the JSON grammar (see examples on Encoder.WriteToken). If the provided value is invalid, then it reports a SyntacticError and the internal state remains unchanged.

type Kind

type Kind byte

Kind represents each possible JSON token kind with a single byte, which is conveniently the first byte of that kind's grammar with the restriction that numbers always be represented with '0':

  • 'n': null
  • 'f': false
  • 't': true
  • '"': string
  • '0': number
  • '{': object start
  • '}': object end
  • '[': array start
  • ']': array end

An invalid kind is usually represented using 0, but may be non-zero due to invalid JSON data.

func (Kind) String

func (k Kind) String() string

String prints the kind in a humanly readable fashion.

type MarshalOptions

type MarshalOptions struct {

	// Marshalers is a list of type-specific marshalers to use.
	Marshalers *Marshalers

	// StringifyNumbers specifies that numeric Go types should be serialized
	// as a JSON string containing the equivalent JSON number value.
	//
	// According to RFC 8259, section 6, a JSON implementation may choose to
	// limit the representation of a JSON number to an IEEE 754 binary64 value.
	// This may cause decoders to lose precision for int64 and uint64 types.
	// Escaping JSON numbers as a JSON string preserves the exact precision.
	StringifyNumbers bool

	// DiscardUnknownMembers specifies that marshaling should ignore any
	// JSON object members stored in Go struct fields dedicated to storing
	// unknown JSON object members.
	DiscardUnknownMembers bool

	// Deterministic specifies that the same input value will be serialized
	// as the exact same output bytes. Different processes of
	// the same program will serialize equal values to the same bytes,
	// but different versions of the same program are not guaranteed
	// to produce the exact same sequence of bytes.
	Deterministic bool
	// contains filtered or unexported fields
}

MarshalOptions configures how Go data is serialized as JSON data. The zero value is equivalent to the default marshal settings.

Example (Errors)

Many error types are not serializable since they tend to be Go structs without any exported fields (e.g., errors constructed with errors.New). Some applications, may desire to marshal an error as a JSON string even if these errors cannot be unmarshaled.

package main

import (
	"fmt"
	"log"
	"os"
	"strconv"

	"k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"
)

func main() {
	// Response to serialize with some Go errors encountered.
	response := []struct {
		Result string `json:",omitzero"`
		Error  error  `json:",omitzero"`
	}{
		{Result: "Oranges are a good source of Vitamin C."},
		{Error: &strconv.NumError{Func: "ParseUint", Num: "-1234", Err: strconv.ErrSyntax}},
		{Error: &os.PathError{Op: "ReadFile", Path: "/path/to/secret/file", Err: os.ErrPermission}},
	}

	b, err := json.MarshalOptions{
		// Intercept every attempt to marshal an error type.
		Marshalers: json.NewMarshalers(
			// Suppose we consider strconv.NumError to be a safe to serialize:
			// this type-specific marshal function intercepts this type
			// and encodes the error message as a JSON string.
			json.MarshalFuncV2(func(opts json.MarshalOptions, enc *json.Encoder, err *strconv.NumError) error {
				return enc.WriteToken(json.String(err.Error()))
			}),
			// Error messages may contain sensitive information that may not
			// be appropriate to serialize. For all errors not handled above,
			// report some generic error message.
			json.MarshalFuncV1(func(error) ([]byte, error) {
				return []byte(`"internal server error"`), nil
			}),
		),
	}.Marshal(json.EncodeOptions{
		Indent: "\t", // indent for readability
	}, &response)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(string(b))

}
Output:

[
	{
		"Result": "Oranges are a good source of Vitamin C."
	},
	{
		"Error": "strconv.ParseUint: parsing \"-1234\": invalid syntax"
	},
	{
		"Error": "internal server error"
	}
]

func (MarshalOptions) Marshal

func (mo MarshalOptions) Marshal(eo EncodeOptions, in any) (out []byte, err error)

Marshal serializes a Go value as a []byte according to the provided marshal and encode options. It does not terminate the output with a newline. See MarshalNext for details about the conversion of a Go value into JSON.

func (MarshalOptions) MarshalFull

func (mo MarshalOptions) MarshalFull(eo EncodeOptions, out io.Writer, in any) error

MarshalFull serializes a Go value into an io.Writer according to the provided marshal and encode options. It does not terminate the output with a newline. See MarshalNext for details about the conversion of a Go value into JSON.

func (MarshalOptions) MarshalNext

func (mo MarshalOptions) MarshalNext(out *Encoder, in any) error

MarshalNext encodes a Go value as the next JSON value according to the provided marshal options.

Type-specific marshal functions and methods take precedence over the default representation of a value. Functions or methods that operate on *T are only called when encoding a value of type T (by taking its address) or a non-nil value of *T. MarshalNext ensures that a value is always addressable (by boxing it on the heap if necessary) so that these functions and methods can be consistently called. For performance, it is recommended that MarshalNext be passed a non-nil pointer to the value.

The input value is encoded as JSON according the following rules:

  • If any type-specific functions in MarshalOptions.Marshalers match the value type, then those functions are called to encode the value. If all applicable functions return SkipFunc, then the value is encoded according to subsequent rules.

  • If the value type implements MarshalerV2, then the MarshalNextJSON method is called to encode the value.

  • If the value type implements MarshalerV1, then the MarshalJSON method is called to encode the value.

  • If the value type implements encoding.TextMarshaler, then the MarshalText method is called to encode the value and subsequently encode its result as a JSON string.

  • Otherwise, the value is encoded according to the value's type as described in detail below.

Most Go types have a default JSON representation. Certain types support specialized formatting according to a format flag optionally specified in the Go struct tag for the struct field that contains the current value (see the “JSON Representation of Go structs” section for more details).

The representation of each type is as follows:

  • A Go boolean is encoded as a JSON boolean (e.g., true or false). It does not support any custom format flags.

  • A Go string is encoded as a JSON string. It does not support any custom format flags.

  • A Go []byte or [N]byte is encoded as a JSON string containing the binary value encoded using RFC 4648. If the format is "base64" or unspecified, then this uses RFC 4648, section 4. If the format is "base64url", then this uses RFC 4648, section 5. If the format is "base32", then this uses RFC 4648, section 6. If the format is "base32hex", then this uses RFC 4648, section 7. If the format is "base16" or "hex", then this uses RFC 4648, section 8. If the format is "array", then the bytes value is encoded as a JSON array where each byte is recursively JSON-encoded as each JSON array element.

  • A Go integer is encoded as a JSON number without fractions or exponents. If MarshalOptions.StringifyNumbers is specified, then the JSON number is encoded within a JSON string. It does not support any custom format flags.

  • A Go float is encoded as a JSON number. If MarshalOptions.StringifyNumbers is specified, then the JSON number is encoded within a JSON string. If the format is "nonfinite", then NaN, +Inf, and -Inf are encoded as the JSON strings "NaN", "Infinity", and "-Infinity", respectively. Otherwise, the presence of non-finite numbers results in a SemanticError.

  • A Go map is encoded as a JSON object, where each Go map key and value is recursively encoded as a name and value pair in the JSON object. The Go map key must encode as a JSON string, otherwise this results in a SemanticError. When encoding keys, MarshalOptions.StringifyNumbers is automatically applied so that numeric keys encode as JSON strings. The Go map is traversed in a non-deterministic order. For deterministic encoding, consider using RawValue.Canonicalize. If the format is "emitnull", then a nil map is encoded as a JSON null. Otherwise by default, a nil map is encoded as an empty JSON object.

  • A Go struct is encoded as a JSON object. See the “JSON Representation of Go structs” section in the package-level documentation for more details.

  • A Go slice is encoded as a JSON array, where each Go slice element is recursively JSON-encoded as the elements of the JSON array. If the format is "emitnull", then a nil slice is encoded as a JSON null. Otherwise by default, a nil slice is encoded as an empty JSON array.

  • A Go array is encoded as a JSON array, where each Go array element is recursively JSON-encoded as the elements of the JSON array. The JSON array length is always identical to the Go array length. It does not support any custom format flags.

  • A Go pointer is encoded as a JSON null if nil, otherwise it is the recursively JSON-encoded representation of the underlying value. Format flags are forwarded to the encoding of the underlying value.

  • A Go interface is encoded as a JSON null if nil, otherwise it is the recursively JSON-encoded representation of the underlying value. It does not support any custom format flags.

  • A Go time.Time is encoded as a JSON string containing the timestamp formatted in RFC 3339 with nanosecond resolution. If the format matches one of the format constants declared in the time package (e.g., RFC1123), then that format is used. Otherwise, the format is used as-is with time.Time.Format if non-empty.

  • A Go time.Duration is encoded as a JSON string containing the duration formatted according to time.Duration.String. If the format is "nanos", it is encoded as a JSON number containing the number of nanoseconds in the duration.

  • All other Go types (e.g., complex numbers, channels, and functions) have no default representation and result in a SemanticError.

JSON cannot represent cyclic data structures and MarshalNext does not handle them. Passing cyclic structures will result in an error.

type MarshalerV1

type MarshalerV1 interface {
	MarshalJSON() ([]byte, error)
}

MarshalerV1 is implemented by types that can marshal themselves. It is recommended that types implement MarshalerV2 unless the implementation is trying to avoid a hard dependency on the "jsontext" package.

It is recommended that implementations return a buffer that is safe for the caller to retain and potentially mutate.

type MarshalerV2

type MarshalerV2 interface {
	MarshalNextJSON(MarshalOptions, *Encoder) error
}

MarshalerV2 is implemented by types that can marshal themselves. It is recommended that types implement MarshalerV2 instead of MarshalerV1 since this is both more performant and flexible. If a type implements both MarshalerV1 and MarshalerV2, then MarshalerV2 takes precedence. In such a case, both implementations should aim to have equivalent behavior for the default marshal options.

The implementation must write only one JSON value to the Encoder and must not retain the pointer to Encoder.

type Marshalers

type Marshalers = typedMarshalers

Marshalers is a list of functions that may override the marshal behavior of specific types. Populate MarshalOptions.Marshalers to use it. A nil *Marshalers is equivalent to an empty list.

func MarshalFuncV1

func MarshalFuncV1[T any](fn func(T) ([]byte, error)) *Marshalers

MarshalFuncV1 constructs a type-specific marshaler that specifies how to marshal values of type T. T can be any type except a named pointer. The function is always provided with a non-nil pointer value if T is an interface or pointer type.

The function must marshal exactly one JSON value. The value of T must not be retained outside the function call. It may not return SkipFunc.

func MarshalFuncV2

func MarshalFuncV2[T any](fn func(MarshalOptions, *Encoder, T) error) *Marshalers

MarshalFuncV2 constructs a type-specific marshaler that specifies how to marshal values of type T. T can be any type except a named pointer. The function is always provided with a non-nil pointer value if T is an interface or pointer type.

The function must marshal exactly one JSON value by calling write methods on the provided encoder. It may return SkipFunc such that marshaling can move on to the next marshal function. However, no mutable method calls may be called on the encoder if SkipFunc is returned. The pointer to Encoder and the value of T must not be retained outside the function call.

func NewMarshalers

func NewMarshalers(ms ...*Marshalers) *Marshalers

NewMarshalers constructs a flattened list of marshal functions. If multiple functions in the list are applicable for a value of a given type, then those earlier in the list take precedence over those that come later. If a function returns SkipFunc, then the next applicable function is called, otherwise the default marshaling behavior is used.

For example:

m1 := NewMarshalers(f1, f2)
m2 := NewMarshalers(f0, m1, f3)     // equivalent to m3
m3 := NewMarshalers(f0, f1, f2, f3) // equivalent to m2

type RawValue

type RawValue []byte

RawValue represents a single raw JSON value, which may be one of the following:

  • a JSON literal (i.e., null, true, or false)
  • a JSON string (e.g., "hello, world!")
  • a JSON number (e.g., 123.456)
  • an entire JSON object (e.g., {"fizz":"buzz"} )
  • an entire JSON array (e.g., [1,2,3] )

RawValue can represent entire array or object values, while Token cannot. RawValue may contain leading and/or trailing whitespace.

func (*RawValue) Canonicalize

func (v *RawValue) Canonicalize() error

Canonicalize canonicalizes the raw JSON value according to the JSON Canonicalization Scheme (JCS) as defined by RFC 8785 where it produces a stable representation of a JSON value.

The output stability is dependent on the stability of the application data (see RFC 8785, Appendix E). It cannot produce stable output from fundamentally unstable input. For example, if the JSON value contains ephemeral data (e.g., a frequently changing timestamp), then the value is still unstable regardless of whether this is called.

Note that JCS treats all JSON numbers as IEEE 754 double precision numbers. Any numbers with precision beyond what is representable by that form will lose their precision when canonicalized. For example, integer values beyond ±2⁵³ will lose their precision. It is recommended that int64 and uint64 data types be represented as a JSON string.

It is guaranteed to succeed if the input is valid. If the value is already canonicalized, then the buffer is not mutated.

func (RawValue) Clone

func (v RawValue) Clone() RawValue

Clone returns a copy of v.

func (*RawValue) Compact

func (v *RawValue) Compact() error

Compact removes all whitespace from the raw JSON value.

It does not reformat JSON strings to use any other representation. It is guaranteed to succeed if the input is valid. If the value is already compacted, then the buffer is not mutated.

func (*RawValue) Indent

func (v *RawValue) Indent(prefix, indent string) error

Indent reformats the whitespace in the raw JSON value so that each element in a JSON object or array begins on a new, indented line beginning with prefix followed by one or more copies of indent according to the nesting. The value does not begin with the prefix nor any indention, to make it easier to embed inside other formatted JSON data.

It does not reformat JSON strings to use any other representation. It is guaranteed to succeed if the input is valid. If the value is already indented properly, then the buffer is not mutated.

func (RawValue) IsValid

func (v RawValue) IsValid() bool

IsValid reports whether the raw JSON value is syntactically valid according to RFC 7493.

It verifies whether the input is properly encoded as UTF-8, that escape sequences within strings decode to valid Unicode codepoints, and that all names in each object are unique. It does not verify whether numbers are representable within the limits of any common numeric type (e.g., float64, int64, or uint64).

func (RawValue) Kind

func (v RawValue) Kind() Kind

Kind returns the starting token kind. For a valid value, this will never include '}' or ']'.

func (RawValue) MarshalJSON

func (v RawValue) MarshalJSON() ([]byte, error)

MarshalJSON returns v as the JSON encoding of v. It returns the stored value as the raw JSON output without any validation. If v is nil, then this returns a JSON null.

func (RawValue) String

func (v RawValue) String() string

String returns the string formatting of v.

func (*RawValue) UnmarshalJSON

func (v *RawValue) UnmarshalJSON(b []byte) error

UnmarshalJSON sets v as the JSON encoding of b. It stores a copy of the provided raw JSON input without any validation.

type SemanticError

type SemanticError struct {

	// ByteOffset indicates that an error occurred after this byte offset.
	ByteOffset int64
	// JSONPointer indicates that an error occurred within this JSON value
	// as indicated using the JSON Pointer notation (see RFC 6901).
	JSONPointer string

	// JSONKind is the JSON kind that could not be handled.
	JSONKind Kind // may be zero if unknown
	// GoType is the Go type that could not be handled.
	GoType reflect.Type // may be nil if unknown

	// Err is the underlying error.
	Err error // may be nil
	// contains filtered or unexported fields
}

SemanticError describes an error determining the meaning of JSON data as Go data or vice-versa.

The contents of this error as produced by this package may change over time.

func (*SemanticError) Error

func (e *SemanticError) Error() string

func (*SemanticError) Is

func (e *SemanticError) Is(target error) bool

func (*SemanticError) Unwrap

func (e *SemanticError) Unwrap() error

type SyntacticError

type SyntacticError struct {

	// ByteOffset indicates that an error occurred after this byte offset.
	ByteOffset int64
	// contains filtered or unexported fields
}

SyntacticError is a description of a syntactic error that occurred when encoding or decoding JSON according to the grammar.

The contents of this error as produced by this package may change over time.

func (*SyntacticError) Error

func (e *SyntacticError) Error() string

func (*SyntacticError) Is

func (e *SyntacticError) Is(target error) bool

type Token

type Token struct {
	// contains filtered or unexported fields
}

Token represents a lexical JSON token, which may be one of the following:

  • a JSON literal (i.e., null, true, or false)
  • a JSON string (e.g., "hello, world!")
  • a JSON number (e.g., 123.456)
  • a start or end delimiter for a JSON object (i.e., { or } )
  • a start or end delimiter for a JSON array (i.e., [ or ] )

A Token cannot represent entire array or object values, while a RawValue can. There is no Token to represent commas and colons since these structural tokens can be inferred from the surrounding context.

var (
	Null  Token = rawToken("null")
	False Token = rawToken("false")
	True  Token = rawToken("true")

	ObjectStart Token = rawToken("{")
	ObjectEnd   Token = rawToken("}")
	ArrayStart  Token = rawToken("[")
	ArrayEnd    Token = rawToken("]")
)

func Bool

func Bool(b bool) Token

Bool constructs a Token representing a JSON boolean.

func Float

func Float(n float64) Token

Float constructs a Token representing a JSON number. The values NaN, +Inf, and -Inf will be represented as a JSON string with the values "NaN", "Infinity", and "-Infinity".

func Int

func Int(n int64) Token

Int constructs a Token representing a JSON number from an int64.

func String

func String(s string) Token

String constructs a Token representing a JSON string. The provided string should contain valid UTF-8, otherwise invalid characters may be mangled as the Unicode replacement character.

func Uint

func Uint(n uint64) Token

Uint constructs a Token representing a JSON number from a uint64.

func (Token) Bool

func (t Token) Bool() bool

Bool returns the value for a JSON boolean. It panics if the token kind is not a JSON boolean.

func (Token) Clone

func (t Token) Clone() Token

Clone makes a copy of the Token such that its value remains valid even after a subsequent Decoder.Read call.

func (Token) Float

func (t Token) Float() float64

Float returns the floating-point value for a JSON number. It returns a NaN, +Inf, or -Inf value for any JSON string with the values "NaN", "Infinity", or "-Infinity". It panics for all other cases.

func (Token) Int

func (t Token) Int() int64

Int returns the signed integer value for a JSON number. The fractional component of any number is ignored (truncation toward zero). Any number beyond the representation of an int64 will be saturated to the closest representable value. It panics if the token kind is not a JSON number.

func (Token) Kind

func (t Token) Kind() Kind

Kind returns the token kind.

func (Token) String

func (t Token) String() string

String returns the unescaped string value for a JSON string. For other JSON kinds, this returns the raw JSON representation.

func (Token) Uint

func (t Token) Uint() uint64

Uint returns the unsigned integer value for a JSON number. The fractional component of any number is ignored (truncation toward zero). Any number beyond the representation of an uint64 will be saturated to the closest representable value. It panics if the token kind is not a JSON number.

type UnmarshalOptions

type UnmarshalOptions struct {

	// Unmarshalers is a list of type-specific unmarshalers to use.
	Unmarshalers *Unmarshalers

	// StringifyNumbers specifies that numeric Go types can be deserialized
	// from either a JSON number or a JSON string containing a JSON number
	// without any surrounding whitespace.
	StringifyNumbers bool

	// RejectUnknownMembers specifies that unknown members should be rejected
	// when unmarshaling a JSON object, regardless of whether there is a field
	// to store unknown members.
	RejectUnknownMembers bool
	// contains filtered or unexported fields
}

UnmarshalOptions configures how JSON data is deserialized as Go data. The zero value is equivalent to the default unmarshal settings.

Example (RawNumber)

In some applications, the exact precision of JSON numbers needs to be preserved when unmarshaling. This can be accomplished using a type-specific unmarshal function that intercepts all any types and pre-populates the interface value with a RawValue, which can represent a JSON number exactly.

package main

import (
	"fmt"
	"log"
	"reflect"

	"k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"
)

func main() {
	// Input with JSON numbers beyond the representation of a float64.
	const input = `[false, 1e-1000, 3.141592653589793238462643383279, 1e+1000, true]`

	var value any
	err := json.UnmarshalOptions{
		// Intercept every attempt to unmarshal into the any type.
		Unmarshalers: json.UnmarshalFuncV2(func(opts json.UnmarshalOptions, dec *json.Decoder, val *any) error {
			// If the next value to be decoded is a JSON number,
			// then provide a concrete Go type to unmarshal into.
			if dec.PeekKind() == '0' {
				*val = json.RawValue(nil)
			}
			// Return SkipFunc to fallback on default unmarshal behavior.
			return json.SkipFunc
		}),
	}.Unmarshal(json.DecodeOptions{}, []byte(input), &value)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(value)

	// Sanity check.
	want := []any{false, json.RawValue("1e-1000"), json.RawValue("3.141592653589793238462643383279"), json.RawValue("1e+1000"), true}
	if !reflect.DeepEqual(value, want) {
		log.Fatalf("value mismatch:\ngot  %v\nwant %v", value, want)
	}

}
Output:

[false 1e-1000 3.141592653589793238462643383279 1e+1000 true]
Example (RecordOffsets)

When using JSON for parsing configuration files, the parsing logic often needs to report an error with a line and column indicating where in the input an error occurred.

package main

import (
	"bytes"
	"fmt"
	"log"
	"net/netip"
	"strings"

	"k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"
)

func main() {
	// Hypothetical configuration file.
	const input = `[
		{"Source": "192.168.0.100:1234", "Destination": "192.168.0.1:80"},
		{"Source": "192.168.0.251:4004"},
		{"Source": "192.168.0.165:8080", "Destination": "0.0.0.0:80"}
	]`
	type Tunnel struct {
		Source      netip.AddrPort
		Destination netip.AddrPort

		// ByteOffset is populated during unmarshal with the byte offset
		// within the JSON input of the JSON object for this Go struct.
		ByteOffset int64 `json:"-"` // metadata to be ignored for JSON serialization
	}

	var tunnels []Tunnel
	err := json.UnmarshalOptions{
		// Intercept every attempt to unmarshal into the Tunnel type.
		Unmarshalers: json.UnmarshalFuncV2(func(opts json.UnmarshalOptions, dec *json.Decoder, tunnel *Tunnel) error {
			// Decoder.InputOffset reports the offset after the last token,
			// but we want to record the offset before the next token.
			//
			// Call Decoder.PeekKind to buffer enough to reach the next token.
			// Add the number of leading whitespace, commas, and colons
			// to locate the start of the next token.
			dec.PeekKind()
			unread := dec.UnreadBuffer()
			n := len(unread) - len(bytes.TrimLeft(unread, " \n\r\t,:"))
			tunnel.ByteOffset = dec.InputOffset() + int64(n)

			// Return SkipFunc to fallback on default unmarshal behavior.
			return json.SkipFunc
		}),
	}.Unmarshal(json.DecodeOptions{}, []byte(input), &tunnels)
	if err != nil {
		log.Fatal(err)
	}

	// lineColumn converts a byte offset into a one-indexed line and column.
	// The offset must be within the bounds of the input.
	lineColumn := func(input string, offset int) (line, column int) {
		line = 1 + strings.Count(input[:offset], "\n")
		column = 1 + offset - (strings.LastIndex(input[:offset], "\n") + len("\n"))
		return line, column
	}

	// Verify that the configuration file is valid.
	for _, tunnel := range tunnels {
		if !tunnel.Source.IsValid() || !tunnel.Destination.IsValid() {
			line, column := lineColumn(input, int(tunnel.ByteOffset))
			fmt.Printf("%d:%d: source and destination must both be specified", line, column)
		}
	}

}
Output:

3:3: source and destination must both be specified

func (UnmarshalOptions) Unmarshal

func (uo UnmarshalOptions) Unmarshal(do DecodeOptions, in []byte, out any) error

Unmarshal deserializes a Go value from a []byte according to the provided unmarshal and decode options. The output must be a non-nil pointer. The input must be a single JSON value with optional whitespace interspersed. See UnmarshalNext for details about the conversion of JSON into a Go value.

func (UnmarshalOptions) UnmarshalFull

func (uo UnmarshalOptions) UnmarshalFull(do DecodeOptions, in io.Reader, out any) error

UnmarshalFull deserializes a Go value from an io.Reader according to the provided unmarshal and decode options. The output must be a non-nil pointer. The input must be a single JSON value with optional whitespace interspersed. It consumes the entirety of io.Reader until io.EOF is encountered. See UnmarshalNext for details about the conversion of JSON into a Go value.

func (UnmarshalOptions) UnmarshalNext

func (uo UnmarshalOptions) UnmarshalNext(in *Decoder, out any) error

UnmarshalNext decodes the next JSON value into a Go value according to the provided unmarshal options. The output must be a non-nil pointer.

Type-specific unmarshal functions and methods take precedence over the default representation of a value. Functions or methods that operate on *T are only called when decoding a value of type T (by taking its address) or a non-nil value of *T. UnmarshalNext ensures that a value is always addressable (by boxing it on the heap if necessary) so that these functions and methods can be consistently called.

The input is decoded into the output according the following rules:

  • If any type-specific functions in UnmarshalOptions.Unmarshalers match the value type, then those functions are called to decode the JSON value. If all applicable functions return SkipFunc, then the input is decoded according to subsequent rules.

  • If the value type implements UnmarshalerV2, then the UnmarshalNextJSON method is called to decode the JSON value.

  • If the value type implements UnmarshalerV1, then the UnmarshalJSON method is called to decode the JSON value.

  • If the value type implements encoding.TextUnmarshaler, then the input is decoded as a JSON string and the UnmarshalText method is called with the decoded string value. This fails with a SemanticError if the input is not a JSON string.

  • Otherwise, the JSON value is decoded according to the value's type as described in detail below.

Most Go types have a default JSON representation. Certain types support specialized formatting according to a format flag optionally specified in the Go struct tag for the struct field that contains the current value (see the “JSON Representation of Go structs” section for more details). A JSON null may be decoded into every supported Go value where it is equivalent to storing the zero value of the Go value. If the input JSON kind is not handled by the current Go value type, then this fails with a SemanticError. Unless otherwise specified, the decoded value replaces any pre-existing value.

The representation of each type is as follows:

  • A Go boolean is decoded from a JSON boolean (e.g., true or false). It does not support any custom format flags.

  • A Go string is decoded from a JSON string. It does not support any custom format flags.

  • A Go []byte or [N]byte is decoded from a JSON string containing the binary value encoded using RFC 4648. If the format is "base64" or unspecified, then this uses RFC 4648, section 4. If the format is "base64url", then this uses RFC 4648, section 5. If the format is "base32", then this uses RFC 4648, section 6. If the format is "base32hex", then this uses RFC 4648, section 7. If the format is "base16" or "hex", then this uses RFC 4648, section 8. If the format is "array", then the Go slice or array is decoded from a JSON array where each JSON element is recursively decoded for each byte. When decoding into a non-nil []byte, the slice length is reset to zero and the decoded input is appended to it. When decoding into a [N]byte, the input must decode to exactly N bytes, otherwise it fails with a SemanticError.

  • A Go integer is decoded from a JSON number. It may also be decoded from a JSON string containing a JSON number if UnmarshalOptions.StringifyNumbers is specified. It fails with a SemanticError if the JSON number has a fractional or exponent component. It also fails if it overflows the representation of the Go integer type. It does not support any custom format flags.

  • A Go float is decoded from a JSON number. It may also be decoded from a JSON string containing a JSON number if UnmarshalOptions.StringifyNumbers is specified. The JSON number is parsed as the closest representable Go float value. If the format is "nonfinite", then the JSON strings "NaN", "Infinity", and "-Infinity" are decoded as NaN, +Inf, and -Inf. Otherwise, the presence of such strings results in a SemanticError.

  • A Go map is decoded from a JSON object, where each JSON object name and value pair is recursively decoded as the Go map key and value. When decoding keys, UnmarshalOptions.StringifyNumbers is automatically applied so that numeric keys can decode from JSON strings. Maps are not cleared. If the Go map is nil, then a new map is allocated to decode into. If the decoded key matches an existing Go map entry, the entry value is reused by decoding the JSON object value into it. The only supported format is "emitnull" and has no effect when decoding.

  • A Go struct is decoded from a JSON object. See the “JSON Representation of Go structs” section in the package-level documentation for more details.

  • A Go slice is decoded from a JSON array, where each JSON element is recursively decoded and appended to the Go slice. Before appending into a Go slice, a new slice is allocated if it is nil, otherwise the slice length is reset to zero. The only supported format is "emitnull" and has no effect when decoding.

  • A Go array is decoded from a JSON array, where each JSON array element is recursively decoded as each corresponding Go array element. Each Go array element is zeroed before decoding into it. It fails with a SemanticError if the JSON array does not contain the exact same number of elements as the Go array. It does not support any custom format flags.

  • A Go pointer is decoded based on the JSON kind and underlying Go type. If the input is a JSON null, then this stores a nil pointer. Otherwise, it allocates a new underlying value if the pointer is nil, and recursively JSON decodes into the underlying value. Format flags are forwarded to the decoding of the underlying type.

  • A Go interface is decoded based on the JSON kind and underlying Go type. If the input is a JSON null, then this stores a nil interface value. Otherwise, a nil interface value of an empty interface type is initialized with a zero Go bool, string, float64, map[string]any, or []any if the input is a JSON boolean, string, number, object, or array, respectively. If the interface value is still nil, then this fails with a SemanticError since decoding could not determine an appropriate Go type to decode into. For example, unmarshaling into a nil io.Reader fails since there is no concrete type to populate the interface value with. Otherwise an underlying value exists and it recursively decodes the JSON input into it. It does not support any custom format flags.

  • A Go time.Time is decoded from a JSON string containing the time formatted in RFC 3339 with nanosecond resolution. If the format matches one of the format constants declared in the time package (e.g., RFC1123), then that format is used for parsing. Otherwise, the format is used as-is with time.Time.Parse if non-empty.

  • A Go time.Duration is decoded from a JSON string by passing the decoded string to time.ParseDuration. If the format is "nanos", it is instead decoded from a JSON number containing the number of nanoseconds in the duration.

  • All other Go types (e.g., complex numbers, channels, and functions) have no default representation and result in a SemanticError.

In general, unmarshaling follows merge semantics (similar to RFC 7396) where the decoded Go value replaces the destination value for any JSON kind other than an object. For JSON objects, the input object is merged into the destination value where matching object members recursively apply merge semantics.

type UnmarshalerV1

type UnmarshalerV1 interface {
	UnmarshalJSON([]byte) error
}

UnmarshalerV1 is implemented by types that can unmarshal themselves. It is recommended that types implement UnmarshalerV2 unless the implementation is trying to avoid a hard dependency on this package.

The input can be assumed to be a valid encoding of a JSON value if called from unmarshal functionality in this package. UnmarshalJSON must copy the JSON data if it is retained after returning. It is recommended that UnmarshalJSON implement merge semantics when unmarshaling into a pre-populated value.

Implementations must not retain or mutate the input []byte.

type UnmarshalerV2

type UnmarshalerV2 interface {
	UnmarshalNextJSON(UnmarshalOptions, *Decoder) error
}

UnmarshalerV2 is implemented by types that can unmarshal themselves. It is recommended that types implement UnmarshalerV2 instead of UnmarshalerV1 since this is both more performant and flexible. If a type implements both UnmarshalerV1 and UnmarshalerV2, then UnmarshalerV2 takes precedence. In such a case, both implementations should aim to have equivalent behavior for the default unmarshal options.

The implementation must read only one JSON value from the Decoder. It is recommended that UnmarshalNextJSON implement merge semantics when unmarshaling into a pre-populated value.

Implementations must not retain the pointer to Decoder.

type Unmarshalers

type Unmarshalers = typedUnmarshalers

Unmarshalers is a list of functions that may override the unmarshal behavior of specific types. Populate UnmarshalOptions.Unmarshalers to use it. A nil *Unmarshalers is equivalent to an empty list.

func NewUnmarshalers

func NewUnmarshalers(us ...*Unmarshalers) *Unmarshalers

NewUnmarshalers constructs a flattened list of unmarshal functions. If multiple functions in the list are applicable for a value of a given type, then those earlier in the list take precedence over those that come later. If a function returns SkipFunc, then the next applicable function is called, otherwise the default unmarshaling behavior is used.

For example:

u1 := NewUnmarshalers(f1, f2)
u2 := NewUnmarshalers(f0, u1, f3)     // equivalent to u3
u3 := NewUnmarshalers(f0, f1, f2, f3) // equivalent to u2

func UnmarshalFuncV1

func UnmarshalFuncV1[T any](fn func([]byte, T) error) *Unmarshalers

UnmarshalFuncV1 constructs a type-specific unmarshaler that specifies how to unmarshal values of type T. T must be an unnamed pointer or an interface type. The function is always provided with a non-nil pointer value.

The function must unmarshal exactly one JSON value. The input []byte must not be mutated. The input []byte and value T must not be retained outside the function call. It may not return SkipFunc.

func UnmarshalFuncV2

func UnmarshalFuncV2[T any](fn func(UnmarshalOptions, *Decoder, T) error) *Unmarshalers

UnmarshalFuncV2 constructs a type-specific unmarshaler that specifies how to unmarshal values of type T. T must be an unnamed pointer or an interface type. The function is always provided with a non-nil pointer value.

The function must unmarshal exactly one JSON value by calling read methods on the provided decoder. It may return SkipFunc such that unmarshaling can move on to the next unmarshal function. However, no mutable method calls may be called on the decoder if SkipFunc is returned. The pointer to Decoder and the value of T must not be retained outside the function call.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL