// 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.jsonv2package jsonimport ()// 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 := ...// var serr json.SemanticError// if errors.As(err, &serr) && 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.varErrUnknownName = errors.New("unknown object member name")const errorPrefix = "json: "func isSemanticError( error) bool { , := .(*SemanticError)return}func isSyntacticError( error) bool { , := .(*jsontext.SyntacticError)return}// isFatalError reports whether this error must terminate asharling.// 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( error, jsonflags.Flags) bool {return !.Get(jsonflags.ReportErrorsWithLegacySemantics) ||isSyntacticError() || export.IsIOError()}// SemanticError describes an error determining the meaning// of JSON data as Go data or vice-versa.//// The contents of this error as produced by this package may change over time.typeSemanticErrorstruct {requireKeyedLiteralsnonComparable action string// either "marshal" or "unmarshal"// ByteOffset indicates that an error occurred 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 }// newInvalidFormatError wraps err in a SemanticError because// the current type t cannot handle the provided options format.// This error 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( coder, reflect.Type, *jsonopts.Struct) error { := fmt.Errorf("invalid format flag %q", .Format)switch c := .(type) {case *jsontext.Encoder: = newMarshalErrorBefore(, , )case *jsontext.Decoder: = newUnmarshalErrorBeforeWithSkipping(, , , ) }return}// newMarshalErrorBefore wraps err in a SemanticError assuming that e// is positioned right before the next token or value, which causes an error.func newMarshalErrorBefore( *jsontext.Encoder, reflect.Type, error) error {return &SemanticError{action: "marshal", GoType: , Err: ,ByteOffset: .OutputOffset() + int64(export.Encoder().CountNextDelimWhitespace()),JSONPointer: jsontext.Pointer(export.Encoder().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).func newUnmarshalErrorBefore( *jsontext.Decoder, reflect.Type, error) error {return &SemanticError{action: "unmarshal", GoType: , Err: ,ByteOffset: .InputOffset() + int64(export.Decoder().CountNextDelimWhitespace()),JSONPointer: jsontext.Pointer(export.Decoder().AppendStackPointer(nil, +1))}}// newUnmarshalErrorBeforeWithSkipping is like [newUnmarshalErrorBefore],// but automatically skips the next value if// [jsonflags.ReportErrorsWithLegacySemantics] is specified.func newUnmarshalErrorBeforeWithSkipping( *jsontext.Decoder, *jsonopts.Struct, reflect.Type, error) error { = newUnmarshalErrorBefore(, , )if .Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {if := export.Decoder().SkipValue(); != nil {return } }return}// newUnmarshalErrorAfter wraps err in a SemanticError assuming that d// is positioned right after the previous token or value, which caused an error.func newUnmarshalErrorAfter( *jsontext.Decoder, reflect.Type, error) error { := export.Decoder().PreviousTokenOrValue()return &SemanticError{action: "unmarshal", GoType: , Err: ,ByteOffset: .InputOffset() - int64(len()),JSONPointer: jsontext.Pointer(export.Decoder().AppendStackPointer(nil, -1)),JSONKind: jsontext.Value().Kind()}}// newUnmarshalErrorAfter 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( *jsontext.Decoder, reflect.Type, error) error { := newUnmarshalErrorAfter(, , ).(*SemanticError)if .JSONKind == '"' || .JSONKind == '0' { .JSONValue = jsontext.Value(export.Decoder().PreviousTokenOrValue()).Clone() }return}// newUnmarshalErrorAfterWithSkipping is like [newUnmarshalErrorAfter],// but automatically skips the remainder of the current value if// [jsonflags.ReportErrorsWithLegacySemantics] is specified.func newUnmarshalErrorAfterWithSkipping( *jsontext.Decoder, *jsonopts.Struct, reflect.Type, error) error { = newUnmarshalErrorAfter(, , )if .Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {if := export.Decoder().SkipValueRemainder(); != nil {return } }return}// 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( coder, reflect.Type, int, int64, error) error { , := .(*SemanticError)if == nil { = &SemanticError{Err: } }varintvarint64varinterface{ ([]byte, int) []byte }varint64switch c := .(type) {case *jsontext.Encoder: := export.Encoder() .action = cmp.Or(.action, "marshal") , = .Tokens.DepthLength() = .OutputOffset() + int64(export.Encoder().CountNextDelimWhitespace()) = case *jsontext.Decoder: := export.Decoder() .action = cmp.Or(.action, "unmarshal") , = .Tokens.DepthLength() := .PreviousTokenOrValue() = .InputOffset() - int64(len())if ( == && == ) || len() == 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. = .InputOffset() + int64(export.Decoder().CountNextDelimWhitespace()) } = } .ByteOffset = cmp.Or(.ByteOffset, )if .JSONPointer == "" { := 0// default to ambiguous positioningswitch {case == && +0 == : = +1case == && +1 == : = -1 } .JSONPointer = jsontext.Pointer(.(nil, )) } .GoType = cmp.Or(.GoType, )return}// 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( error) error {if , := .(*SemanticError); {if , := .Err.(*SemanticError); { .ByteOffset = .ByteOffset + .ByteOffset .JSONPointer = .JSONPointer + .JSONPointer * = * } }return}// 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 := rangemap[string]struct{}{"cannot": {}, "unable to": {}} {return// use whichever phrase we get in the first iteration }return""})func ( *SemanticError) () string {varstrings.Builder .WriteString(errorPrefix) .WriteString(errorModalVerb())// Format action.varstringswitch .action {case"marshal": .WriteString(" marshal") = " from"case"unmarshal": .WriteString(" unmarshal") = " into"default: .WriteString(" handle") = " with" }// Format JSON kind.switch .JSONKind {case'n': .WriteString(" JSON null")case'f', 't': .WriteString(" JSON boolean")case'"': .WriteString(" JSON string")case'0': .WriteString(" JSON number")case'{', '}': .WriteString(" JSON object")case'[', ']': .WriteString(" JSON array")default:if .action == "" { = "" } }iflen(.JSONValue) > 0 && len(.JSONValue) < 100 { .WriteByte(' ') .Write(.JSONValue) }// Format Go type.if .GoType != nil { := .GoType.String()iflen() > 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. = .GoType.Kind().String()if .GoType.Kind() == reflect.Struct && .GoType.Name() == "" {for := range .GoType.NumField() {if := .GoType.Field().PkgPath; != "" { = [strings.LastIndexByte(, '/')+len("/"):] + ".struct"break } } } } .WriteString() .WriteString(" Go ") .WriteString() }// Special handling for unknown names.if .Err == ErrUnknownName { .WriteString(": ") .WriteString(ErrUnknownName.Error()) .WriteString(" ") .WriteString(strconv.Quote(.JSONPointer.LastToken()))if := .JSONPointer.Parent(); != "" { .WriteString(" within ") .WriteString(strconv.Quote(jsonwire.TruncatePointer(string(), 100))) }return .String() }// Format where. // Avoid printing if it overlaps with a wrapped SyntacticError.switch , := .Err.(*jsontext.SyntacticError); {case .JSONPointer != "":if == nil || !.JSONPointer.Contains(.JSONPointer) { .WriteString(" within ") .WriteString(strconv.Quote(jsonwire.TruncatePointer(string(.JSONPointer), 100))) }case .ByteOffset > 0:if == nil || !(.ByteOffset <= .ByteOffset) { .WriteString(" after offset ") .WriteString(strconv.FormatInt(.ByteOffset, 10)) } }// Format underlying error.if .Err != nil { := .Err.Error()ifisSyntacticError(.Err) { = strings.TrimPrefix(, "jsontext: ") } .WriteString(": ") .WriteString() }return .String()}func ( *SemanticError) () error {return .Err}func newDuplicateNameError( jsontext.Pointer, []byte, int64) error {if != nil { , := jsonwire.AppendUnquote(nil, ) = .AppendToken(string()) }return &jsontext.SyntacticError{ByteOffset: ,JSONPointer: ,Err: jsontext.ErrDuplicateName, }}
The pages are generated with Goldsv0.7.7-preview. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.