| // Copyright 2020 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 |
| |
| package json |
| |
| import ( |
| "bytes" |
| "errors" |
| "io" |
| "reflect" |
| "slices" |
| |
| "encoding/json/internal/jsonflags" |
| "encoding/json/internal/jsonopts" |
| "encoding/json/internal/jsonwire" |
| "encoding/json/jsontext" |
| ) |
| |
| // This package supports "inlining" a Go struct field, where the contents |
| // of the serialized field (which must be a JSON object) are treated as if |
| // they are part of the parent Go struct (which represents a JSON object). |
| // |
| // Generally, inlined fields are of a Go struct type, where the fields of the |
| // nested struct are virtually hoisted up to the parent struct using rules |
| // similar to how Go embedding works (but operating within the JSON namespace). |
| // |
| // However, inlined fields may also be of a Go map type with a string key or |
| // a jsontext.Value. Such inlined fields are called "fallback" fields since they |
| // represent any arbitrary JSON object member. Explicitly named fields take |
| // precedence over the inlined fallback. Only one inlined fallback is allowed. |
| |
| var errRawInlinedNotObject = errors.New("inlined raw value must be a JSON object") |
| |
| var jsontextValueType = reflect.TypeFor[jsontext.Value]() |
| |
| // marshalInlinedFallbackAll marshals all the members in an inlined fallback. |
| func marshalInlinedFallbackAll(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct, f *structField, insertUnquotedName func([]byte) bool) error { |
| v := addressableValue{va.Field(f.index0), va.forcedAddr} // addressable if struct value is addressable |
| if len(f.index) > 0 { |
| v = v.fieldByIndex(f.index, false) |
| if !v.IsValid() { |
| return nil // implies a nil inlined field |
| } |
| } |
| v = v.indirect(false) |
| if !v.IsValid() { |
| return nil |
| } |
| |
| if v.Type() == jsontextValueType { |
| b, _ := reflect.TypeAssert[jsontext.Value](v.Value) |
| if len(b) == 0 { // TODO: Should this be nil? What if it were all whitespace? |
| return nil |
| } |
| |
| dec := export.GetBufferedDecoder(b) |
| defer export.PutBufferedDecoder(dec) |
| xd := export.Decoder(dec) |
| xd.Flags.Set(jsonflags.AllowDuplicateNames | jsonflags.AllowInvalidUTF8 | 1) |
| |
| tok, err := dec.ReadToken() |
| if err != nil { |
| if err == io.EOF { |
| err = io.ErrUnexpectedEOF |
| } |
| return newMarshalErrorBefore(enc, v.Type(), err) |
| } |
| if tok.Kind() != '{' { |
| return newMarshalErrorBefore(enc, v.Type(), errRawInlinedNotObject) |
| } |
| for dec.PeekKind() != '}' { |
| // Parse the JSON object name. |
| var flags jsonwire.ValueFlags |
| val, err := xd.ReadValue(&flags) |
| if err != nil { |
| return newMarshalErrorBefore(enc, v.Type(), err) |
| } |
| if insertUnquotedName != nil { |
| name := jsonwire.UnquoteMayCopy(val, flags.IsVerbatim()) |
| if !insertUnquotedName(name) { |
| return newDuplicateNameError(enc.StackPointer().Parent(), val, enc.OutputOffset()) |
| } |
| } |
| if err := enc.WriteValue(val); err != nil { |
| return err |
| } |
| |
| // Parse the JSON object value. |
| val, err = xd.ReadValue(&flags) |
| if err != nil { |
| return newMarshalErrorBefore(enc, v.Type(), err) |
| } |
| if err := enc.WriteValue(val); err != nil { |
| return err |
| } |
| } |
| if _, err := dec.ReadToken(); err != nil { |
| return newMarshalErrorBefore(enc, v.Type(), err) |
| } |
| if err := xd.CheckEOF(); err != nil { |
| return newMarshalErrorBefore(enc, v.Type(), err) |
| } |
| return nil |
| } else { |
| m := v // must be a map[~string]V |
| n := m.Len() |
| if n == 0 { |
| return nil |
| } |
| mk := newAddressableValue(m.Type().Key()) |
| mv := newAddressableValue(m.Type().Elem()) |
| marshalKey := func(mk addressableValue) error { |
| b, err := jsonwire.AppendQuote(enc.AvailableBuffer(), mk.String(), &mo.Flags) |
| if err != nil { |
| return newMarshalErrorBefore(enc, m.Type().Key(), err) |
| } |
| if insertUnquotedName != nil { |
| isVerbatim := bytes.IndexByte(b, '\\') < 0 |
| name := jsonwire.UnquoteMayCopy(b, isVerbatim) |
| if !insertUnquotedName(name) { |
| return newDuplicateNameError(enc.StackPointer().Parent(), b, enc.OutputOffset()) |
| } |
| } |
| return enc.WriteValue(b) |
| } |
| marshalVal := f.fncs.marshal |
| if mo.Marshalers != nil { |
| marshalVal, _ = mo.Marshalers.(*Marshalers).lookup(marshalVal, mv.Type()) |
| } |
| if !mo.Flags.Get(jsonflags.Deterministic) || n <= 1 { |
| for iter := m.MapRange(); iter.Next(); { |
| mk.SetIterKey(iter) |
| if err := marshalKey(mk); err != nil { |
| return err |
| } |
| mv.Set(iter.Value()) |
| if err := marshalVal(enc, mv, mo); err != nil { |
| return err |
| } |
| } |
| } else { |
| names := getStrings(n) |
| for i, iter := 0, m.Value.MapRange(); i < n && iter.Next(); i++ { |
| mk.SetIterKey(iter) |
| (*names)[i] = mk.String() |
| } |
| slices.Sort(*names) |
| for _, name := range *names { |
| mk.SetString(name) |
| if err := marshalKey(mk); err != nil { |
| return err |
| } |
| // TODO(https://go.dev/issue/57061): Use mv.SetMapIndexOf. |
| mv.Set(m.MapIndex(mk.Value)) |
| if err := marshalVal(enc, mv, mo); err != nil { |
| return err |
| } |
| } |
| putStrings(names) |
| } |
| return nil |
| } |
| } |
| |
| // unmarshalInlinedFallbackNext unmarshals only the next member in an inlined fallback. |
| func unmarshalInlinedFallbackNext(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct, f *structField, quotedName, unquotedName []byte) error { |
| v := addressableValue{va.Field(f.index0), va.forcedAddr} // addressable if struct value is addressable |
| if len(f.index) > 0 { |
| v = v.fieldByIndex(f.index, true) |
| } |
| v = v.indirect(true) |
| |
| if v.Type() == jsontextValueType { |
| b, _ := reflect.TypeAssert[*jsontext.Value](v.Addr()) |
| if len(*b) == 0 { // TODO: Should this be nil? What if it were all whitespace? |
| *b = append(*b, '{') |
| } else { |
| *b = jsonwire.TrimSuffixWhitespace(*b) |
| if jsonwire.HasSuffixByte(*b, '}') { |
| // TODO: When merging into an object for the first time, |
| // should we verify that it is valid? |
| *b = jsonwire.TrimSuffixByte(*b, '}') |
| *b = jsonwire.TrimSuffixWhitespace(*b) |
| if !jsonwire.HasSuffixByte(*b, ',') && !jsonwire.HasSuffixByte(*b, '{') { |
| *b = append(*b, ',') |
| } |
| } else { |
| return newUnmarshalErrorAfterWithSkipping(dec, v.Type(), errRawInlinedNotObject) |
| } |
| } |
| *b = append(*b, quotedName...) |
| *b = append(*b, ':') |
| val, err := dec.ReadValue() |
| if err != nil { |
| return err |
| } |
| *b = append(*b, val...) |
| *b = append(*b, '}') |
| return nil |
| } else { |
| name := string(unquotedName) // TODO: Intern this? |
| |
| m := v // must be a map[~string]V |
| if m.IsNil() { |
| m.Set(reflect.MakeMap(m.Type())) |
| } |
| mk := reflect.ValueOf(name) |
| if mkt := m.Type().Key(); mkt != stringType { |
| mk = mk.Convert(mkt) |
| } |
| mv := newAddressableValue(m.Type().Elem()) // TODO: Cache across calls? |
| if v2 := m.MapIndex(mk); v2.IsValid() { |
| mv.Set(v2) |
| } |
| |
| unmarshal := f.fncs.unmarshal |
| if uo.Unmarshalers != nil { |
| unmarshal, _ = uo.Unmarshalers.(*Unmarshalers).lookup(unmarshal, mv.Type()) |
| } |
| err := unmarshal(dec, mv, uo) |
| m.SetMapIndex(mk, mv.Value) |
| if err != nil { |
| return err |
| } |
| return nil |
| } |
| } |