| // 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 ( | |
| "cmp" | |
| "errors" | |
| "fmt" | |
| "io" | |
| "reflect" | |
| "strconv" | |
| "strings" | |
| "sync" | |
| "encoding/json/internal/jsonflags" | |
| "encoding/json/internal/jsonopts" | |
| "encoding/json/internal/jsonwire" | |
| "encoding/json/jsontext" | |
| ) | |
| // ErrUnknownName indicates that a JSON object member could not be | |
| // unmarshaled because the name is not known to the target Go struct. | |
| // This error is directly wrapped within a [SemanticError] when produced. | |
| // | |
| // The name of an unknown JSON object member can be extracted as: | |
| // | |
| // err := ... | |
| // serr, ok := errors.AsType[*json.SemanticError](err) | |
| // if ok && serr.Err == json.ErrUnknownName { | |
| // ptr := serr.JSONPointer // JSON pointer to unknown name | |
| // name := ptr.LastToken() // unknown name itself | |
| // ... | |
| // } | |
| // | |
| // This error is only returned if [RejectUnknownMembers] is true. | |
| var ErrUnknownName = errors.New("unknown object member name") | |
| // errAmbiguousName indicates a JSON object member could be unmarshal | |
| // into multiple candidate Go struct fields. | |
| var errAmbiguousName = errors.New("ambiguous object member name") | |
| const errorPrefix = "json: " | |
| func isSemanticError(err error) bool { | |
| _, ok := err.(*SemanticError) | |
| return ok | |
| } | |
| func isSyntacticError(err error) bool { | |
| _, ok := err.(*jsontext.SyntacticError) | |
| return ok | |
| } | |
| // isFatalError reports whether this error must terminate arshaling. | |
| // All errors are considered fatal unless operating under | |
| // [jsonflags.ReportErrorsWithLegacySemantics] in which case only | |
| // syntactic errors and I/O errors are considered fatal. | |
| func isFatalError(err error, flags jsonflags.Flags) bool { | |
| return !flags.Get(jsonflags.ReportErrorsWithLegacySemantics) || | |
| isSyntacticError(err) || export.IsIOError(err) | |
| } | |
| // SemanticError describes an error determining the meaning | |
| // of JSON data as Go data, or vice versa. | |
| // | |
| // If a [Marshaler], [MarshalerTo], [Unmarshaler], or [UnmarshalerFrom] method | |
| // returns a SemanticError when called by the [json] package, | |
| // then the ByteOffset, JSONPointer, and GoType fields are automatically | |
| // populated by the calling context if they are the zero value. | |
| // | |
| // The contents of this error as produced by this package may change over time. | |
| type SemanticError struct { | |
| requireKeyedLiterals | |
| nonComparable | |
| action string // either "marshal" or "unmarshal" | |
| // ByteOffset indicates that an error occurred at or 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 jsontext.Pointer | |
| // JSONKind is the JSON kind that could not be handled. | |
| JSONKind jsontext.Kind // may be zero if unknown | |
| // JSONValue is the JSON number or string that could not be unmarshaled. | |
| // It is not populated during marshaling. | |
| JSONValue jsontext.Value // may be nil if irrelevant or 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 | |
| } | |
| // coder is implemented by [jsontext.Encoder] or [jsontext.Decoder]. | |
| type coder interface { | |
| StackPointer() jsontext.Pointer | |
| Options() Options | |
| } | |
| // newInvalidFormatError constructs a SemanticError because | |
| // the current type t cannot handle the provided options format. | |
| // This function must be called before producing or consuming the next value. | |
| // | |
| // If [jsonflags.ReportErrorsWithLegacySemantics] is specified, | |
| // then this automatically skips the next value when unmarshaling | |
| // to ensure that the value is fully consumed. | |
| func newInvalidFormatError(c coder, t reflect.Type) error { | |
| err := fmt.Errorf("invalid format flag %q", c.Options().(*jsonopts.Struct).Format) | |
| switch c := c.(type) { | |
| case *jsontext.Encoder: | |
| err = newMarshalErrorBefore(c, t, err) | |
| case *jsontext.Decoder: | |
| err = newUnmarshalErrorBeforeWithSkipping(c, t, err) | |
| } | |
| return err | |
| } | |
| // newMarshalErrorBefore wraps err in a SemanticError assuming that e | |
| // is positioned right before the next token or value, which causes an error. | |
| func newMarshalErrorBefore(e *jsontext.Encoder, t reflect.Type, err error) error { | |
| return &SemanticError{action: "marshal", GoType: t, Err: toUnexpectedEOF(err), | |
| ByteOffset: e.OutputOffset() + int64(export.Encoder(e).CountNextDelimWhitespace()), | |
| JSONPointer: jsontext.Pointer(export.Encoder(e).AppendStackPointer(nil, +1))} | |
| } | |
| // newUnmarshalErrorBefore wraps err in a SemanticError assuming that d | |
| // is positioned right before the next token or value, which causes an error. | |
| // It does not record the next JSON kind as this error is used to indicate | |
| // the receiving Go value is invalid to unmarshal into (and not a JSON error). | |
| // However, if [jsonflags.ReportErrorsWithLegacySemantics] is specified, | |
| // then it does record the next JSON kind for historical reporting reasons. | |
| func newUnmarshalErrorBefore(d *jsontext.Decoder, t reflect.Type, err error) error { | |
| var k jsontext.Kind | |
| if export.Decoder(d).Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { | |
| k = d.PeekKind() | |
| } | |
| return &SemanticError{action: "unmarshal", GoType: t, Err: toUnexpectedEOF(err), | |
| ByteOffset: d.InputOffset() + int64(export.Decoder(d).CountNextDelimWhitespace()), | |
| JSONPointer: jsontext.Pointer(export.Decoder(d).AppendStackPointer(nil, +1)), | |
| JSONKind: k} | |
| } | |
| // newUnmarshalErrorBeforeWithSkipping is like [newUnmarshalErrorBefore], | |
| // but automatically skips the next value if | |
| // [jsonflags.ReportErrorsWithLegacySemantics] is specified. | |
| func newUnmarshalErrorBeforeWithSkipping(d *jsontext.Decoder, t reflect.Type, err error) error { | |
| err = newUnmarshalErrorBefore(d, t, err) | |
| if export.Decoder(d).Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { | |
| if err2 := export.Decoder(d).SkipValue(); err2 != nil { | |
| return err2 | |
| } | |
| } | |
| return err | |
| } | |
| // newUnmarshalErrorAfter wraps err in a SemanticError assuming that d | |
| // is positioned right after the previous token or value, which caused an error. | |
| func newUnmarshalErrorAfter(d *jsontext.Decoder, t reflect.Type, err error) error { | |
| tokOrVal := export.Decoder(d).PreviousTokenOrValue() | |
| byteOffset := d.InputOffset() - int64(len(tokOrVal)) | |
| if export.Decoder(d).Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { | |
| // NOTE: In v1, the offset pointed to the end of the bad token or value. | |
| if k := jsontext.Value(tokOrVal).Kind(); k == '[' || k == '{' { | |
| byteOffset++ // add just the '[' or '{' | |
| } else { | |
| byteOffset += int64(len(tokOrVal)) | |
| } | |
| } | |
| return &SemanticError{action: "unmarshal", GoType: t, Err: toUnexpectedEOF(err), | |
| ByteOffset: byteOffset, | |
| JSONPointer: jsontext.Pointer(export.Decoder(d).AppendStackPointer(nil, -1)), | |
| JSONKind: jsontext.Value(tokOrVal).Kind()} | |
| } | |
| // newUnmarshalErrorAfterWithValue wraps err in a SemanticError assuming that d | |
| // is positioned right after the previous token or value, which caused an error. | |
| // It also stores a copy of the last JSON value if it is a string or number. | |
| func newUnmarshalErrorAfterWithValue(d *jsontext.Decoder, t reflect.Type, err error) error { | |
| serr := newUnmarshalErrorAfter(d, t, err).(*SemanticError) | |
| if serr.JSONKind == '"' || serr.JSONKind == '0' { | |
| serr.JSONValue = jsontext.Value(export.Decoder(d).PreviousTokenOrValue()).Clone() | |
| } | |
| return serr | |
| } | |
| // newUnmarshalErrorAfterWithSkipping is like [newUnmarshalErrorAfter], | |
| // but automatically skips the remainder of the current value if | |
| // [jsonflags.ReportErrorsWithLegacySemantics] is specified. | |
| func newUnmarshalErrorAfterWithSkipping(d *jsontext.Decoder, t reflect.Type, err error) error { | |
| err = newUnmarshalErrorAfter(d, t, err) | |
| if export.Decoder(d).Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { | |
| if err2 := export.Decoder(d).SkipValueRemainder(); err2 != nil { | |
| return err2 | |
| } | |
| } | |
| return err | |
| } | |
| // newSemanticErrorWithPosition wraps err in a SemanticError assuming that | |
| // the error occurred at the provided depth, and length. | |
| // If err is already a SemanticError, then position information is only | |
| // injected if it is currently unpopulated. | |
| // | |
| // If the position is unpopulated, it is ambiguous where the error occurred | |
| // in the user code, whether it was before or after the current position. | |
| // For the byte offset, we assume that the error occurred before the last read | |
| // token or value when decoding, or before the next value when encoding. | |
| // For the JSON pointer, we point to the parent object or array unless | |
| // we can be certain that it happened with an object member. | |
| // | |
| // This is used to annotate errors returned by user-provided | |
| // v2 MarshalJSON or UnmarshalJSON methods or functions. | |
| func newSemanticErrorWithPosition(c coder, t reflect.Type, prevDepth int, prevLength int64, err error) error { | |
| serr, _ := err.(*SemanticError) | |
| if serr == nil { | |
| serr = &SemanticError{Err: err} | |
| } | |
| serr.Err = toUnexpectedEOF(serr.Err) | |
| var currDepth int | |
| var currLength int64 | |
| var coderState interface{ AppendStackPointer([]byte, int) []byte } | |
| var offset int64 | |
| switch c := c.(type) { | |
| case *jsontext.Encoder: | |
| e := export.Encoder(c) | |
| serr.action = cmp.Or(serr.action, "marshal") | |
| currDepth, currLength = e.Tokens.DepthLength() | |
| offset = c.OutputOffset() + int64(export.Encoder(c).CountNextDelimWhitespace()) | |
| coderState = e | |
| case *jsontext.Decoder: | |
| d := export.Decoder(c) | |
| serr.action = cmp.Or(serr.action, "unmarshal") | |
| currDepth, currLength = d.Tokens.DepthLength() | |
| tokOrVal := d.PreviousTokenOrValue() | |
| offset = c.InputOffset() - int64(len(tokOrVal)) | |
| if (prevDepth == currDepth && prevLength == currLength) || len(tokOrVal) == 0 { | |
| // If no Read method was called in the user-defined method or | |
| // if the Peek method was called, then use the offset of the next value. | |
| offset = c.InputOffset() + int64(export.Decoder(c).CountNextDelimWhitespace()) | |
| } | |
| coderState = d | |
| } | |
| serr.ByteOffset = cmp.Or(serr.ByteOffset, offset) | |
| if serr.JSONPointer == "" { | |
| where := 0 // default to ambiguous positioning | |
| switch { | |
| case prevDepth == currDepth && prevLength+0 == currLength: | |
| where = +1 | |
| case prevDepth == currDepth && prevLength+1 == currLength: | |
| where = -1 | |
| } | |
| serr.JSONPointer = jsontext.Pointer(coderState.AppendStackPointer(nil, where)) | |
| } | |
| serr.GoType = cmp.Or(serr.GoType, t) | |
| return serr | |
| } | |
| // collapseSemanticErrors collapses double SemanticErrors at the outer levels | |
| // into a single SemanticError by preserving the inner error, | |
| // but prepending the ByteOffset and JSONPointer with the outer error. | |
| // | |
| // For example: | |
| // | |
| // collapseSemanticErrors(&SemanticError{ | |
| // ByteOffset: len64(`[0,{"alpha":[0,1,`), | |
| // JSONPointer: "/1/alpha/2", | |
| // GoType: reflect.TypeFor[outerType](), | |
| // Err: &SemanticError{ | |
| // ByteOffset: len64(`{"foo":"bar","fizz":[0,`), | |
| // JSONPointer: "/fizz/1", | |
| // GoType: reflect.TypeFor[innerType](), | |
| // Err: ..., | |
| // }, | |
| // }) | |
| // | |
| // results in: | |
| // | |
| // &SemanticError{ | |
| // ByteOffset: len64(`[0,{"alpha":[0,1,`) + len64(`{"foo":"bar","fizz":[0,`), | |
| // JSONPointer: "/1/alpha/2" + "/fizz/1", | |
| // GoType: reflect.TypeFor[innerType](), | |
| // Err: ..., | |
| // } | |
| // | |
| // This is used to annotate errors returned by user-provided | |
| // v1 MarshalJSON or UnmarshalJSON methods with precise position information | |
| // if they themselves happened to return a SemanticError. | |
| // Since MarshalJSON and UnmarshalJSON are not operating on the root JSON value, | |
| // their positioning must be relative to the nested JSON value | |
| // returned by UnmarshalJSON or passed to MarshalJSON. | |
| // Therefore, we can construct an absolute position by concatenating | |
| // the outer with the inner positions. | |
| // | |
| // Note that we do not use collapseSemanticErrors with user-provided functions | |
| // that take in an [jsontext.Encoder] or [jsontext.Decoder] since they contain | |
| // methods to report position relative to the root JSON value. | |
| // We assume user-constructed errors are correctly precise about position. | |
| func collapseSemanticErrors(err error) error { | |
| if serr1, ok := err.(*SemanticError); ok { | |
| if serr2, ok := serr1.Err.(*SemanticError); ok { | |
| serr2.ByteOffset = serr1.ByteOffset + serr2.ByteOffset | |
| serr2.JSONPointer = serr1.JSONPointer + serr2.JSONPointer | |
| *serr1 = *serr2 | |
| } | |
| } | |
| return err | |
| } | |
| func wrapErrUnsupported(err error, what string) error { | |
| if errors.Is(err, errors.ErrUnsupported) { | |
| return errors.New(what + " may not return errors.ErrUnsupported") | |
| } | |
| return err | |
| } | |
| // errorModalVerb is a modal verb like "cannot" or "unable to". | |
| // | |
| // Once per process, Hyrum-proof the error message by deliberately | |
| // switching between equivalent renderings of the same error message. | |
| // The randomization is tied to the Hyrum-proofing already applied | |
| // on map iteration in Go. | |
| var errorModalVerb = sync.OnceValue(func() string { | |
| for phrase := range map[string]struct{}{"cannot": {}, "unable to": {}} { | |
| return phrase // use whichever phrase we get in the first iteration | |
| } | |
| return "" | |
| }) | |
| func (e *SemanticError) Error() string { | |
| var sb strings.Builder | |
| sb.WriteString(errorPrefix) | |
| sb.WriteString(errorModalVerb()) | |
| // Format action. | |
| var preposition string | |
| switch e.action { | |
| case "marshal": | |
| sb.WriteString(" marshal") | |
| preposition = " from" | |
| case "unmarshal": | |
| sb.WriteString(" unmarshal") | |
| preposition = " into" | |
| default: | |
| sb.WriteString(" handle") | |
| preposition = " with" | |
| } | |
| // Format JSON kind. | |
| switch e.JSONKind { | |
| case 'n': | |
| sb.WriteString(" JSON null") | |
| case 'f', 't': | |
| sb.WriteString(" JSON boolean") | |
| case '"': | |
| sb.WriteString(" JSON string") | |
| case '0': | |
| sb.WriteString(" JSON number") | |
| case '{', '}': | |
| sb.WriteString(" JSON object") | |
| case '[', ']': | |
| sb.WriteString(" JSON array") | |
| default: | |
| if e.action == "" { | |
| preposition = "" | |
| } | |
| } | |
| if len(e.JSONValue) > 0 && len(e.JSONValue) < 100 { | |
| sb.WriteByte(' ') | |
| sb.Write(e.JSONValue) | |
| } | |
| // Format Go type. | |
| if e.GoType != nil { | |
| typeString := e.GoType.String() | |
| if len(typeString) > 100 { | |
| // An excessively long type string most likely occurs for | |
| // an anonymous struct declaration with many fields. | |
| // Reduce the noise by just printing the kind, | |
| // and optionally prepending it with the package name | |
| // if the struct happens to include an unexported field. | |
| typeString = e.GoType.Kind().String() | |
| if e.GoType.Kind() == reflect.Struct && e.GoType.Name() == "" { | |
| for i := range e.GoType.NumField() { | |
| if pkgPath := e.GoType.Field(i).PkgPath; pkgPath != "" { | |
| typeString = pkgPath[strings.LastIndexByte(pkgPath, '/')+len("/"):] + ".struct" | |
| break | |
| } | |
| } | |
| } | |
| } | |
| sb.WriteString(preposition) | |
| sb.WriteString(" Go ") | |
| sb.WriteString(typeString) | |
| } | |
| // Special handling for unknown names. | |
| if e.Err == ErrUnknownName || e.Err == errAmbiguousName { | |
| sb.WriteString(": ") | |
| sb.WriteString(e.Err.Error()) | |
| sb.WriteString(" ") | |
| sb.WriteString(strconv.Quote(e.JSONPointer.LastToken())) | |
| if parent := e.JSONPointer.Parent(); parent != "" { | |
| sb.WriteString(" within ") | |
| sb.WriteString(strconv.Quote(jsonwire.TruncatePointer(string(parent), 100))) | |
| } | |
| return sb.String() | |
| } | |
| // Format where. | |
| // Avoid printing if it overlaps with a wrapped SyntacticError. | |
| switch serr, _ := e.Err.(*jsontext.SyntacticError); { | |
| case e.JSONPointer != "": | |
| if serr == nil || !e.JSONPointer.Contains(serr.JSONPointer) { | |
| sb.WriteString(" within ") | |
| sb.WriteString(strconv.Quote(jsonwire.TruncatePointer(string(e.JSONPointer), 100))) | |
| } | |
| case e.ByteOffset > 0: | |
| if serr == nil || !(e.ByteOffset <= serr.ByteOffset) { | |
| sb.WriteString(" after offset ") | |
| sb.WriteString(strconv.FormatInt(e.ByteOffset, 10)) | |
| } | |
| } | |
| // Format underlying error. | |
| if e.Err != nil { | |
| errString := e.Err.Error() | |
| if isSyntacticError(e.Err) { | |
| errString = strings.TrimPrefix(errString, "jsontext: ") | |
| } | |
| sb.WriteString(": ") | |
| sb.WriteString(errString) | |
| } | |
| return sb.String() | |
| } | |
| func (e *SemanticError) Unwrap() error { | |
| return e.Err | |
| } | |
| func newDuplicateNameError(ptr jsontext.Pointer, quotedName []byte, offset int64) error { | |
| if quotedName != nil { | |
| name, _ := jsonwire.AppendUnquote(nil, quotedName) | |
| ptr = ptr.AppendToken(string(name)) | |
| } | |
| return &jsontext.SyntacticError{ | |
| ByteOffset: offset, | |
| JSONPointer: ptr, | |
| Err: jsontext.ErrDuplicateName, | |
| } | |
| } | |
| // toUnexpectedEOF converts [io.EOF] to [io.ErrUnexpectedEOF]. | |
| func toUnexpectedEOF(err error) error { | |
| if err == io.EOF { | |
| return io.ErrUnexpectedEOF | |
| } | |
| return err | |
| } |