aboutsummaryrefslogtreecommitdiff
path: root/src/encoding/json/v2_decode.go
diff options
context:
space:
mode:
authorDamien Neil <dneil@google.com>2025-04-11 14:19:51 -0700
committerGopher Robot <gobot@golang.org>2025-04-18 08:24:07 -0700
commit0e17905793cb5e0acc323a0cdf3733199d93976a (patch)
treefec117ceb6b56866e6c51e6acd72901cf91717ce /src/encoding/json/v2_decode.go
parentc889004615b40535ebd5054cbcf2deebdb3a299a (diff)
downloadgo-0e17905793cb5e0acc323a0cdf3733199d93976a.tar.xz
encoding/json: add json/v2 with GOEXPERIMENT=jsonv2 guard
This imports the proposed new v2 JSON API implemented in github.com/go-json-experiment/json as of commit d3c622f1b874954c355e60c8e6b6baa5f60d2fed. When GOEXPERIMENT=jsonv2 is set, the encoding/json/v2 and encoding/jsontext packages are visible, the encoding/json package is implemented in terms of encoding/json/v2, and the encoding/json package include various additional APIs. (See #71497 for details.) When GOEXPERIMENT=jsonv2 is not set, the new API is not present and the encoding/json package is unchanged. The experimental API is not bound by the Go compatibility promise and is expected to evolve as updates are made to the json/v2 proposal. The contents of encoding/json/internal/jsontest/testdata are compressed with zstd v1.5.7 with the -19 option. Fixes #71845 For #71497 Change-Id: Ib8c94e5f0586b6aaa22833190b41cf6ef59f4f01 Reviewed-on: https://go-review.googlesource.com/c/go/+/665796 Auto-Submit: Damien Neil <dneil@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Michael Pratt <mpratt@google.com> Reviewed-by: Joseph Tsai <joetsai@digital-static.net> Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Diffstat (limited to 'src/encoding/json/v2_decode.go')
-rw-r--r--src/encoding/json/v2_decode.go253
1 files changed, 253 insertions, 0 deletions
diff --git a/src/encoding/json/v2_decode.go b/src/encoding/json/v2_decode.go
new file mode 100644
index 0000000000..4b9e850939
--- /dev/null
+++ b/src/encoding/json/v2_decode.go
@@ -0,0 +1,253 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build goexperiment.jsonv2
+
+// Represents JSON data structure using native Go types: booleans, floats,
+// strings, arrays, and maps.
+
+package json
+
+import (
+ "cmp"
+ "fmt"
+ "reflect"
+ "strconv"
+
+ "encoding/json/internal/jsonwire"
+ "encoding/json/jsontext"
+ jsonv2 "encoding/json/v2"
+)
+
+// Unmarshal parses the JSON-encoded data and stores the result
+// in the value pointed to by v. If v is nil or not a pointer,
+// Unmarshal returns an [InvalidUnmarshalError].
+//
+// Unmarshal uses the inverse of the encodings that
+// [Marshal] uses, allocating maps, slices, and pointers as necessary,
+// with the following additional rules:
+//
+// To unmarshal JSON into a pointer, Unmarshal first handles the case of
+// the JSON being the JSON literal null. In that case, Unmarshal sets
+// the pointer to nil. Otherwise, Unmarshal unmarshals the JSON into
+// the value pointed at by the pointer. If the pointer is nil, Unmarshal
+// allocates a new value for it to point to.
+//
+// To unmarshal JSON into a value implementing [Unmarshaler],
+// Unmarshal calls that value's [Unmarshaler.UnmarshalJSON] method, including
+// when the input is a JSON null.
+// Otherwise, if the value implements [encoding.TextUnmarshaler]
+// and the input is a JSON quoted string, Unmarshal calls
+// [encoding.TextUnmarshaler.UnmarshalText] with the unquoted form of the string.
+//
+// To unmarshal JSON into a struct, Unmarshal matches incoming object
+// keys to the keys used by [Marshal] (either the struct field name or its tag),
+// preferring an exact match but also accepting a case-insensitive match. By
+// default, object keys which don't have a corresponding struct field are
+// ignored (see [Decoder.DisallowUnknownFields] for an alternative).
+//
+// To unmarshal JSON into an interface value,
+// Unmarshal stores one of these in the interface value:
+//
+// - bool, for JSON booleans
+// - float64, for JSON numbers
+// - string, for JSON strings
+// - []any, for JSON arrays
+// - map[string]any, for JSON objects
+// - nil for JSON null
+//
+// To unmarshal a JSON array into a slice, Unmarshal resets the slice length
+// to zero and then appends each element to the slice.
+// As a special case, to unmarshal an empty JSON array into a slice,
+// Unmarshal replaces the slice with a new empty slice.
+//
+// To unmarshal a JSON array into a Go array, Unmarshal decodes
+// JSON array elements into corresponding Go array elements.
+// If the Go array is smaller than the JSON array,
+// the additional JSON array elements are discarded.
+// If the JSON array is smaller than the Go array,
+// the additional Go array elements are set to zero values.
+//
+// To unmarshal a JSON object into a map, Unmarshal first establishes a map to
+// use. If the map is nil, Unmarshal allocates a new map. Otherwise Unmarshal
+// reuses the existing map, keeping existing entries. Unmarshal then stores
+// key-value pairs from the JSON object into the map. The map's key type must
+// either be any string type, an integer, or implement [encoding.TextUnmarshaler].
+//
+// If the JSON-encoded data contain a syntax error, Unmarshal returns a [SyntaxError].
+//
+// If a JSON value is not appropriate for a given target type,
+// or if a JSON number overflows the target type, Unmarshal
+// skips that field and completes the unmarshaling as best it can.
+// If no more serious errors are encountered, Unmarshal returns
+// an [UnmarshalTypeError] describing the earliest such error. In any
+// case, it's not guaranteed that all the remaining fields following
+// the problematic one will be unmarshaled into the target object.
+//
+// The JSON null value unmarshals into an interface, map, pointer, or slice
+// by setting that Go value to nil. Because null is often used in JSON to mean
+// “not present,” unmarshaling a JSON null into any other Go type has no effect
+// on the value and produces no error.
+//
+// When unmarshaling quoted strings, invalid UTF-8 or
+// invalid UTF-16 surrogate pairs are not treated as an error.
+// Instead, they are replaced by the Unicode replacement
+// character U+FFFD.
+func Unmarshal(data []byte, v any) error {
+ return jsonv2.Unmarshal(data, v, DefaultOptionsV1())
+}
+
+// Unmarshaler is the interface implemented by types
+// that can unmarshal a JSON description of themselves.
+// The input can be assumed to be a valid encoding of
+// a JSON value. UnmarshalJSON must copy the JSON data
+// if it wishes to retain the data after returning.
+type Unmarshaler = jsonv2.Unmarshaler
+
+// An UnmarshalTypeError describes a JSON value that was
+// not appropriate for a value of a specific Go type.
+type UnmarshalTypeError struct {
+ Value string // description of JSON value - "bool", "array", "number -5"
+ Type reflect.Type // type of Go value it could not be assigned to
+ Offset int64 // error occurred after reading Offset bytes
+ Struct string // name of the root type containing the field
+ Field string // the full path from root node to the value
+ Err error // may be nil
+}
+
+func (e *UnmarshalTypeError) Error() string {
+ s := "json: cannot unmarshal"
+ if e.Value != "" {
+ s += " JSON " + e.Value
+ }
+ s += " into"
+ var preposition string
+ if e.Field != "" {
+ s += " " + e.Struct + "." + e.Field
+ preposition = " of"
+ }
+ if e.Type != nil {
+ s += preposition
+ s += " Go type " + e.Type.String()
+ }
+ if e.Err != nil {
+ s += ": " + e.Err.Error()
+ }
+ return s
+}
+
+func (e *UnmarshalTypeError) Unwrap() error {
+ return e.Err
+}
+
+// An UnmarshalFieldError describes a JSON object key that
+// led to an unexported (and therefore unwritable) struct field.
+//
+// Deprecated: No longer used; kept for compatibility.
+type UnmarshalFieldError struct {
+ Key string
+ Type reflect.Type
+ Field reflect.StructField
+}
+
+func (e *UnmarshalFieldError) Error() string {
+ return "json: cannot unmarshal object key " + strconv.Quote(e.Key) + " into unexported field " + e.Field.Name + " of type " + e.Type.String()
+}
+
+// An InvalidUnmarshalError describes an invalid argument passed to [Unmarshal].
+// (The argument to [Unmarshal] must be a non-nil pointer.)
+type InvalidUnmarshalError struct {
+ Type reflect.Type
+}
+
+func (e *InvalidUnmarshalError) Error() string {
+ if e.Type == nil {
+ return "json: Unmarshal(nil)"
+ }
+
+ if e.Type.Kind() != reflect.Pointer {
+ return "json: Unmarshal(non-pointer " + e.Type.String() + ")"
+ }
+ return "json: Unmarshal(nil " + e.Type.String() + ")"
+}
+
+// A Number represents a JSON number literal.
+type Number string
+
+// String returns the literal text of the number.
+func (n Number) String() string { return string(n) }
+
+// Float64 returns the number as a float64.
+func (n Number) Float64() (float64, error) {
+ return strconv.ParseFloat(string(n), 64)
+}
+
+// Int64 returns the number as an int64.
+func (n Number) Int64() (int64, error) {
+ return strconv.ParseInt(string(n), 10, 64)
+}
+
+var numberType = reflect.TypeFor[Number]()
+
+// MarshalJSONTo implements [jsonv2.MarshalerTo].
+func (n Number) MarshalJSONTo(enc *jsontext.Encoder) error {
+ opts := enc.Options()
+ stringify, _ := jsonv2.GetOption(opts, jsonv2.StringifyNumbers)
+ if k, n := enc.StackIndex(enc.StackDepth()); k == '{' && n%2 == 0 {
+ stringify = true // expecting a JSON object name
+ }
+ n = cmp.Or(n, "0")
+ var num []byte
+ val := enc.UnusedBuffer()
+ if stringify {
+ val = append(val, '"')
+ val = append(val, n...)
+ val = append(val, '"')
+ num = val[len(`"`) : len(val)-len(`"`)]
+ } else {
+ val = append(val, n...)
+ num = val
+ }
+ if n, err := jsonwire.ConsumeNumber(num); n != len(num) || err != nil {
+ return fmt.Errorf("cannot parse %q as JSON number: %w", val, strconv.ErrSyntax)
+ }
+ return enc.WriteValue(val)
+}
+
+// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
+func (n *Number) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
+ opts := dec.Options()
+ stringify, _ := jsonv2.GetOption(opts, jsonv2.StringifyNumbers)
+ if k, n := dec.StackIndex(dec.StackDepth()); k == '{' && n%2 == 0 {
+ stringify = true // expecting a JSON object name
+ }
+ val, err := dec.ReadValue()
+ if err != nil {
+ return err
+ }
+ val0 := val
+ k := val.Kind()
+ switch k {
+ case 'n':
+ if legacy, _ := jsonv2.GetOption(opts, MergeWithLegacySemantics); !legacy {
+ *n = ""
+ }
+ return nil
+ case '"':
+ verbatim := jsonwire.ConsumeSimpleString(val) == len(val)
+ val = jsonwire.UnquoteMayCopy(val, verbatim)
+ if n, err := jsonwire.ConsumeNumber(val); n != len(val) || err != nil {
+ return &jsonv2.SemanticError{JSONKind: val0.Kind(), JSONValue: val0.Clone(), GoType: numberType, Err: strconv.ErrSyntax}
+ }
+ *n = Number(val)
+ return nil
+ case '0':
+ if stringify {
+ break
+ }
+ *n = Number(val)
+ return nil
+ }
+ return &jsonv2.SemanticError{JSONKind: k, GoType: numberType}
+}