You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
153 lines
5.0 KiB
153 lines
5.0 KiB
// 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 ( |
|
"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. |
|
// The Val used to be populated with the reflect.Value, |
|
// but this is no longer supported. |
|
errStr := err.Err.Error() |
|
if err.Err == internal.ErrCycle && err.GoType != nil { |
|
errStr += " via " + err.GoType.String() |
|
} |
|
errStr = strings.TrimPrefix(errStr, "unsupported value: ") |
|
return &UnsupportedValueError{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()) |
|
} |
|
|
|
// 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 a 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) |
|
}
|
|
|