282 lines
7.9 KiB
Go
282 lines
7.9 KiB
Go
// Copyright 2022 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.
|
|
|
|
package json
|
|
|
|
import (
|
|
"cmp"
|
|
"reflect"
|
|
"strconv"
|
|
|
|
"github.com/go-json-experiment/json/internal"
|
|
"github.com/go-json-experiment/json/internal/jsonflags"
|
|
"github.com/go-json-experiment/json/internal/jsonopts"
|
|
"github.com/go-json-experiment/json/internal/jsonwire"
|
|
"github.com/go-json-experiment/json/jsontext"
|
|
)
|
|
|
|
// This file contains an optimized marshal and unmarshal implementation
|
|
// for the any type. This type is often used when the Go program has
|
|
// no knowledge of the JSON schema. This is a common enough occurrence
|
|
// to justify the complexity of adding logic for this.
|
|
|
|
// marshalValueAny marshals a Go any as a JSON value.
|
|
// This assumes that there are no special formatting directives
|
|
// for any possible nested value.
|
|
func marshalValueAny(enc *jsontext.Encoder, val any, mo *jsonopts.Struct) error {
|
|
switch val := val.(type) {
|
|
case nil:
|
|
return enc.WriteToken(jsontext.Null)
|
|
case bool:
|
|
return enc.WriteToken(jsontext.Bool(val))
|
|
case string:
|
|
return enc.WriteToken(jsontext.String(val))
|
|
case float64:
|
|
return enc.WriteToken(jsontext.Float(val))
|
|
case map[string]any:
|
|
return marshalObjectAny(enc, val, mo)
|
|
case []any:
|
|
return marshalArrayAny(enc, val, mo)
|
|
default:
|
|
v := newAddressableValue(reflect.TypeOf(val))
|
|
v.Set(reflect.ValueOf(val))
|
|
marshal := lookupArshaler(v.Type()).marshal
|
|
if mo.Marshalers != nil {
|
|
marshal, _ = mo.Marshalers.(*Marshalers).lookup(marshal, v.Type())
|
|
}
|
|
return marshal(enc, v, mo)
|
|
}
|
|
}
|
|
|
|
// unmarshalValueAny unmarshals a JSON value as a Go any.
|
|
// This assumes that there are no special formatting directives
|
|
// for any possible nested value.
|
|
// Duplicate names must be rejected since this does not implement merging.
|
|
func unmarshalValueAny(dec *jsontext.Decoder, uo *jsonopts.Struct) (any, error) {
|
|
switch k := dec.PeekKind(); k {
|
|
case '{':
|
|
return unmarshalObjectAny(dec, uo)
|
|
case '[':
|
|
return unmarshalArrayAny(dec, uo)
|
|
default:
|
|
xd := export.Decoder(dec)
|
|
var flags jsonwire.ValueFlags
|
|
val, err := xd.ReadValue(&flags)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
switch val.Kind() {
|
|
case 'n':
|
|
return nil, nil
|
|
case 'f':
|
|
return false, nil
|
|
case 't':
|
|
return true, nil
|
|
case '"':
|
|
val = jsonwire.UnquoteMayCopy(val, flags.IsVerbatim())
|
|
if xd.StringCache == nil {
|
|
xd.StringCache = new(stringCache)
|
|
}
|
|
return makeString(xd.StringCache, val), nil
|
|
case '0':
|
|
if uo.Flags.Get(jsonflags.UnmarshalAnyWithRawNumber) {
|
|
return internal.RawNumberOf(val), nil
|
|
}
|
|
fv, ok := jsonwire.ParseFloat(val, 64)
|
|
if !ok {
|
|
return fv, newUnmarshalErrorAfterWithValue(dec, float64Type, strconv.ErrRange)
|
|
}
|
|
return fv, nil
|
|
default:
|
|
panic("BUG: invalid kind: " + k.String())
|
|
}
|
|
}
|
|
}
|
|
|
|
// marshalObjectAny marshals a Go map[string]any as a JSON object
|
|
// (or as a JSON null if nil and [jsonflags.FormatNilMapAsNull]).
|
|
func marshalObjectAny(enc *jsontext.Encoder, obj map[string]any, mo *jsonopts.Struct) error {
|
|
// Check for cycles.
|
|
xe := export.Encoder(enc)
|
|
if xe.Tokens.Depth() > startDetectingCyclesAfter {
|
|
v := reflect.ValueOf(obj)
|
|
if err := visitPointer(&xe.SeenPointers, v); err != nil {
|
|
return newMarshalErrorBefore(enc, anyType, err)
|
|
}
|
|
defer leavePointer(&xe.SeenPointers, v)
|
|
}
|
|
|
|
// Handle empty maps.
|
|
if len(obj) == 0 {
|
|
if mo.Flags.Get(jsonflags.FormatNilMapAsNull) && obj == nil {
|
|
return enc.WriteToken(jsontext.Null)
|
|
}
|
|
// Optimize for marshaling an empty map without any preceding whitespace.
|
|
if !mo.Flags.Get(jsonflags.AnyWhitespace) && !xe.Tokens.Last.NeedObjectName() {
|
|
xe.Buf = append(xe.Tokens.MayAppendDelim(xe.Buf, '{'), "{}"...)
|
|
xe.Tokens.Last.Increment()
|
|
if xe.NeedFlush() {
|
|
return xe.Flush()
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
if err := enc.WriteToken(jsontext.BeginObject); err != nil {
|
|
return err
|
|
}
|
|
// A Go map guarantees that each entry has a unique key
|
|
// The only possibility of duplicates is due to invalid UTF-8.
|
|
if !mo.Flags.Get(jsonflags.AllowInvalidUTF8) {
|
|
xe.Tokens.Last.DisableNamespace()
|
|
}
|
|
if !mo.Flags.Get(jsonflags.Deterministic) || len(obj) <= 1 {
|
|
for name, val := range obj {
|
|
if err := enc.WriteToken(jsontext.String(name)); err != nil {
|
|
return err
|
|
}
|
|
if err := marshalValueAny(enc, val, mo); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
} else {
|
|
names := getStrings(len(obj))
|
|
var i int
|
|
for name := range obj {
|
|
(*names)[i] = name
|
|
i++
|
|
}
|
|
names.Sort()
|
|
for _, name := range *names {
|
|
if err := enc.WriteToken(jsontext.String(name)); err != nil {
|
|
return err
|
|
}
|
|
if err := marshalValueAny(enc, obj[name], mo); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
putStrings(names)
|
|
}
|
|
if err := enc.WriteToken(jsontext.EndObject); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// unmarshalObjectAny unmarshals a JSON object as a Go map[string]any.
|
|
// It panics if not decoding a JSON object.
|
|
func unmarshalObjectAny(dec *jsontext.Decoder, uo *jsonopts.Struct) (map[string]any, error) {
|
|
switch tok, err := dec.ReadToken(); {
|
|
case err != nil:
|
|
return nil, err
|
|
case tok.Kind() != '{':
|
|
panic("BUG: invalid kind: " + tok.Kind().String())
|
|
}
|
|
obj := make(map[string]any)
|
|
// A Go map guarantees that each entry has a unique key
|
|
// The only possibility of duplicates is due to invalid UTF-8.
|
|
if !uo.Flags.Get(jsonflags.AllowInvalidUTF8) {
|
|
export.Decoder(dec).Tokens.Last.DisableNamespace()
|
|
}
|
|
var errUnmarshal error
|
|
for dec.PeekKind() != '}' {
|
|
tok, err := dec.ReadToken()
|
|
if err != nil {
|
|
return obj, err
|
|
}
|
|
name := tok.String()
|
|
|
|
// Manually check for duplicate names.
|
|
if _, ok := obj[name]; ok {
|
|
// TODO: Unread the object name.
|
|
name := export.Decoder(dec).PreviousTokenOrValue()
|
|
err := newDuplicateNameError(dec.StackPointer(), nil, dec.InputOffset()-len64(name))
|
|
return obj, err
|
|
}
|
|
|
|
val, err := unmarshalValueAny(dec, uo)
|
|
obj[name] = val
|
|
if err != nil {
|
|
if isFatalError(err, uo.Flags) {
|
|
return obj, err
|
|
}
|
|
errUnmarshal = cmp.Or(err, errUnmarshal)
|
|
}
|
|
}
|
|
if _, err := dec.ReadToken(); err != nil {
|
|
return obj, err
|
|
}
|
|
return obj, errUnmarshal
|
|
}
|
|
|
|
// marshalArrayAny marshals a Go []any as a JSON array
|
|
// (or as a JSON null if nil and [jsonflags.FormatNilSliceAsNull]).
|
|
func marshalArrayAny(enc *jsontext.Encoder, arr []any, mo *jsonopts.Struct) error {
|
|
// Check for cycles.
|
|
xe := export.Encoder(enc)
|
|
if xe.Tokens.Depth() > startDetectingCyclesAfter {
|
|
v := reflect.ValueOf(arr)
|
|
if err := visitPointer(&xe.SeenPointers, v); err != nil {
|
|
return newMarshalErrorBefore(enc, sliceAnyType, err)
|
|
}
|
|
defer leavePointer(&xe.SeenPointers, v)
|
|
}
|
|
|
|
// Handle empty slices.
|
|
if len(arr) == 0 {
|
|
if mo.Flags.Get(jsonflags.FormatNilSliceAsNull) && arr == nil {
|
|
return enc.WriteToken(jsontext.Null)
|
|
}
|
|
// Optimize for marshaling an empty slice without any preceding whitespace.
|
|
if !mo.Flags.Get(jsonflags.AnyWhitespace) && !xe.Tokens.Last.NeedObjectName() {
|
|
xe.Buf = append(xe.Tokens.MayAppendDelim(xe.Buf, '['), "[]"...)
|
|
xe.Tokens.Last.Increment()
|
|
if xe.NeedFlush() {
|
|
return xe.Flush()
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
if err := enc.WriteToken(jsontext.BeginArray); err != nil {
|
|
return err
|
|
}
|
|
for _, val := range arr {
|
|
if err := marshalValueAny(enc, val, mo); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err := enc.WriteToken(jsontext.EndArray); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// unmarshalArrayAny unmarshals a JSON array as a Go []any.
|
|
// It panics if not decoding a JSON array.
|
|
func unmarshalArrayAny(dec *jsontext.Decoder, uo *jsonopts.Struct) ([]any, error) {
|
|
switch tok, err := dec.ReadToken(); {
|
|
case err != nil:
|
|
return nil, err
|
|
case tok.Kind() != '[':
|
|
panic("BUG: invalid kind: " + tok.Kind().String())
|
|
}
|
|
arr := []any{}
|
|
var errUnmarshal error
|
|
for dec.PeekKind() != ']' {
|
|
val, err := unmarshalValueAny(dec, uo)
|
|
arr = append(arr, val)
|
|
if err != nil {
|
|
if isFatalError(err, uo.Flags) {
|
|
return arr, err
|
|
}
|
|
errUnmarshal = cmp.Or(errUnmarshal, err)
|
|
}
|
|
}
|
|
if _, err := dec.ReadToken(); err != nil {
|
|
return arr, err
|
|
}
|
|
return arr, errUnmarshal
|
|
}
|