Source file src/encoding/json/v2_diff_test.go

     1  // Copyright 2020 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_test
     8  
     9  import (
    10  	"errors"
    11  	"path"
    12  	"reflect"
    13  	"strings"
    14  	"testing"
    15  	"time"
    16  
    17  	jsonv1 "encoding/json"
    18  	"encoding/json/jsontext"
    19  	jsonv2 "encoding/json/v2"
    20  )
    21  
    22  // NOTE: This file serves as a list of semantic differences between v1 and v2.
    23  // Each test explains how v1 behaves, how v2 behaves, and
    24  // a rationale for why the behavior was changed.
    25  
    26  var jsonPackages = []struct {
    27  	Version   string
    28  	Marshal   func(any) ([]byte, error)
    29  	Unmarshal func([]byte, any) error
    30  }{
    31  	{"v1", jsonv1.Marshal, jsonv1.Unmarshal},
    32  	{"v2",
    33  		func(in any) ([]byte, error) { return jsonv2.Marshal(in) },
    34  		func(in []byte, out any) error { return jsonv2.Unmarshal(in, out) }},
    35  }
    36  
    37  // In v1, unmarshal matches struct fields using a case-insensitive match.
    38  // In v2, unmarshal matches struct fields using a case-sensitive match.
    39  //
    40  // Case-insensitive matching is a surprising default and
    41  // incurs significant performance cost when unmarshaling unknown fields.
    42  // In v2, we can opt into v1-like behavior with the `case:ignore` tag option.
    43  // The case-insensitive matching performed by v2 is looser than that of v1
    44  // where it also ignores dashes and underscores.
    45  // This allows v2 to match fields regardless of whether the name is in
    46  // snake_case, camelCase, or kebab-case.
    47  //
    48  // Related issue:
    49  //
    50  //	https://go.dev/issue/14750
    51  func TestCaseSensitivity(t *testing.T) {
    52  	type Fields struct {
    53  		FieldA bool
    54  		FieldB bool `json:"fooBar"`
    55  		FieldC bool `json:"fizzBuzz,case:ignore"` // `case:ignore` is used by v2 to explicitly enable case-insensitive matching
    56  	}
    57  
    58  	for _, json := range jsonPackages {
    59  		t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) {
    60  			// This is a mapping from Go field names to JSON member names to
    61  			// whether the JSON member name would match the Go field name.
    62  			type goName = string
    63  			type jsonName = string
    64  			onlyV1 := json.Version == "v1"
    65  			onlyV2 := json.Version == "v2"
    66  			allMatches := map[goName]map[jsonName]bool{
    67  				"FieldA": {
    68  					"FieldA": true,   // exact match
    69  					"fielda": onlyV1, // v1 is case-insensitive by default
    70  					"fieldA": onlyV1, // v1 is case-insensitive by default
    71  					"FIELDA": onlyV1, // v1 is case-insensitive by default
    72  					"FieldB": false,
    73  					"FieldC": false,
    74  				},
    75  				"FieldB": {
    76  					"fooBar":   true,   // exact match for explicitly specified JSON name
    77  					"FooBar":   onlyV1, // v1 is case-insensitive even if an explicit JSON name is provided
    78  					"foobar":   onlyV1, // v1 is case-insensitive even if an explicit JSON name is provided
    79  					"FOOBAR":   onlyV1, // v1 is case-insensitive even if an explicit JSON name is provided
    80  					"fizzBuzz": false,
    81  					"FieldA":   false,
    82  					"FieldB":   false, // explicit JSON name means that the Go field name is not used for matching
    83  					"FieldC":   false,
    84  				},
    85  				"FieldC": {
    86  					"fizzBuzz":  true,   // exact match for explicitly specified JSON name
    87  					"fizzbuzz":  true,   // v2 is case-insensitive due to `case:ignore` tag
    88  					"FIZZBUZZ":  true,   // v2 is case-insensitive due to `case:ignore` tag
    89  					"fizz_buzz": onlyV2, // case-insensitivity in v2 ignores dashes and underscores
    90  					"fizz-buzz": onlyV2, // case-insensitivity in v2 ignores dashes and underscores
    91  					"fooBar":    false,
    92  					"FieldA":    false,
    93  					"FieldC":    false, // explicit JSON name means that the Go field name is not used for matching
    94  					"FieldB":    false,
    95  				},
    96  			}
    97  
    98  			for goFieldName, matches := range allMatches {
    99  				for jsonMemberName, wantMatch := range matches {
   100  					in := `{"` + jsonMemberName + `":true}`
   101  					var s Fields
   102  					if err := json.Unmarshal([]byte(in), &s); err != nil {
   103  						t.Fatalf("json.Unmarshal error: %v", err)
   104  					}
   105  					gotMatch := reflect.ValueOf(s).FieldByName(goFieldName).Bool()
   106  					if gotMatch != wantMatch {
   107  						t.Fatalf("%T.%s = %v, want %v", s, goFieldName, gotMatch, wantMatch)
   108  					}
   109  				}
   110  			}
   111  		})
   112  	}
   113  }
   114  
   115  // In v1, the "omitempty" option specifies that a struct field is omitted
   116  // when marshaling if it is an empty Go value, which is defined as
   117  // false, 0, a nil pointer, a nil interface value, and
   118  // any empty array, slice, map, or string.
   119  //
   120  // In v2, the "omitempty" option specifies that a struct field is omitted
   121  // when marshaling if it is an empty JSON value, which is defined as
   122  // a JSON null or empty JSON string, object, or array.
   123  //
   124  // In v2, we also provide the "omitzero" option which specifies that a field
   125  // is omitted if it is the zero Go value or if it implements an "IsZero() bool"
   126  // method that reports true. Together, "omitzero" and "omitempty" can cover
   127  // all the prior use cases of the v1 definition of "omitempty".
   128  // Note that "omitempty" is defined in terms of the Go type system in v1,
   129  // but now defined in terms of the JSON type system in v2.
   130  //
   131  // Related issues:
   132  //
   133  //	https://go.dev/issue/11939
   134  //	https://go.dev/issue/22480
   135  //	https://go.dev/issue/29310
   136  //	https://go.dev/issue/32675
   137  //	https://go.dev/issue/45669
   138  //	https://go.dev/issue/45787
   139  //	https://go.dev/issue/50480
   140  //	https://go.dev/issue/52803
   141  func TestOmitEmptyOption(t *testing.T) {
   142  	type Struct struct {
   143  		Foo string  `json:",omitempty"`
   144  		Bar []int   `json:",omitempty"`
   145  		Baz *Struct `json:",omitempty"`
   146  	}
   147  	type Types struct {
   148  		Bool       bool              `json:",omitempty"`
   149  		StringA    string            `json:",omitempty"`
   150  		StringB    string            `json:",omitempty"`
   151  		BytesA     []byte            `json:",omitempty"`
   152  		BytesB     []byte            `json:",omitempty"`
   153  		BytesC     []byte            `json:",omitempty"`
   154  		Int        int               `json:",omitempty"`
   155  		MapA       map[string]string `json:",omitempty"`
   156  		MapB       map[string]string `json:",omitempty"`
   157  		MapC       map[string]string `json:",omitempty"`
   158  		StructA    Struct            `json:",omitempty"`
   159  		StructB    Struct            `json:",omitempty"`
   160  		StructC    Struct            `json:",omitempty"`
   161  		SliceA     []string          `json:",omitempty"`
   162  		SliceB     []string          `json:",omitempty"`
   163  		SliceC     []string          `json:",omitempty"`
   164  		Array      [1]string         `json:",omitempty"`
   165  		PointerA   *string           `json:",omitempty"`
   166  		PointerB   *string           `json:",omitempty"`
   167  		PointerC   *string           `json:",omitempty"`
   168  		InterfaceA any               `json:",omitempty"`
   169  		InterfaceB any               `json:",omitempty"`
   170  		InterfaceC any               `json:",omitempty"`
   171  		InterfaceD any               `json:",omitempty"`
   172  	}
   173  
   174  	something := "something"
   175  	for _, json := range jsonPackages {
   176  		t.Run(path.Join("Marshal", json.Version), func(t *testing.T) {
   177  			in := Types{
   178  				Bool:       false,
   179  				StringA:    "",
   180  				StringB:    something,
   181  				BytesA:     nil,
   182  				BytesB:     []byte{},
   183  				BytesC:     []byte(something),
   184  				Int:        0,
   185  				MapA:       nil,
   186  				MapB:       map[string]string{},
   187  				MapC:       map[string]string{something: something},
   188  				StructA:    Struct{},
   189  				StructB:    Struct{Bar: []int{}, Baz: new(Struct)},
   190  				StructC:    Struct{Foo: something},
   191  				SliceA:     nil,
   192  				SliceB:     []string{},
   193  				SliceC:     []string{something},
   194  				Array:      [1]string{something},
   195  				PointerA:   nil,
   196  				PointerB:   new(string),
   197  				PointerC:   &something,
   198  				InterfaceA: nil,
   199  				InterfaceB: (*string)(nil),
   200  				InterfaceC: new(string),
   201  				InterfaceD: &something,
   202  			}
   203  			b, err := json.Marshal(in)
   204  			if err != nil {
   205  				t.Fatalf("json.Marshal error: %v", err)
   206  			}
   207  			var out map[string]any
   208  			if err := json.Unmarshal(b, &out); err != nil {
   209  				t.Fatalf("json.Unmarshal error: %v", err)
   210  			}
   211  
   212  			onlyV1 := json.Version == "v1"
   213  			onlyV2 := json.Version == "v2"
   214  			wantPresent := map[string]bool{
   215  				"Bool":       onlyV2, // false is an empty Go bool, but is NOT an empty JSON value
   216  				"StringA":    false,
   217  				"StringB":    true,
   218  				"BytesA":     false,
   219  				"BytesB":     false,
   220  				"BytesC":     true,
   221  				"Int":        onlyV2, // 0 is an empty Go integer, but NOT an empty JSON value
   222  				"MapA":       false,
   223  				"MapB":       false,
   224  				"MapC":       true,
   225  				"StructA":    onlyV1, // Struct{} is NOT an empty Go value, but {} is an empty JSON value
   226  				"StructB":    onlyV1, // Struct{...} is NOT an empty Go value, but {} is an empty JSON value
   227  				"StructC":    true,
   228  				"SliceA":     false,
   229  				"SliceB":     false,
   230  				"SliceC":     true,
   231  				"Array":      true,
   232  				"PointerA":   false,
   233  				"PointerB":   onlyV1, // new(string) is NOT a nil Go pointer, but "" is an empty JSON value
   234  				"PointerC":   true,
   235  				"InterfaceA": false,
   236  				"InterfaceB": onlyV1, // (*string)(nil) is NOT a nil Go interface, but null is an empty JSON value
   237  				"InterfaceC": onlyV1, // new(string) is NOT a nil Go interface, but "" is an empty JSON value
   238  				"InterfaceD": true,
   239  			}
   240  			for field, want := range wantPresent {
   241  				_, got := out[field]
   242  				if got != want {
   243  					t.Fatalf("%T.%s = %v, want %v", in, field, got, want)
   244  				}
   245  			}
   246  		})
   247  	}
   248  }
   249  
   250  func addr[T any](v T) *T {
   251  	return &v
   252  }
   253  
   254  // In v1, the "string" option specifies that Go strings, bools, and numeric
   255  // values are encoded within a JSON string when marshaling and
   256  // are unmarshaled from its native representation escaped within a JSON string.
   257  // The "string" option is not applied recursively, and so does not affect
   258  // strings, bools, and numeric values within a Go slice or map, but
   259  // does have special handling to affect the underlying value within a pointer.
   260  // When unmarshaling, the "string" option permits decoding from a JSON null
   261  // escaped within a JSON string in some inconsistent cases.
   262  //
   263  // In v2, the "string" option specifies that only numeric values are encoded as
   264  // a JSON number within a JSON string when marshaling and are unmarshaled
   265  // from either a JSON number or a JSON string containing a JSON number.
   266  // The "string" option is applied recursively to all numeric sub-values,
   267  // and thus affects numeric values within a Go slice or map.
   268  // There is no support for escaped JSON nulls within a JSON string.
   269  //
   270  // The main utility for stringifying JSON numbers is because JSON parsers
   271  // often represents numbers as IEEE 754 floating-point numbers.
   272  // This results in a loss of precision representing 64-bit integer values.
   273  // Consequently, many JSON-based APIs actually requires that such values
   274  // be encoded within a JSON string. Since the main utility of stringification
   275  // is for numeric values, v2 limits the effect of the "string" option
   276  // to just numeric Go types. According to all code known by the Go module proxy,
   277  // there are close to zero usages of the "string" option on a Go string or bool.
   278  //
   279  // Regarding the recursive application of the "string" option,
   280  // there have been a number of issues filed about users being surprised that
   281  // the "string" option does not recursively affect numeric values
   282  // within a composite type like a Go map, slice, or interface value.
   283  // In v1, specifying the "string" option on composite type has no effect
   284  // and so this would be a largely backwards compatible change.
   285  //
   286  // The ability to decode from a JSON null wrapped within a JSON string
   287  // is removed in v2 because this behavior was surprising and inconsistent in v1.
   288  //
   289  // Related issues:
   290  //
   291  //	https://go.dev/issue/15624
   292  //	https://go.dev/issue/20651
   293  //	https://go.dev/issue/22177
   294  //	https://go.dev/issue/32055
   295  //	https://go.dev/issue/32117
   296  //	https://go.dev/issue/50997
   297  func TestStringOption(t *testing.T) {
   298  	type Types struct {
   299  		String     string              `json:",string"`
   300  		Bool       bool                `json:",string"`
   301  		Int        int                 `json:",string"`
   302  		Float      float64             `json:",string"`
   303  		Map        map[string]int      `json:",string"`
   304  		Struct     struct{ Field int } `json:",string"`
   305  		Slice      []int               `json:",string"`
   306  		Array      [1]int              `json:",string"`
   307  		PointerA   *int                `json:",string"`
   308  		PointerB   *int                `json:",string"`
   309  		PointerC   **int               `json:",string"`
   310  		InterfaceA any                 `json:",string"`
   311  		InterfaceB any                 `json:",string"`
   312  	}
   313  
   314  	for _, json := range jsonPackages {
   315  		t.Run(path.Join("Marshal", json.Version), func(t *testing.T) {
   316  			in := Types{
   317  				String:     "string",
   318  				Bool:       true,
   319  				Int:        1,
   320  				Float:      1,
   321  				Map:        map[string]int{"Name": 1},
   322  				Struct:     struct{ Field int }{1},
   323  				Slice:      []int{1},
   324  				Array:      [1]int{1},
   325  				PointerA:   nil,
   326  				PointerB:   addr(1),
   327  				PointerC:   addr(addr(1)),
   328  				InterfaceA: nil,
   329  				InterfaceB: 1,
   330  			}
   331  			quote := func(s string) string {
   332  				b, _ := jsontext.AppendQuote(nil, s)
   333  				return string(b)
   334  			}
   335  			quoteOnlyV1 := func(s string) string {
   336  				if json.Version == "v1" {
   337  					s = quote(s)
   338  				}
   339  				return s
   340  			}
   341  			quoteOnlyV2 := func(s string) string {
   342  				if json.Version == "v2" {
   343  					s = quote(s)
   344  				}
   345  				return s
   346  			}
   347  			want := strings.Join([]string{
   348  				`{`,
   349  				`"String":` + quoteOnlyV1(`"string"`) + `,`, // in v1, Go strings are also stringified
   350  				`"Bool":` + quoteOnlyV1("true") + `,`,       // in v1, Go bools are also stringified
   351  				`"Int":` + quote("1") + `,`,
   352  				`"Float":` + quote("1") + `,`,
   353  				`"Map":{"Name":` + quoteOnlyV2("1") + `},`,     // in v2, numbers are recursively stringified
   354  				`"Struct":{"Field":` + quoteOnlyV2("1") + `},`, // in v2, numbers are recursively stringified
   355  				`"Slice":[` + quoteOnlyV2("1") + `],`,          // in v2, numbers are recursively stringified
   356  				`"Array":[` + quoteOnlyV2("1") + `],`,          // in v2, numbers are recursively stringified
   357  				`"PointerA":null,`,
   358  				`"PointerB":` + quote("1") + `,`,       // in v1, numbers are stringified after a single pointer indirection
   359  				`"PointerC":` + quoteOnlyV2("1") + `,`, // in v2, numbers are recursively stringified
   360  				`"InterfaceA":null,`,
   361  				`"InterfaceB":` + quoteOnlyV2("1") + ``, // in v2, numbers are recursively stringified
   362  				`}`}, "")
   363  			got, err := json.Marshal(in)
   364  			if err != nil {
   365  				t.Fatalf("json.Marshal error: %v", err)
   366  			}
   367  			if string(got) != want {
   368  				t.Fatalf("json.Marshal = %s, want %s", got, want)
   369  			}
   370  		})
   371  	}
   372  
   373  	for _, json := range jsonPackages {
   374  		t.Run(path.Join("Unmarshal/Null", json.Version), func(t *testing.T) {
   375  			var got Types
   376  			err := json.Unmarshal([]byte(`{
   377  				"Bool":     "null",
   378  				"Int":      "null",
   379  				"PointerA": "null"
   380  			}`), &got)
   381  			switch {
   382  			case !reflect.DeepEqual(got, Types{}):
   383  				t.Fatalf("json.Unmarshal = %v, want %v", got, Types{})
   384  			case json.Version == "v1" && err != nil:
   385  				t.Fatalf("json.Unmarshal error: %v", err)
   386  			case json.Version == "v2" && err == nil:
   387  				t.Fatal("json.Unmarshal error is nil, want non-nil")
   388  			}
   389  		})
   390  
   391  		t.Run(path.Join("Unmarshal/Bool", json.Version), func(t *testing.T) {
   392  			var got Types
   393  			want := map[string]Types{
   394  				"v1": {Bool: true},
   395  				"v2": {Bool: false},
   396  			}[json.Version]
   397  			err := json.Unmarshal([]byte(`{"Bool": "true"}`), &got)
   398  			switch {
   399  			case !reflect.DeepEqual(got, want):
   400  				t.Fatalf("json.Unmarshal = %v, want %v", got, want)
   401  			case json.Version == "v1" && err != nil:
   402  				t.Fatalf("json.Unmarshal error: %v", err)
   403  			case json.Version == "v2" && err == nil:
   404  				t.Fatal("json.Unmarshal error is nil, want non-nil")
   405  			}
   406  		})
   407  
   408  		t.Run(path.Join("Unmarshal/Shallow", json.Version), func(t *testing.T) {
   409  			var got Types
   410  			want := Types{Int: 1, PointerB: addr(1)}
   411  			err := json.Unmarshal([]byte(`{
   412  				"Int":      "1",
   413  				"PointerB": "1"
   414  			}`), &got)
   415  			switch {
   416  			case !reflect.DeepEqual(got, want):
   417  				t.Fatalf("json.Unmarshal = %v, want %v", got, want)
   418  			case err != nil:
   419  				t.Fatalf("json.Unmarshal error: %v", err)
   420  			}
   421  		})
   422  
   423  		t.Run(path.Join("Unmarshal/Deep", json.Version), func(t *testing.T) {
   424  			var got Types
   425  			want := map[string]Types{
   426  				"v1": {
   427  					Map:      map[string]int{"Name": 0},
   428  					Slice:    []int{0},
   429  					PointerC: addr(addr(0)),
   430  				},
   431  				"v2": {
   432  					Map:      map[string]int{"Name": 1},
   433  					Struct:   struct{ Field int }{1},
   434  					Slice:    []int{1},
   435  					Array:    [1]int{1},
   436  					PointerC: addr(addr(1)),
   437  				},
   438  			}[json.Version]
   439  			err := json.Unmarshal([]byte(`{
   440  				"Map":      {"Name":"1"},
   441  				"Struct":   {"Field":"1"},
   442  				"Slice":    ["1"],
   443  				"Array":    ["1"],
   444  				"PointerC": "1"
   445  			}`), &got)
   446  			switch {
   447  			case !reflect.DeepEqual(got, want):
   448  				t.Fatalf("json.Unmarshal =\n%v, want\n%v", got, want)
   449  			case json.Version == "v1" && err == nil:
   450  				t.Fatal("json.Unmarshal error is nil, want non-nil")
   451  			case json.Version == "v2" && err != nil:
   452  				t.Fatalf("json.Unmarshal error: %v", err)
   453  			}
   454  		})
   455  	}
   456  }
   457  
   458  // In v1, nil slices and maps are marshaled as a JSON null.
   459  // In v2, nil slices and maps are marshaled as an empty JSON object or array.
   460  //
   461  // Users of v2 can opt into the v1 behavior by setting
   462  // the "format:emitnull" option in the `json` struct field tag:
   463  //
   464  //	struct {
   465  //		S []string          `json:",format:emitnull"`
   466  //		M map[string]string `json:",format:emitnull"`
   467  //	}
   468  //
   469  // JSON is a language-agnostic data interchange format.
   470  // The fact that maps and slices are nil-able in Go is a semantic detail of the
   471  // Go language. We should avoid leaking such details to the JSON representation.
   472  // When JSON implementations leak language-specific details,
   473  // it complicates transition to/from languages with different type systems.
   474  //
   475  // Furthermore, consider two related Go types: string and []byte.
   476  // It's an asymmetric oddity of v1 that zero values of string and []byte marshal
   477  // as an empty JSON string for the former, while the latter as a JSON null.
   478  // The non-zero values of those types always marshal as JSON strings.
   479  //
   480  // Related issues:
   481  //
   482  //	https://go.dev/issue/27589
   483  //	https://go.dev/issue/37711
   484  func TestNilSlicesAndMaps(t *testing.T) {
   485  	type Composites struct {
   486  		B []byte            // always encoded in v2 as a JSON string
   487  		S []string          // always encoded in v2 as a JSON array
   488  		M map[string]string // always encoded in v2 as a JSON object
   489  	}
   490  
   491  	for _, json := range jsonPackages {
   492  		t.Run(path.Join("Marshal", json.Version), func(t *testing.T) {
   493  			in := []Composites{
   494  				{B: []byte(nil), S: []string(nil), M: map[string]string(nil)},
   495  				{B: []byte{}, S: []string{}, M: map[string]string{}},
   496  			}
   497  			want := map[string]string{
   498  				"v1": `[{"B":null,"S":null,"M":null},{"B":"","S":[],"M":{}}]`,
   499  				"v2": `[{"B":"","S":[],"M":{}},{"B":"","S":[],"M":{}}]`, // v2 emits nil slices and maps as empty JSON objects and arrays
   500  			}[json.Version]
   501  			got, err := json.Marshal(in)
   502  			if err != nil {
   503  				t.Fatalf("json.Marshal error: %v", err)
   504  			}
   505  			if string(got) != want {
   506  				t.Fatalf("json.Marshal = %s, want %s", got, want)
   507  			}
   508  		})
   509  	}
   510  }
   511  
   512  // In v1, unmarshaling into a Go array permits JSON arrays with any length.
   513  // In v2, unmarshaling into a Go array requires that the JSON array
   514  // have the exact same number of elements as the Go array.
   515  //
   516  // Go arrays are often used because the exact length has significant meaning.
   517  // Ignoring this detail seems like a mistake. Also, the v1 behavior leads to
   518  // silent data loss when excess JSON array elements are discarded.
   519  func TestArrays(t *testing.T) {
   520  	for _, json := range jsonPackages {
   521  		t.Run(path.Join("Unmarshal/TooFew", json.Version), func(t *testing.T) {
   522  			var got [2]int
   523  			err := json.Unmarshal([]byte(`[1]`), &got)
   524  			switch {
   525  			case got != [2]int{1, 0}:
   526  				t.Fatalf(`json.Unmarshal = %v, want [1 0]`, got)
   527  			case json.Version == "v1" && err != nil:
   528  				t.Fatalf("json.Unmarshal error: %v", err)
   529  			case json.Version == "v2" && err == nil:
   530  				t.Fatal("json.Unmarshal error is nil, want non-nil")
   531  			}
   532  		})
   533  	}
   534  
   535  	for _, json := range jsonPackages {
   536  		t.Run(path.Join("Unmarshal/TooMany", json.Version), func(t *testing.T) {
   537  			var got [2]int
   538  			err := json.Unmarshal([]byte(`[1,2,3]`), &got)
   539  			switch {
   540  			case got != [2]int{1, 2}:
   541  				t.Fatalf(`json.Unmarshal = %v, want [1 2]`, got)
   542  			case json.Version == "v1" && err != nil:
   543  				t.Fatalf("json.Unmarshal error: %v", err)
   544  			case json.Version == "v2" && err == nil:
   545  				t.Fatal("json.Unmarshal error is nil, want non-nil")
   546  			}
   547  		})
   548  	}
   549  }
   550  
   551  // In v1, byte arrays are treated as arrays of unsigned integers.
   552  // In v2, byte arrays are treated as binary values (similar to []byte).
   553  // This is to make the behavior of [N]byte and []byte more consistent.
   554  //
   555  // Users of v2 can opt into the v1 behavior by setting
   556  // the "format:array" option in the `json` struct field tag:
   557  //
   558  //	struct {
   559  //		B [32]byte `json:",format:array"`
   560  //	}
   561  func TestByteArrays(t *testing.T) {
   562  	for _, json := range jsonPackages {
   563  		t.Run(path.Join("Marshal", json.Version), func(t *testing.T) {
   564  			in := [4]byte{1, 2, 3, 4}
   565  			got, err := json.Marshal(in)
   566  			if err != nil {
   567  				t.Fatalf("json.Marshal error: %v", err)
   568  			}
   569  			want := map[string]string{
   570  				"v1": `[1,2,3,4]`,
   571  				"v2": `"AQIDBA=="`,
   572  			}[json.Version]
   573  			if string(got) != want {
   574  				t.Fatalf("json.Marshal = %s, want %s", got, want)
   575  			}
   576  		})
   577  	}
   578  
   579  	for _, json := range jsonPackages {
   580  		t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) {
   581  			in := map[string]string{
   582  				"v1": `[1,2,3,4]`,
   583  				"v2": `"AQIDBA=="`,
   584  			}[json.Version]
   585  			var got [4]byte
   586  			err := json.Unmarshal([]byte(in), &got)
   587  			switch {
   588  			case err != nil:
   589  				t.Fatalf("json.Unmarshal error: %v", err)
   590  			case got != [4]byte{1, 2, 3, 4}:
   591  				t.Fatalf("json.Unmarshal = %v, want [1 2 3 4]", got)
   592  			}
   593  		})
   594  	}
   595  }
   596  
   597  // CallCheck implements json.{Marshaler,Unmarshaler} on a pointer receiver.
   598  type CallCheck string
   599  
   600  // MarshalJSON always returns a JSON string with the literal "CALLED".
   601  func (*CallCheck) MarshalJSON() ([]byte, error) {
   602  	return []byte(`"CALLED"`), nil
   603  }
   604  
   605  // UnmarshalJSON always stores a string with the literal "CALLED".
   606  func (v *CallCheck) UnmarshalJSON([]byte) error {
   607  	*v = `CALLED`
   608  	return nil
   609  }
   610  
   611  // In v1, the implementation is inconsistent about whether it calls
   612  // MarshalJSON and UnmarshalJSON methods declared on pointer receivers
   613  // when it has an unaddressable value (per reflect.Value.CanAddr) on hand.
   614  // When marshaling, it never boxes the value on the heap to make it addressable,
   615  // while it sometimes boxes values (e.g., for map entries) when unmarshaling.
   616  //
   617  // In v2, the implementation always calls MarshalJSON and UnmarshalJSON methods
   618  // by boxing the value on the heap if necessary.
   619  //
   620  // The v1 behavior is surprising at best and buggy at worst.
   621  // Unfortunately, it cannot be changed without breaking existing usages.
   622  //
   623  // Related issues:
   624  //
   625  //	https://go.dev/issue/27722
   626  //	https://go.dev/issue/33993
   627  //	https://go.dev/issue/42508
   628  func TestPointerReceiver(t *testing.T) {
   629  	type Values struct {
   630  		S []CallCheck
   631  		A [1]CallCheck
   632  		M map[string]CallCheck
   633  		V CallCheck
   634  		I any
   635  	}
   636  
   637  	for _, json := range jsonPackages {
   638  		t.Run(path.Join("Marshal", json.Version), func(t *testing.T) {
   639  			var cc CallCheck
   640  			in := Values{
   641  				S: []CallCheck{cc},
   642  				A: [1]CallCheck{cc},             // MarshalJSON not called on v1
   643  				M: map[string]CallCheck{"": cc}, // MarshalJSON not called on v1
   644  				V: cc,                           // MarshalJSON not called on v1
   645  				I: cc,                           // MarshalJSON not called on v1
   646  			}
   647  			want := map[string]string{
   648  				"v1": `{"S":["CALLED"],"A":[""],"M":{"":""},"V":"","I":""}`,
   649  				"v2": `{"S":["CALLED"],"A":["CALLED"],"M":{"":"CALLED"},"V":"CALLED","I":"CALLED"}`,
   650  			}[json.Version]
   651  			got, err := json.Marshal(in)
   652  			if err != nil {
   653  				t.Fatalf("json.Marshal error: %v", err)
   654  			}
   655  			if string(got) != want {
   656  				t.Fatalf("json.Marshal = %s, want %s", got, want)
   657  			}
   658  		})
   659  	}
   660  
   661  	for _, json := range jsonPackages {
   662  		t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) {
   663  			in := `{"S":[""],"A":[""],"M":{"":""},"V":"","I":""}`
   664  			called := CallCheck("CALLED") // resulting state if UnmarshalJSON is called
   665  			want := map[string]Values{
   666  				"v1": {
   667  					S: []CallCheck{called},
   668  					A: [1]CallCheck{called},
   669  					M: map[string]CallCheck{"": called},
   670  					V: called,
   671  					I: "", // UnmarshalJSON not called on v1; replaced with Go string
   672  				},
   673  				"v2": {
   674  					S: []CallCheck{called},
   675  					A: [1]CallCheck{called},
   676  					M: map[string]CallCheck{"": called},
   677  					V: called,
   678  					I: called,
   679  				},
   680  			}[json.Version]
   681  			got := Values{
   682  				A: [1]CallCheck{CallCheck("")},
   683  				S: []CallCheck{CallCheck("")},
   684  				M: map[string]CallCheck{"": CallCheck("")},
   685  				V: CallCheck(""),
   686  				I: CallCheck(""),
   687  			}
   688  			if err := json.Unmarshal([]byte(in), &got); err != nil {
   689  				t.Fatalf("json.Unmarshal error: %v", err)
   690  			}
   691  			if !reflect.DeepEqual(got, want) {
   692  				t.Fatalf("json.Unmarshal = %v, want %v", got, want)
   693  			}
   694  		})
   695  	}
   696  }
   697  
   698  // In v1, maps are marshaled in a deterministic order.
   699  // In v2, maps are marshaled in a non-deterministic order.
   700  //
   701  // The reason for the change is that v2 prioritizes performance and
   702  // the guarantee that marshaling operates primarily in a streaming manner.
   703  //
   704  // The v2 API provides jsontext.Value.Canonicalize if stability is needed:
   705  //
   706  //	(*jsontext.Value)(&b).Canonicalize()
   707  //
   708  // Related issue:
   709  //
   710  //	https://go.dev/issue/7872
   711  //	https://go.dev/issue/33714
   712  func TestMapDeterminism(t *testing.T) {
   713  	const iterations = 10
   714  	in := map[int]int{0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9}
   715  
   716  	for _, json := range jsonPackages {
   717  		t.Run(path.Join("Marshal", json.Version), func(t *testing.T) {
   718  			outs := make(map[string]bool)
   719  			for range iterations {
   720  				b, err := json.Marshal(in)
   721  				if err != nil {
   722  					t.Fatalf("json.Marshal error: %v", err)
   723  				}
   724  				outs[string(b)] = true
   725  			}
   726  			switch {
   727  			case json.Version == "v1" && len(outs) != 1:
   728  				t.Fatalf("json.Marshal encoded to %d unique forms, expected 1", len(outs))
   729  			case json.Version == "v2" && len(outs) == 1:
   730  				t.Logf("json.Marshal encoded to 1 unique form by chance; are you feeling lucky?")
   731  			}
   732  		})
   733  	}
   734  }
   735  
   736  // In v1, JSON string encoding escapes special characters related to HTML.
   737  // In v2, JSON string encoding uses a normalized representation (per RFC 8785).
   738  //
   739  // Users of v2 can opt into the v1 behavior by setting EscapeForHTML and EscapeForJS.
   740  //
   741  // Escaping HTML-specific characters in a JSON library is a layering violation.
   742  // It presumes that JSON is always used with HTML and ignores other
   743  // similar classes of injection attacks (e.g., SQL injection).
   744  // Users of JSON with HTML should either manually ensure that embedded JSON is
   745  // properly escaped or be relying on a module like "github.com/google/safehtml"
   746  // to handle safe interoperability of JSON and HTML.
   747  func TestEscapeHTML(t *testing.T) {
   748  	for _, json := range jsonPackages {
   749  		t.Run(path.Join("Marshal", json.Version), func(t *testing.T) {
   750  			const in = `<script> console.log("Hello, world!"); </script>`
   751  			got, err := json.Marshal(in)
   752  			if err != nil {
   753  				t.Fatalf("json.Marshal error: %v", err)
   754  			}
   755  			want := map[string]string{
   756  				"v1": `"\u003cscript\u003e console.log(\"Hello, world!\"); \u003c/script\u003e"`,
   757  				"v2": `"<script> console.log(\"Hello, world!\"); </script>"`,
   758  			}[json.Version]
   759  			if string(got) != want {
   760  				t.Fatalf("json.Marshal = %s, want %s", got, want)
   761  			}
   762  		})
   763  	}
   764  }
   765  
   766  // In v1, JSON serialization silently ignored invalid UTF-8 by
   767  // replacing such bytes with the Unicode replacement character.
   768  // In v2, JSON serialization reports an error if invalid UTF-8 is encountered.
   769  //
   770  // Users of v2 can opt into the v1 behavior by setting [AllowInvalidUTF8].
   771  //
   772  // Silently allowing invalid UTF-8 causes data corruption that can be difficult
   773  // to detect until it is too late. Once it has been discovered, strict UTF-8
   774  // behavior sometimes cannot be enabled since other logic may be depending
   775  // on the current behavior due to Hyrum's Law.
   776  //
   777  // Tim Bray, the author of RFC 8259 recommends that implementations should
   778  // go beyond RFC 8259 and instead target compliance with RFC 7493,
   779  // which makes strict decisions about behavior left undefined in RFC 8259.
   780  // In particular, RFC 7493 rejects the presence of invalid UTF-8.
   781  // See https://www.tbray.org/ongoing/When/201x/2017/12/14/RFC-8259-STD-90
   782  func TestInvalidUTF8(t *testing.T) {
   783  	for _, json := range jsonPackages {
   784  		t.Run(path.Join("Marshal", json.Version), func(t *testing.T) {
   785  			got, err := json.Marshal("\xff")
   786  			switch {
   787  			case json.Version == "v1" && err != nil:
   788  				t.Fatalf("json.Marshal error: %v", err)
   789  			case json.Version == "v1" && string(got) != `"\ufffd"`:
   790  				t.Fatalf(`json.Marshal = %s, want "\ufffd"`, got)
   791  			case json.Version == "v2" && err == nil:
   792  				t.Fatal("json.Marshal error is nil, want non-nil")
   793  			}
   794  		})
   795  	}
   796  
   797  	for _, json := range jsonPackages {
   798  		t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) {
   799  			const in = "\"\xff\""
   800  			var got string
   801  			err := json.Unmarshal([]byte(in), &got)
   802  			switch {
   803  			case json.Version == "v1" && err != nil:
   804  				t.Fatalf("json.Unmarshal error: %v", err)
   805  			case json.Version == "v1" && got != "\ufffd":
   806  				t.Fatalf(`json.Unmarshal = %q, want "\ufffd"`, got)
   807  			case json.Version == "v2" && err == nil:
   808  				t.Fatal("json.Unmarshal error is nil, want non-nil")
   809  			}
   810  		})
   811  	}
   812  }
   813  
   814  // In v1, duplicate JSON object names are permitted by default where
   815  // they follow the inconsistent and difficult-to-explain merge semantics of v1.
   816  // In v2, duplicate JSON object names are rejected by default where
   817  // they follow the merge semantics of v2 based on RFC 7396.
   818  //
   819  // Users of v2 can opt into the v1 behavior by setting [AllowDuplicateNames].
   820  //
   821  // Per RFC 8259, the handling of duplicate names is left as undefined behavior.
   822  // Rejecting such inputs is within the realm of valid behavior.
   823  // Tim Bray, the author of RFC 8259 recommends that implementations should
   824  // go beyond RFC 8259 and instead target compliance with RFC 7493,
   825  // which makes strict decisions about behavior left undefined in RFC 8259.
   826  // In particular, RFC 7493 rejects the presence of duplicate object names.
   827  // See https://www.tbray.org/ongoing/When/201x/2017/12/14/RFC-8259-STD-90
   828  //
   829  // The lack of duplicate name rejection has correctness implications where
   830  // roundtrip unmarshal/marshal do not result in semantically equivalent JSON.
   831  // This is surprising behavior for users when they accidentally
   832  // send JSON objects with duplicate names.
   833  //
   834  // The lack of duplicate name rejection may have security implications since it
   835  // becomes difficult for a security tool to validate the semantic meaning of a
   836  // JSON object since meaning is undefined in the presence of duplicate names.
   837  // See https://labs.bishopfox.com/tech-blog/an-exploration-of-json-interoperability-vulnerabilities
   838  //
   839  // Related issue:
   840  //
   841  //	https://go.dev/issue/48298
   842  func TestDuplicateNames(t *testing.T) {
   843  	for _, json := range jsonPackages {
   844  		t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) {
   845  			const in = `{"Name":1,"Name":2}`
   846  			var got struct{ Name int }
   847  			err := json.Unmarshal([]byte(in), &got)
   848  			switch {
   849  			case json.Version == "v1" && err != nil:
   850  				t.Fatalf("json.Unmarshal error: %v", err)
   851  			case json.Version == "v1" && got != struct{ Name int }{2}:
   852  				t.Fatalf(`json.Unmarshal = %v, want {2}`, got)
   853  			case json.Version == "v2" && err == nil:
   854  				t.Fatal("json.Unmarshal error is nil, want non-nil")
   855  			}
   856  		})
   857  	}
   858  }
   859  
   860  // In v1, unmarshaling a JSON null into a non-empty value was inconsistent
   861  // in that sometimes it would be ignored and other times clear the value.
   862  // In v2, unmarshaling a JSON null into a non-empty value would consistently
   863  // always clear the value regardless of the value's type.
   864  //
   865  // The purpose of this change is to have consistent behavior with how JSON nulls
   866  // are handled during Unmarshal. This semantic detail has no effect
   867  // when Unmarshaling into a empty value.
   868  //
   869  // Related issues:
   870  //
   871  //	https://go.dev/issue/22177
   872  //	https://go.dev/issue/33835
   873  func TestMergeNull(t *testing.T) {
   874  	type Types struct {
   875  		Bool      bool
   876  		String    string
   877  		Bytes     []byte
   878  		Int       int
   879  		Map       map[string]string
   880  		Struct    struct{ Field string }
   881  		Slice     []string
   882  		Array     [1]string
   883  		Pointer   *string
   884  		Interface any
   885  	}
   886  
   887  	for _, json := range jsonPackages {
   888  		t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) {
   889  			// Start with a non-empty value where all fields are populated.
   890  			in := Types{
   891  				Bool:      true,
   892  				String:    "old",
   893  				Bytes:     []byte("old"),
   894  				Int:       1234,
   895  				Map:       map[string]string{"old": "old"},
   896  				Struct:    struct{ Field string }{"old"},
   897  				Slice:     []string{"old"},
   898  				Array:     [1]string{"old"},
   899  				Pointer:   new(string),
   900  				Interface: "old",
   901  			}
   902  
   903  			// Unmarshal a JSON null into every field.
   904  			if err := json.Unmarshal([]byte(`{
   905  				"Bool":      null,
   906  				"String":    null,
   907  				"Bytes":     null,
   908  				"Int":       null,
   909  				"Map":       null,
   910  				"Struct":    null,
   911  				"Slice":     null,
   912  				"Array":     null,
   913  				"Pointer":   null,
   914  				"Interface": null
   915  			}`), &in); err != nil {
   916  				t.Fatalf("json.Unmarshal error: %v", err)
   917  			}
   918  
   919  			want := map[string]Types{
   920  				"v1": {
   921  					Bool:   true,
   922  					String: "old",
   923  					Int:    1234,
   924  					Struct: struct{ Field string }{"old"},
   925  					Array:  [1]string{"old"},
   926  				},
   927  				"v2": {}, // all fields are zeroed
   928  			}[json.Version]
   929  			if !reflect.DeepEqual(in, want) {
   930  				t.Fatalf("json.Unmarshal = %+v, want %+v", in, want)
   931  			}
   932  		})
   933  	}
   934  }
   935  
   936  // In v1, merge semantics are inconsistent and difficult to explain.
   937  // In v2, merge semantics replaces the destination value for anything
   938  // other than a JSON object, and recursively merges JSON objects.
   939  //
   940  // Merge semantics in v1 are inconsistent and difficult to explain
   941  // largely because the behavior came about organically, rather than
   942  // having a principled approach to how the semantics should operate.
   943  // In v2, merging follows behavior based on RFC 7396.
   944  //
   945  // Related issues:
   946  //
   947  //	https://go.dev/issue/21092
   948  //	https://go.dev/issue/26946
   949  //	https://go.dev/issue/27172
   950  //	https://go.dev/issue/30701
   951  //	https://go.dev/issue/31924
   952  //	https://go.dev/issue/43664
   953  func TestMergeComposite(t *testing.T) {
   954  	type Tuple struct{ Old, New bool }
   955  	type Composites struct {
   956  		Slice            []Tuple
   957  		Array            [1]Tuple
   958  		Map              map[string]Tuple
   959  		MapPointer       map[string]*Tuple
   960  		Struct           struct{ Tuple Tuple }
   961  		StructPointer    *struct{ Tuple Tuple }
   962  		Interface        any
   963  		InterfacePointer any
   964  	}
   965  
   966  	for _, json := range jsonPackages {
   967  		t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) {
   968  			// Start with a non-empty value where all fields are populated.
   969  			in := Composites{
   970  				Slice:            []Tuple{{Old: true}, {Old: true}}[:1],
   971  				Array:            [1]Tuple{{Old: true}},
   972  				Map:              map[string]Tuple{"Tuple": {Old: true}},
   973  				MapPointer:       map[string]*Tuple{"Tuple": {Old: true}},
   974  				Struct:           struct{ Tuple Tuple }{Tuple{Old: true}},
   975  				StructPointer:    &struct{ Tuple Tuple }{Tuple{Old: true}},
   976  				Interface:        Tuple{Old: true},
   977  				InterfacePointer: &Tuple{Old: true},
   978  			}
   979  
   980  			// Unmarshal into every pre-populated field.
   981  			if err := json.Unmarshal([]byte(`{
   982  				"Slice":            [{"New":true}, {"New":true}],
   983  				"Array":            [{"New":true}],
   984  				"Map":              {"Tuple": {"New":true}},
   985  				"MapPointer":       {"Tuple": {"New":true}},
   986  				"Struct":           {"Tuple": {"New":true}},
   987  				"StructPointer":    {"Tuple": {"New":true}},
   988  				"Interface":        {"New":true},
   989  				"InterfacePointer": {"New":true}
   990  			}`), &in); err != nil {
   991  				t.Fatalf("json.Unmarshal error: %v", err)
   992  			}
   993  
   994  			merged := Tuple{Old: true, New: true}
   995  			replaced := Tuple{Old: false, New: true}
   996  			want := map[string]Composites{
   997  				"v1": {
   998  					Slice:            []Tuple{merged, merged},               // merged
   999  					Array:            [1]Tuple{merged},                      // merged
  1000  					Map:              map[string]Tuple{"Tuple": replaced},   // replaced
  1001  					MapPointer:       map[string]*Tuple{"Tuple": &replaced}, // replaced
  1002  					Struct:           struct{ Tuple Tuple }{merged},         // merged (same as v2)
  1003  					StructPointer:    &struct{ Tuple Tuple }{merged},        // merged (same as v2)
  1004  					Interface:        map[string]any{"New": true},           // replaced
  1005  					InterfacePointer: &merged,                               // merged (same as v2)
  1006  				},
  1007  				"v2": {
  1008  					Slice:            []Tuple{replaced, replaced},         // replaced
  1009  					Array:            [1]Tuple{replaced},                  // replaced
  1010  					Map:              map[string]Tuple{"Tuple": merged},   // merged
  1011  					MapPointer:       map[string]*Tuple{"Tuple": &merged}, // merged
  1012  					Struct:           struct{ Tuple Tuple }{merged},       // merged (same as v1)
  1013  					StructPointer:    &struct{ Tuple Tuple }{merged},      // merged (same as v1)
  1014  					Interface:        merged,                              // merged
  1015  					InterfacePointer: &merged,                             // merged (same as v1)
  1016  				},
  1017  			}[json.Version]
  1018  			if !reflect.DeepEqual(in, want) {
  1019  				t.Fatalf("json.Unmarshal = %+v, want %+v", in, want)
  1020  			}
  1021  		})
  1022  	}
  1023  }
  1024  
  1025  // In v1, there was no special support for time.Duration,
  1026  // which resulted in that type simply being treated as a signed integer.
  1027  // In v2, there is now first-class support for time.Duration, where the type is
  1028  // formatted and parsed using time.Duration.String and time.ParseDuration.
  1029  //
  1030  // Users of v2 can opt into the v1 behavior by setting
  1031  // the "format:nano" option in the `json` struct field tag:
  1032  //
  1033  //	struct {
  1034  //		Duration time.Duration `json:",format:nano"`
  1035  //	}
  1036  //
  1037  // Related issue:
  1038  //
  1039  //	https://go.dev/issue/10275
  1040  func TestTimeDurations(t *testing.T) {
  1041  	for _, json := range jsonPackages {
  1042  		t.Run(path.Join("Marshal", json.Version), func(t *testing.T) {
  1043  			got, err := json.Marshal(time.Minute)
  1044  			switch {
  1045  			case err != nil:
  1046  				t.Fatalf("json.Marshal error: %v", err)
  1047  			case json.Version == "v1" && string(got) != "60000000000":
  1048  				t.Fatalf("json.Marshal = %s, want 60000000000", got)
  1049  			case json.Version == "v2" && string(got) != `"1m0s"`:
  1050  				t.Fatalf(`json.Marshal = %s, want "1m0s"`, got)
  1051  			}
  1052  		})
  1053  	}
  1054  
  1055  	for _, json := range jsonPackages {
  1056  		t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) {
  1057  			in := map[string]string{
  1058  				"v1": "60000000000",
  1059  				"v2": `"1m0s"`,
  1060  			}[json.Version]
  1061  			var got time.Duration
  1062  			err := json.Unmarshal([]byte(in), &got)
  1063  			switch {
  1064  			case err != nil:
  1065  				t.Fatalf("json.Unmarshal error: %v", err)
  1066  			case got != time.Minute:
  1067  				t.Fatalf("json.Unmarshal = %v, want 1m0s", got)
  1068  			}
  1069  		})
  1070  	}
  1071  }
  1072  
  1073  // In v1, non-empty structs without any JSON serializable fields are permitted.
  1074  // In v2, non-empty structs without any JSON serializable fields are rejected.
  1075  //
  1076  // The purpose of this change is to avoid a common pitfall for new users
  1077  // where they expect JSON serialization to handle unexported fields.
  1078  // However, this does not work since Go reflection does not
  1079  // provide the package the ability to mutate such fields.
  1080  // Rejecting unserializable structs in v2 is intended to be a clear signal
  1081  // that the type is not supposed to be serialized.
  1082  func TestEmptyStructs(t *testing.T) {
  1083  	never := func(string) bool { return false }
  1084  	onlyV2 := func(v string) bool { return v == "v2" }
  1085  	values := []struct {
  1086  		in        any
  1087  		wantError func(string) bool
  1088  	}{
  1089  		// It is okay to marshal a truly empty struct in v1 and v2.
  1090  		{in: addr(struct{}{}), wantError: never},
  1091  		// In v1, a non-empty struct without exported fields
  1092  		// is equivalent to an empty struct, but is rejected in v2.
  1093  		// Note that errors.errorString type has only unexported fields.
  1094  		{in: errors.New("error"), wantError: onlyV2},
  1095  		// A mix of exported and unexported fields is permitted.
  1096  		{in: addr(struct{ Exported, unexported int }{}), wantError: never},
  1097  	}
  1098  
  1099  	for _, json := range jsonPackages {
  1100  		t.Run("Marshal", func(t *testing.T) {
  1101  			for _, value := range values {
  1102  				wantError := value.wantError(json.Version)
  1103  				_, err := json.Marshal(value.in)
  1104  				switch {
  1105  				case (err == nil) && wantError:
  1106  					t.Fatalf("json.Marshal error is nil, want non-nil")
  1107  				case (err != nil) && !wantError:
  1108  					t.Fatalf("json.Marshal error: %v", err)
  1109  				}
  1110  			}
  1111  		})
  1112  	}
  1113  
  1114  	for _, json := range jsonPackages {
  1115  		t.Run("Unmarshal", func(t *testing.T) {
  1116  			for _, value := range values {
  1117  				wantError := value.wantError(json.Version)
  1118  				out := reflect.New(reflect.TypeOf(value.in).Elem()).Interface()
  1119  				err := json.Unmarshal([]byte("{}"), out)
  1120  				switch {
  1121  				case (err == nil) && wantError:
  1122  					t.Fatalf("json.Unmarshal error is nil, want non-nil")
  1123  				case (err != nil) && !wantError:
  1124  					t.Fatalf("json.Unmarshal error: %v", err)
  1125  				}
  1126  			}
  1127  		})
  1128  	}
  1129  }
  1130  

View as plain text