You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
253 lines
8.6 KiB
253 lines
8.6 KiB
// 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.AvailableBuffer() |
|
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} |
|
}
|
|
|