| // Copyright 2024 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 ( | |
| "errors" | |
| "fmt" | |
| "reflect" | |
| "strconv" | |
| "strings" | |
| "encoding/json/internal" | |
| "encoding/json/jsontext" | |
| jsonv2 "encoding/json/v2" | |
| ) | |
| // Inject functionality into v2 to properly handle v1 types. | |
| func init() { | |
| internal.TransformMarshalError = transformMarshalError | |
| internal.TransformUnmarshalError = transformUnmarshalError | |
| internal.NewMarshalerError = func(val any, err error, funcName string) error { | |
| return &MarshalerError{reflect.TypeOf(val), err, funcName} | |
| } | |
| internal.NewRawNumber = func() any { return new(Number) } | |
| internal.RawNumberOf = func(b []byte) any { return Number(b) } | |
| } | |
| func transformMarshalError(root any, err error) error { | |
| // Historically, errors returned from Marshal methods were wrapped | |
| // in a [MarshalerError]. This is directly performed by the v2 package | |
| // via the injected [internal.NewMarshalerError] constructor | |
| // while operating under [ReportErrorsWithLegacySemantics]. | |
| // Note that errors from a Marshal method were always wrapped, | |
| // even if wrapped for multiple layers. | |
| if err, ok := err.(*jsonv2.SemanticError); err != nil { | |
| if err.Err == nil { | |
| // Historically, this was only reported for unserializable types | |
| // like complex numbers, channels, functions, and unsafe.Pointers. | |
| return &UnsupportedTypeError{Type: err.GoType} | |
| } else { | |
| // Historically, this was only reported for NaN or ±Inf values | |
| // and cycles detected in the value. | |
| var v reflect.Value | |
| if err, ok := errors.AsType[*internal.ValueError](err.Err); ok { | |
| v = reflect.ValueOf(err.Val) | |
| } | |
| errStr := err.Err.Error() | |
| if errors.Is(err.Err, internal.ErrCycle) && err.GoType != nil { | |
| errStr += " via " + err.GoType.String() | |
| } | |
| errStr = strings.TrimPrefix(errStr, "unsupported value: ") | |
| return &UnsupportedValueError{Value: v, Str: errStr} | |
| } | |
| } else if ok { | |
| return (*UnsupportedValueError)(nil) | |
| } | |
| if err, _ := err.(*MarshalerError); err != nil { | |
| err.Err = transformSyntacticError(err.Err) | |
| return err | |
| } | |
| return transformSyntacticError(err) | |
| } | |
| func transformUnmarshalError(root any, err error) error { | |
| // Historically, errors from Unmarshal methods were never wrapped and | |
| // returned verbatim while operating under [ReportErrorsWithLegacySemantics]. | |
| if err, ok := err.(*jsonv2.SemanticError); err != nil { | |
| if err.Err == internal.ErrNonNilReference { | |
| return &InvalidUnmarshalError{err.GoType} | |
| } | |
| if err.Err == jsonv2.ErrUnknownName { | |
| return fmt.Errorf("json: unknown field %q", err.JSONPointer.LastToken()) | |
| } | |
| if err.Err == internal.ErrNilInterface { | |
| err.Err = nil // non-descriptive for historical reasons | |
| } | |
| // Historically, UnmarshalTypeError has always been inconsistent | |
| // about how it reported position information. | |
| // | |
| // The Struct field now points to the root type, | |
| // rather than some intermediate struct in the path. | |
| // This better matches the original intent of the field based | |
| // on how the Error message was formatted. | |
| // | |
| // For a representation closer to the historical representation, | |
| // we switch the '/'-delimited representation of a JSON pointer | |
| // to use a '.'-delimited representation. This may be ambiguous, | |
| // but the prior representation was always ambiguous as well. | |
| // Users that care about precise positions should use v2 errors | |
| // by disabling [ReportErrorsWithLegacySemantics]. | |
| // | |
| // The introduction of an Err field is new to the v1-to-v2 migration | |
| // and allows us to preserve stronger error information | |
| // that may be surfaced by the v2 package. | |
| // | |
| // See https://go.dev/issue/43126 | |
| var value string | |
| switch err.JSONKind { | |
| case 'n', '"', '0': | |
| value = err.JSONKind.String() | |
| case 'f', 't': | |
| value = "bool" | |
| case '[', ']': | |
| value = "array" | |
| case '{', '}': | |
| value = "object" | |
| } | |
| if len(err.JSONValue) > 0 { | |
| isStrconvError := err.Err == strconv.ErrRange || err.Err == strconv.ErrSyntax | |
| isNumericKind := func(t reflect.Type) bool { | |
| if t == nil { | |
| return false | |
| } | |
| switch t.Kind() { | |
| case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, | |
| reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, | |
| reflect.Float32, reflect.Float64: | |
| return true | |
| } | |
| return false | |
| } | |
| if isStrconvError && isNumericKind(err.GoType) { | |
| value = "number" | |
| if err.JSONKind == '"' { | |
| err.JSONValue, _ = jsontext.AppendUnquote(nil, err.JSONValue) | |
| } | |
| err.Err = nil | |
| } | |
| value += " " + string(err.JSONValue) | |
| } | |
| var rootName string | |
| if t := reflect.TypeOf(root); t != nil && err.JSONPointer != "" { | |
| if t.Kind() == reflect.Pointer { | |
| t = t.Elem() | |
| } | |
| rootName = t.Name() | |
| } | |
| fieldPath := string(err.JSONPointer) | |
| fieldPath = strings.TrimPrefix(fieldPath, "/") | |
| fieldPath = strings.ReplaceAll(fieldPath, "/", ".") | |
| return &UnmarshalTypeError{ | |
| Value: value, | |
| Type: err.GoType, | |
| Offset: err.ByteOffset, | |
| Struct: rootName, | |
| Field: fieldPath, | |
| Err: transformSyntacticError(err.Err), | |
| } | |
| } else if ok { | |
| return (*UnmarshalTypeError)(nil) | |
| } | |
| return transformSyntacticError(err) | |
| } |