Source file src/encoding/json/v2_inject.go

     1  // Copyright 2024 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build goexperiment.jsonv2
     6  
     7  package json
     8  
     9  import (
    10  	"errors"
    11  	"fmt"
    12  	"reflect"
    13  	"strconv"
    14  	"strings"
    15  
    16  	"encoding/json/internal"
    17  	"encoding/json/jsontext"
    18  	jsonv2 "encoding/json/v2"
    19  )
    20  
    21  // Inject functionality into v2 to properly handle v1 types.
    22  func init() {
    23  	internal.TransformMarshalError = transformMarshalError
    24  	internal.TransformUnmarshalError = transformUnmarshalError
    25  	internal.NewMarshalerError = func(val any, err error, funcName string) error {
    26  		return &MarshalerError{reflect.TypeOf(val), err, funcName}
    27  	}
    28  
    29  	internal.NewRawNumber = func() any { return new(Number) }
    30  	internal.RawNumberOf = func(b []byte) any { return Number(b) }
    31  }
    32  
    33  func transformMarshalError(root any, err error) error {
    34  	// Historically, errors returned from Marshal methods were wrapped
    35  	// in a [MarshalerError]. This is directly performed by the v2 package
    36  	// via the injected [internal.NewMarshalerError] constructor
    37  	// while operating under [ReportErrorsWithLegacySemantics].
    38  	// Note that errors from a Marshal method were always wrapped,
    39  	// even if wrapped for multiple layers.
    40  	if err, ok := err.(*jsonv2.SemanticError); err != nil {
    41  		if err.Err == nil {
    42  			// Historically, this was only reported for unserializable types
    43  			// like complex numbers, channels, functions, and unsafe.Pointers.
    44  			return &UnsupportedTypeError{Type: err.GoType}
    45  		} else {
    46  			// Historically, this was only reported for NaN or ±Inf values
    47  			// and cycles detected in the value.
    48  			var v reflect.Value
    49  			if err, ok := errors.AsType[*internal.ValueError](err.Err); ok {
    50  				v = reflect.ValueOf(err.Val)
    51  			}
    52  			errStr := err.Err.Error()
    53  			if err.Err == internal.ErrCycle && err.GoType != nil {
    54  				errStr += " via " + err.GoType.String()
    55  			}
    56  			errStr = strings.TrimPrefix(errStr, "unsupported value: ")
    57  			return &UnsupportedValueError{Value: v, Str: errStr}
    58  		}
    59  	} else if ok {
    60  		return (*UnsupportedValueError)(nil)
    61  	}
    62  	if err, _ := err.(*MarshalerError); err != nil {
    63  		err.Err = transformSyntacticError(err.Err)
    64  		return err
    65  	}
    66  	return transformSyntacticError(err)
    67  }
    68  
    69  func transformUnmarshalError(root any, err error) error {
    70  	// Historically, errors from Unmarshal methods were never wrapped and
    71  	// returned verbatim while operating under [ReportErrorsWithLegacySemantics].
    72  	if err, ok := err.(*jsonv2.SemanticError); err != nil {
    73  		if err.Err == internal.ErrNonNilReference {
    74  			return &InvalidUnmarshalError{err.GoType}
    75  		}
    76  		if err.Err == jsonv2.ErrUnknownName {
    77  			return fmt.Errorf("json: unknown field %q", err.JSONPointer.LastToken())
    78  		}
    79  		if err.Err == internal.ErrNilInterface {
    80  			err.Err = nil // non-descriptive for historical reasons
    81  		}
    82  
    83  		// Historically, UnmarshalTypeError has always been inconsistent
    84  		// about how it reported position information.
    85  		//
    86  		// The Struct field now points to the root type,
    87  		// rather than some intermediate struct in the path.
    88  		// This better matches the original intent of the field based
    89  		// on how the Error message was formatted.
    90  		//
    91  		// For a representation closer to the historical representation,
    92  		// we switch the '/'-delimited representation of a JSON pointer
    93  		// to use a '.'-delimited representation. This may be ambiguous,
    94  		// but the prior representation was always ambiguous as well.
    95  		// Users that care about precise positions should use v2 errors
    96  		// by disabling [ReportErrorsWithLegacySemantics].
    97  		//
    98  		// The introduction of an Err field is new to the v1-to-v2 migration
    99  		// and allows us to preserve stronger error information
   100  		// that may be surfaced by the v2 package.
   101  		//
   102  		// See https://go.dev/issue/43126
   103  		var value string
   104  		switch err.JSONKind {
   105  		case 'n', '"', '0':
   106  			value = err.JSONKind.String()
   107  		case 'f', 't':
   108  			value = "bool"
   109  		case '[', ']':
   110  			value = "array"
   111  		case '{', '}':
   112  			value = "object"
   113  		}
   114  		if len(err.JSONValue) > 0 {
   115  			isStrconvError := err.Err == strconv.ErrRange || err.Err == strconv.ErrSyntax
   116  			isNumericKind := func(t reflect.Type) bool {
   117  				if t == nil {
   118  					return false
   119  				}
   120  				switch t.Kind() {
   121  				case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
   122  					reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
   123  					reflect.Float32, reflect.Float64:
   124  					return true
   125  				}
   126  				return false
   127  			}
   128  			if isStrconvError && isNumericKind(err.GoType) {
   129  				value = "number"
   130  				if err.JSONKind == '"' {
   131  					err.JSONValue, _ = jsontext.AppendUnquote(nil, err.JSONValue)
   132  				}
   133  				err.Err = nil
   134  			}
   135  			value += " " + string(err.JSONValue)
   136  		}
   137  		var rootName string
   138  		if t := reflect.TypeOf(root); t != nil && err.JSONPointer != "" {
   139  			if t.Kind() == reflect.Pointer {
   140  				t = t.Elem()
   141  			}
   142  			rootName = t.Name()
   143  		}
   144  		fieldPath := string(err.JSONPointer)
   145  		fieldPath = strings.TrimPrefix(fieldPath, "/")
   146  		fieldPath = strings.ReplaceAll(fieldPath, "/", ".")
   147  		return &UnmarshalTypeError{
   148  			Value:  value,
   149  			Type:   err.GoType,
   150  			Offset: err.ByteOffset,
   151  			Struct: rootName,
   152  			Field:  fieldPath,
   153  			Err:    transformSyntacticError(err.Err),
   154  		}
   155  	} else if ok {
   156  		return (*UnmarshalTypeError)(nil)
   157  	}
   158  	return transformSyntacticError(err)
   159  }
   160  

View as plain text