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

View as plain text