// Copyright 2020 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.

package json_test

import (
	"errors"
	"math"
	"path"
	"reflect"
	"strings"
	"testing"
	"time"

	jsonv1 "encoding/json"

	jsonv2 "k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"
)

// NOTE: This file serves as a list of semantic differences between v1 and v2.
// Each test explains how v1 behaves, how v2 behaves, and
// a rationale for why the behavior was changed.

var jsonPackages = []struct {
	Version   string
	Marshal   func(any) ([]byte, error)
	Unmarshal func([]byte, any) error
}{
	{"v1", jsonv1.Marshal, jsonv1.Unmarshal},
	{"v2", jsonv2.Marshal, jsonv2.Unmarshal},
}

// In v1, unmarshal matches struct fields using a case-insensitive match.
// In v2, unmarshal matches struct fields using a case-sensitive match.
//
// Case-insensitive matching is a surprising default and
// incurs significant performance cost when unmarshaling unknown fields.
// In v2, we can opt into v1-like behavior with the `nocase` tag option.
// The case-insensitive matching performed by v2 is looser than that of v1
// where it also ignores dashes and underscores.
// This allows v2 to match fields regardless of whether the name is in
// snake_case, camelCase, or kebab-case.
//
// Related issue:
//
//	https://go.dev/issue/14750
func TestCaseSensitivity(t *testing.T) {
	type Fields struct {
		FieldA bool
		FieldB bool `json:"fooBar"`
		FieldC bool `json:"fizzBuzz,nocase"` // `nocase` is used by v2 to explicitly enable case-insensitive matching
	}

	for _, json := range jsonPackages {
		t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) {
			// This is a mapping from Go field names to JSON member names to
			// whether the JSON member name would match the Go field name.
			type goName = string
			type jsonName = string
			onlyV1 := json.Version == "v1"
			onlyV2 := json.Version == "v2"
			allMatches := map[goName]map[jsonName]bool{
				"FieldA": {
					"FieldA": true,   // exact match
					"fielda": onlyV1, // v1 is case-insensitive by default
					"fieldA": onlyV1, // v1 is case-insensitive by default
					"FIELDA": onlyV1, // v1 is case-insensitive by default
					"FieldB": false,
					"FieldC": false,
				},
				"FieldB": {
					"fooBar":   true,   // exact match for explicitly specified JSON name
					"FooBar":   onlyV1, // v1 is case-insensitive even if an explicit JSON name is provided
					"foobar":   onlyV1, // v1 is case-insensitive even if an explicit JSON name is provided
					"FOOBAR":   onlyV1, // v1 is case-insensitive even if an explicit JSON name is provided
					"fizzBuzz": false,
					"FieldA":   false,
					"FieldB":   false, // explicit JSON name means that the Go field name is not used for matching
					"FieldC":   false,
				},
				"FieldC": {
					"fizzBuzz":  true,   // exact match for explicitly specified JSON name
					"fizzbuzz":  true,   // v2 is case-insensitive due to `nocase` tag
					"FIZZBUZZ":  true,   // v2 is case-insensitive due to `nocase` tag
					"fizz_buzz": onlyV2, // case-insensitivity in v2 ignores dashes and underscores
					"fizz-buzz": onlyV2, // case-insensitivity in v2 ignores dashes and underscores
					"fooBar":    false,
					"FieldA":    false,
					"FieldC":    false, // explicit JSON name means that the Go field name is not used for matching
					"FieldB":    false,
				},
			}

			for goFieldName, matches := range allMatches {
				for jsonMemberName, wantMatch := range matches {
					in := `{"` + jsonMemberName + `":true}`
					var s Fields
					if err := json.Unmarshal([]byte(in), &s); err != nil {
						t.Fatalf("json.Unmarshal error: %v", err)
					}
					gotMatch := reflect.ValueOf(s).FieldByName(goFieldName).Bool()
					if gotMatch != wantMatch {
						t.Fatalf("%T.%s = %v, want %v", s, goFieldName, gotMatch, wantMatch)
					}
				}
			}
		})
	}
}

// In v1, the "omitempty" option specifies that a struct field is omitted
// when marshaling if it is an empty Go value, which is defined as
// false, 0, a nil pointer, a nil interface value, and
// any empty array, slice, map, or string.
//
// In v2, the "omitempty" option specifies that a struct field is omitted
// when marshaling if it is an empty JSON value, which is defined as
// a JSON null or empty JSON string, object, or array.
//
// In v2, we also provide the "omitzero" option which specifies that a field
// is omitted if it is the zero Go value or if it implements an "IsZero() bool"
// method that reports true. Together, "omitzero" and "omitempty" can cover
// all the prior use cases of the v1 definition of "omitempty".
// Note that "omitempty" is defined in terms of the Go type system in v1,
// but now defined in terms of the JSON type system in v2.
//
// Related issues:
//
//	https://go.dev/issue/11939
//	https://go.dev/issue/22480
//	https://go.dev/issue/29310
//	https://go.dev/issue/32675
//	https://go.dev/issue/45669
//	https://go.dev/issue/45787
//	https://go.dev/issue/50480
//	https://go.dev/issue/52803
func TestOmitEmptyOption(t *testing.T) {
	type Struct struct {
		Foo string  `json:",omitempty"`
		Bar []int   `json:",omitempty"`
		Baz *Struct `json:",omitempty"`
	}
	type Types struct {
		Bool       bool              `json:",omitempty"`
		StringA    string            `json:",omitempty"`
		StringB    string            `json:",omitempty"`
		BytesA     []byte            `json:",omitempty"`
		BytesB     []byte            `json:",omitempty"`
		BytesC     []byte            `json:",omitempty"`
		Int        int               `json:",omitempty"`
		MapA       map[string]string `json:",omitempty"`
		MapB       map[string]string `json:",omitempty"`
		MapC       map[string]string `json:",omitempty"`
		StructA    Struct            `json:",omitempty"`
		StructB    Struct            `json:",omitempty"`
		StructC    Struct            `json:",omitempty"`
		SliceA     []string          `json:",omitempty"`
		SliceB     []string          `json:",omitempty"`
		SliceC     []string          `json:",omitempty"`
		Array      [1]string         `json:",omitempty"`
		PointerA   *string           `json:",omitempty"`
		PointerB   *string           `json:",omitempty"`
		PointerC   *string           `json:",omitempty"`
		InterfaceA any               `json:",omitempty"`
		InterfaceB any               `json:",omitempty"`
		InterfaceC any               `json:",omitempty"`
		InterfaceD any               `json:",omitempty"`
	}

	something := "something"
	for _, json := range jsonPackages {
		t.Run(path.Join("Marshal", json.Version), func(t *testing.T) {
			in := Types{
				Bool:       false,
				StringA:    "",
				StringB:    something,
				BytesA:     nil,
				BytesB:     []byte{},
				BytesC:     []byte(something),
				Int:        0,
				MapA:       nil,
				MapB:       map[string]string{},
				MapC:       map[string]string{something: something},
				StructA:    Struct{},
				StructB:    Struct{Bar: []int{}, Baz: new(Struct)},
				StructC:    Struct{Foo: something},
				SliceA:     nil,
				SliceB:     []string{},
				SliceC:     []string{something},
				Array:      [1]string{something},
				PointerA:   nil,
				PointerB:   new(string),
				PointerC:   &something,
				InterfaceA: nil,
				InterfaceB: (*string)(nil),
				InterfaceC: new(string),
				InterfaceD: &something,
			}
			b, err := json.Marshal(in)
			if err != nil {
				t.Fatalf("json.Marshal error: %v", err)
			}
			var out map[string]any
			if err := json.Unmarshal(b, &out); err != nil {
				t.Fatalf("json.Unmarshal error: %v", err)
			}

			onlyV1 := json.Version == "v1"
			onlyV2 := json.Version == "v2"
			wantPresent := map[string]bool{
				"Bool":       onlyV2, // false is an empty Go bool, but is NOT an empty JSON value
				"StringA":    false,
				"StringB":    true,
				"BytesA":     false,
				"BytesB":     false,
				"BytesC":     true,
				"Int":        onlyV2, // 0 is an empty Go integer, but NOT an empty JSON value
				"MapA":       false,
				"MapB":       false,
				"MapC":       true,
				"StructA":    onlyV1, // Struct{} is NOT an empty Go value, but {} is an empty JSON value
				"StructB":    onlyV1, // Struct{...} is NOT an empty Go value, but {} is an empty JSON value
				"StructC":    true,
				"SliceA":     false,
				"SliceB":     false,
				"SliceC":     true,
				"Array":      true,
				"PointerA":   false,
				"PointerB":   onlyV1, // new(string) is NOT a nil Go pointer, but "" is an empty JSON value
				"PointerC":   true,
				"InterfaceA": false,
				"InterfaceB": onlyV1, // (*string)(nil) is NOT a nil Go interface, but null is an empty JSON value
				"InterfaceC": onlyV1, // new(string) is NOT a nil Go interface, but "" is an empty JSON value
				"InterfaceD": true,
			}
			for field, want := range wantPresent {
				_, got := out[field]
				if got != want {
					t.Fatalf("%T.%s = %v, want %v", in, field, got, want)
				}
			}
		})
	}
}

func addr[T any](v T) *T {
	return &v
}

// In v1, the "string" option specifies that Go bools and numeric values are
// encoded within a JSON string when marshaling and are unmarshaled from
// either the native JSON representation (i.e., a JSON bool or number) or
// its native representation escaped within a JSON string.
// The "string" option is not applied recursively, and
// so does not affect bools and numeric values within a Go slice or map, but
// does have special handling to affect the underlying value within a pointer.
// When unmarshaling, the "string" option permits decoding from a JSON null
// escaped within a JSON string in some inconsistent cases.
//
// In v2, the "string" option specifies that only numeric values are encoded as
// a JSON number within a JSON string when marshaling and are unmarshaled
// from either a JSON number or a JSON string containing a JSON number.
// The "string" option is applied recursively to all numeric sub-values,
// and thus affects numeric values within a Go slice or map.
// There is no support for escaped JSON nulls within a JSON string.
//
// The main utility for stringifying JSON primitives (i.e., bools and numbers)
// is because JSON parsers often represents numbers as IEEE 754
// floating-point numbers. This results in a loss of precision when trying to
// represent 64-bit integer values. Consequently, many JSON-based APIs actually
// requires that such values be encoded within a JSON string.
// Given the main utility of stringification is for numeric values,
// v2 limits the effect of the "string" option to just numeric Go types.
// According to all code known by the Go module proxy,
// there are close to zero usages of the "string" option with a Go bool.
//
// Regarding the recursive application of the "string" option,
// there have been a number of issues filed about users being surprised that
// the "string" option does not recursively affect numeric values
// within a composite type like a Go map, slice, or interface value.
// In v1, specifying the "string" option on composite type has no effect
// and so this would be a largely backwards compatible change.
//
// The ability to decode from a JSON null wrapped within a JSON string
// is removed in v2 because this behavior was surprising and inconsistent in v1.
//
// Related issues:
//
//	https://go.dev/issue/15624
//	https://go.dev/issue/20651
//	https://go.dev/issue/22177
//	https://go.dev/issue/32055
//	https://go.dev/issue/32117
//	https://go.dev/issue/50997
func TestStringOption(t *testing.T) {
	type Types struct {
		Bool       bool                `json:",string"`
		Int        int                 `json:",string"`
		Float      float64             `json:",string"`
		Map        map[string]int      `json:",string"`
		Struct     struct{ Field int } `json:",string"`
		Slice      []int               `json:",string"`
		Array      [1]int              `json:",string"`
		PointerA   *int                `json:",string"`
		PointerB   *int                `json:",string"`
		PointerC   **int               `json:",string"`
		InterfaceA any                 `json:",string"`
		InterfaceB any                 `json:",string"`
	}

	for _, json := range jsonPackages {
		t.Run(path.Join("Marshal", json.Version), func(t *testing.T) {
			in := Types{
				Bool:       true,
				Int:        1,
				Float:      1,
				Map:        map[string]int{"Name": 1},
				Struct:     struct{ Field int }{1},
				Slice:      []int{1},
				Array:      [1]int{1},
				PointerA:   nil,
				PointerB:   addr(1),
				PointerC:   addr(addr(1)),
				InterfaceA: nil,
				InterfaceB: 1,
			}
			quote := func(s string) string { return `"` + s + `"` }
			quoteOnlyV1 := func(s string) string {
				if json.Version == "v1" {
					s = quote(s)
				}
				return s
			}
			quoteOnlyV2 := func(s string) string {
				if json.Version == "v2" {
					s = quote(s)
				}
				return s
			}
			want := strings.Join([]string{
				`{`,
				`"Bool":` + quoteOnlyV1("true") + `,`, // in v1, Go bool are also stringified
				`"Int":` + quote("1") + `,`,
				`"Float":` + quote("1") + `,`,
				`"Map":{"Name":` + quoteOnlyV2("1") + `},`,     // in v2, numbers are recursively stringified
				`"Struct":{"Field":` + quoteOnlyV2("1") + `},`, // in v2, numbers are recursively stringified
				`"Slice":[` + quoteOnlyV2("1") + `],`,          // in v2, numbers are recursively stringified
				`"Array":[` + quoteOnlyV2("1") + `],`,          // in v2, numbers are recursively stringified
				`"PointerA":null,`,
				`"PointerB":` + quote("1") + `,`,       // in v1, numbers are stringified after a single pointer indirection
				`"PointerC":` + quoteOnlyV2("1") + `,`, // in v2, numbers are recursively stringified
				`"InterfaceA":null,`,
				`"InterfaceB":` + quoteOnlyV2("1") + ``, // in v2, numbers are recursively stringified
				`}`}, "")
			got, err := json.Marshal(in)
			if err != nil {
				t.Fatalf("json.Marshal error: %v", err)
			}
			if string(got) != want {
				t.Fatalf("json.Marshal = %s, want %s", got, want)
			}
		})
	}

	for _, json := range jsonPackages {
		t.Run(path.Join("Unmarshal/Null", json.Version), func(t *testing.T) {
			var got Types
			err := json.Unmarshal([]byte(`{
				"Bool":     "null",
				"Int":      "null",
				"PointerA": "null"
			}`), &got)
			switch {
			case !reflect.DeepEqual(got, Types{}):
				t.Fatalf("json.Unmarshal = %v, want %v", got, Types{})
			case json.Version == "v1" && err != nil:
				t.Fatalf("json.Unmarshal error: %v", err)
			case json.Version == "v2" && err == nil:
				t.Fatal("json.Unmarshal error is nil, want non-nil")
			}
		})

		t.Run(path.Join("Unmarshal/Bool", json.Version), func(t *testing.T) {
			var got Types
			want := map[string]Types{
				"v1": {Bool: true},
				"v2": {Bool: false},
			}[json.Version]
			err := json.Unmarshal([]byte(`{"Bool": "true"}`), &got)
			switch {
			case !reflect.DeepEqual(got, want):
				t.Fatalf("json.Unmarshal = %v, want %v", got, want)
			case json.Version == "v1" && err != nil:
				t.Fatalf("json.Unmarshal error: %v", err)
			case json.Version == "v2" && err == nil:
				t.Fatal("json.Unmarshal error is nil, want non-nil")
			}
		})

		t.Run(path.Join("Unmarshal/Shallow", json.Version), func(t *testing.T) {
			var got Types
			want := Types{Int: 1, PointerB: addr(1)}
			err := json.Unmarshal([]byte(`{
				"Int":      "1",
				"PointerB": "1"
			}`), &got)
			switch {
			case !reflect.DeepEqual(got, want):
				t.Fatalf("json.Unmarshal = %v, want %v", got, want)
			case err != nil:
				t.Fatalf("json.Unmarshal error: %v", err)
			}
		})

		t.Run(path.Join("Unmarshal/Deep", json.Version), func(t *testing.T) {
			var got Types
			want := map[string]Types{
				"v1": {
					Map:      map[string]int{"Name": 0},
					Slice:    []int{0},
					PointerC: addr(addr(0)),
				},
				"v2": {
					Map:      map[string]int{"Name": 1},
					Struct:   struct{ Field int }{1},
					Slice:    []int{1},
					Array:    [1]int{1},
					PointerC: addr(addr(1)),
				},
			}[json.Version]
			err := json.Unmarshal([]byte(`{
				"Map":      {"Name":"1"},
				"Struct":   {"Field":"1"},
				"Slice":    ["1"],
				"Array":    ["1"],
				"PointerC": "1"
			}`), &got)
			switch {
			case !reflect.DeepEqual(got, want):
				t.Fatalf("json.Unmarshal =\n%v, want\n%v", got, want)
			case json.Version == "v1" && err == nil:
				t.Fatal("json.Unmarshal error is nil, want non-nil")
			case json.Version == "v2" && err != nil:
				t.Fatalf("json.Unmarshal error: %v", err)
			}
		})
	}
}

// In v1, nil slices and maps are marshaled as a JSON null.
// In v2, nil slices and maps are marshaled as an empty JSON object or array.
//
// Users of v2 can opt into the v1 behavior by setting
// the "format:emitnull" option in the `json` struct field tag:
//
//	struct {
//		S []string          `json:",format:emitnull"`
//		M map[string]string `json:",format:emitnull"`
//	}
//
// JSON is a language-agnostic data interchange format.
// The fact that maps and slices are nil-able in Go is a semantic detail of the
// Go language. We should avoid leaking such details to the JSON representation.
// When JSON implementations leak language-specific details,
// it complicates transition to/from languages with different type systems.
//
// Furthermore, consider two related Go types: string and []byte.
// It's an asymmetric oddity of v1 that zero values of string and []byte marshal
// as an empty JSON string for the former, while the latter as a JSON null.
// The non-zero values of those types always marshal as JSON strings.
//
// Related issues:
//
//	https://go.dev/issue/27589
//	https://go.dev/issue/37711
func TestNilSlicesAndMaps(t *testing.T) {
	type Composites struct {
		B []byte            // always encoded in v2 as a JSON string
		S []string          // always encoded in v2 as a JSON array
		M map[string]string // always encoded in v2 as a JSON object
	}

	for _, json := range jsonPackages {
		t.Run(path.Join("Marshal", json.Version), func(t *testing.T) {
			in := []Composites{
				{B: []byte(nil), S: []string(nil), M: map[string]string(nil)},
				{B: []byte{}, S: []string{}, M: map[string]string{}},
			}
			want := map[string]string{
				"v1": `[{"B":null,"S":null,"M":null},{"B":"","S":[],"M":{}}]`,
				"v2": `[{"B":"","S":[],"M":{}},{"B":"","S":[],"M":{}}]`, // v2 emits nil slices and maps as empty JSON objects and arrays
			}[json.Version]
			got, err := json.Marshal(in)
			if err != nil {
				t.Fatalf("json.Marshal error: %v", err)
			}
			if string(got) != want {
				t.Fatalf("json.Marshal = %s, want %s", got, want)
			}
		})
	}
}

// In v1, unmarshaling into a Go array permits JSON arrays with any length.
// In v2, unmarshaling into a Go array requires that the JSON array
// have the exact same number of elements as the Go array.
//
// Go arrays are often used because the exact length has significant meaning.
// Ignoring this detail seems like a mistake. Also, the v1 behavior leads to
// silent data loss when excess JSON array elements are discarded.
func TestArrays(t *testing.T) {
	for _, json := range jsonPackages {
		t.Run(path.Join("Unmarshal/TooFew", json.Version), func(t *testing.T) {
			var got [2]int
			err := json.Unmarshal([]byte(`[1]`), &got)
			switch {
			case got != [2]int{1, 0}:
				t.Fatalf(`json.Unmarshal = %v, want [1 0]`, got)
			case json.Version == "v1" && err != nil:
				t.Fatalf("json.Unmarshal error: %v", err)
			case json.Version == "v2" && err == nil:
				t.Fatal("json.Unmarshal error is nil, want non-nil")
			}
		})
	}

	for _, json := range jsonPackages {
		t.Run(path.Join("Unmarshal/TooMany", json.Version), func(t *testing.T) {
			var got [2]int
			err := json.Unmarshal([]byte(`[1,2,3]`), &got)
			switch {
			case got != [2]int{1, 2}:
				t.Fatalf(`json.Unmarshal = %v, want [1 2]`, got)
			case json.Version == "v1" && err != nil:
				t.Fatalf("json.Unmarshal error: %v", err)
			case json.Version == "v2" && err == nil:
				t.Fatal("json.Unmarshal error is nil, want non-nil")
			}
		})
	}
}

// In v1, byte arrays are treated as arrays of unsigned integers.
// In v2, byte arrays are treated as binary values (similar to []byte).
// This is to make the behavior of [N]byte and []byte more consistent.
//
// Users of v2 can opt into the v1 behavior by setting
// the "format:array" option in the `json` struct field tag:
//
//	struct {
//		B [32]byte `json:",format:array"`
//	}
func TestByteArrays(t *testing.T) {
	for _, json := range jsonPackages {
		t.Run(path.Join("Marshal", json.Version), func(t *testing.T) {
			in := [4]byte{1, 2, 3, 4}
			got, err := json.Marshal(in)
			if err != nil {
				t.Fatalf("json.Marshal error: %v", err)
			}
			want := map[string]string{
				"v1": `[1,2,3,4]`,
				"v2": `"AQIDBA=="`,
			}[json.Version]
			if string(got) != want {
				t.Fatalf("json.Marshal = %s, want %s", got, want)
			}
		})
	}

	for _, json := range jsonPackages {
		t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) {
			in := map[string]string{
				"v1": `[1,2,3,4]`,
				"v2": `"AQIDBA=="`,
			}[json.Version]
			var got [4]byte
			err := json.Unmarshal([]byte(in), &got)
			switch {
			case err != nil:
				t.Fatalf("json.Unmarshal error: %v", err)
			case got != [4]byte{1, 2, 3, 4}:
				t.Fatalf("json.Unmarshal = %v, want [1 2 3 4]", got)
			}
		})
	}
}

// CallCheck implements json.{Marshaler,Unmarshaler} on a pointer receiver.
type CallCheck string

// MarshalJSON always returns a JSON string with the literal "CALLED".
func (*CallCheck) MarshalJSON() ([]byte, error) {
	return []byte(`"CALLED"`), nil
}

// UnmarshalJSON always stores a string with the literal "CALLED".
func (v *CallCheck) UnmarshalJSON([]byte) error {
	*v = `CALLED`
	return nil
}

// In v1, the implementation is inconsistent about whether it calls
// MarshalJSON and UnmarshalJSON methods declared on pointer receivers
// when it has an unaddressable value (per reflect.Value.CanAddr) on hand.
// When marshaling, it never boxes the value on the heap to make it addressable,
// while it sometimes boxes values (e.g., for map entries) when unmarshaling.
//
// In v2, the implementation always calls MarshalJSON and UnmarshalJSON methods
// by boxing the value on the heap if necessary.
//
// The v1 behavior is surprising at best and buggy at worst.
// Unfortunately, it cannot be changed without breaking existing usages.
//
// Related issues:
//
//	https://go.dev/issue/27722
//	https://go.dev/issue/33993
//	https://go.dev/issue/42508
func TestPointerReceiver(t *testing.T) {
	type Values struct {
		S []CallCheck
		A [1]CallCheck
		M map[string]CallCheck
		V CallCheck
		I any
	}

	for _, json := range jsonPackages {
		t.Run(path.Join("Marshal", json.Version), func(t *testing.T) {
			var cc CallCheck
			in := Values{
				S: []CallCheck{cc},
				A: [1]CallCheck{cc},             // MarshalJSON not called on v1
				M: map[string]CallCheck{"": cc}, // MarshalJSON not called on v1
				V: cc,                           // MarshalJSON not called on v1
				I: cc,                           // MarshalJSON not called on v1
			}
			want := map[string]string{
				"v1": `{"S":["CALLED"],"A":[""],"M":{"":""},"V":"","I":""}`,
				"v2": `{"S":["CALLED"],"A":["CALLED"],"M":{"":"CALLED"},"V":"CALLED","I":"CALLED"}`,
			}[json.Version]
			got, err := json.Marshal(in)
			if err != nil {
				t.Fatalf("json.Marshal error: %v", err)
			}
			if string(got) != want {
				t.Fatalf("json.Marshal = %s, want %s", got, want)
			}
		})
	}

	for _, json := range jsonPackages {
		t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) {
			in := `{"S":[""],"A":[""],"M":{"":""},"V":"","I":""}`
			called := CallCheck("CALLED") // resulting state if UnmarshalJSON is called
			want := map[string]Values{
				"v1": {
					S: []CallCheck{called},
					A: [1]CallCheck{called},
					M: map[string]CallCheck{"": called},
					V: called,
					I: "", // UnmarshalJSON not called on v1; replaced with Go string
				},
				"v2": {
					S: []CallCheck{called},
					A: [1]CallCheck{called},
					M: map[string]CallCheck{"": called},
					V: called,
					I: called,
				},
			}[json.Version]
			got := Values{
				A: [1]CallCheck{CallCheck("")},
				S: []CallCheck{CallCheck("")},
				M: map[string]CallCheck{"": CallCheck("")},
				V: CallCheck(""),
				I: CallCheck(""),
			}
			if err := json.Unmarshal([]byte(in), &got); err != nil {
				t.Fatalf("json.Unmarshal error: %v", err)
			}
			if !reflect.DeepEqual(got, want) {
				t.Fatalf("json.Unmarshal = %v, want %v", got, want)
			}
		})
	}
}

// In v1, maps are marshaled in a deterministic order.
// In v2, maps are marshaled in a non-deterministic order.
//
// The reason for the change is that v2 prioritizes performance and
// the guarantee that marshaling operates primarily in a streaming manner.
//
// The v2 API provides RawValue.Canonicalize if stability is needed:
//
//	(*json.RawValue)(&b).Canonicalize()
//
// Related issue:
//
//	https://go.dev/issue/7872
//	https://go.dev/issue/33714
func TestMapDeterminism(t *testing.T) {
	const iterations = 10
	in := map[int]int{0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9}

	for _, json := range jsonPackages {
		t.Run(path.Join("Marshal", json.Version), func(t *testing.T) {
			outs := make(map[string]bool)
			for i := 0; i < iterations; i++ {
				b, err := json.Marshal(in)
				if err != nil {
					t.Fatalf("json.Marshal error: %v", err)
				}
				outs[string(b)] = true
			}
			switch {
			case json.Version == "v1" && len(outs) != 1:
				t.Fatalf("json.Marshal encoded to %d unique forms, expected 1", len(outs))
			case json.Version == "v2" && len(outs) == 1:
				t.Logf("json.Marshal encoded to 1 unique form by chance; are you feeling lucky?")
			}
		})
	}
}

// In v1, JSON string encoding escapes special characters related to HTML.
// In v2, JSON string encoding uses a normalized representation (per RFC 8785).
//
// Users of v2 can opt into the v1 behavior by setting
// json.EncodeOptions.EscapeRune. See the EscapeHTML example.
//
// Escaping HTML-specific characters in a JSON library is a layering violation.
// It presumes that JSON is always used with HTML and ignores other
// similar classes of injection attacks (e.g., SQL injection).
// Users of JSON with HTML should either manually ensure that embedded JSON is
// properly escaped or be relying on a module like "github.com/google/safehtml"
// to handle safe interoperability of JSON and HTML.
func TestEscapeHTML(t *testing.T) {
	for _, json := range jsonPackages {
		t.Run(path.Join("Marshal", json.Version), func(t *testing.T) {
			const in = `<script> console.log("Hello, world!"); </script>`
			got, err := json.Marshal(in)
			if err != nil {
				t.Fatalf("json.Marshal error: %v", err)
			}
			want := map[string]string{
				"v1": `"\u003cscript\u003e console.log(\"Hello, world!\"); \u003c/script\u003e"`,
				"v2": `"<script> console.log(\"Hello, world!\"); </script>"`,
			}[json.Version]
			if string(got) != want {
				t.Fatalf("json.Marshal = %s, want %s", got, want)
			}
		})
	}
}

// In v1, JSON serialization silently ignored invalid UTF-8 by
// replacing such bytes with the Unicode replacement character.
// In v2, JSON serialization reports an error if invalid UTF-8 is encountered.
//
// Users of v2 can opt into the v1 behavior by setting
// AllowInvalidUTF8 to true in json.EncodeOptions or json.DecodeOptions:
//
//	json.MarshalOptions{...}.Marshal(json.EncodeOptions{AllowInvalidUTF8: true}, ...)
//	json.UnmarshalOptions{...}.Unmarshal(json.DecodeOptions{AllowInvalidUTF8: true}, ...)
//
// Silently allowing invalid UTF-8 causes data corruption that can be difficult
// to detect until it is too late. Once it has been discovered, strict UTF-8
// behavior sometimes cannot be enabled since other logic may be depending
// on the current behavior due to Hyrum's Law.
//
// Tim Bray, the author of RFC 8259 recommends that implementations should
// go beyond RFC 8259 and instead target compliance with RFC 7493,
// which makes strict decisions about behavior left undefined in RFC 8259.
// In particular, RFC 7493 rejects the presence of invalid UTF-8.
// See https://www.tbray.org/ongoing/When/201x/2017/12/14/RFC-8259-STD-90
func TestInvalidUTF8(t *testing.T) {
	for _, json := range jsonPackages {
		t.Run(path.Join("Marshal", json.Version), func(t *testing.T) {
			got, err := json.Marshal("\xff")
			switch {
			case json.Version == "v1" && err != nil:
				t.Fatalf("json.Marshal error: %v", err)
			case json.Version == "v1" && string(got) != `"\ufffd"`:
				t.Fatalf(`json.Marshal = %s, want "\ufffd"`, got)
			case json.Version == "v2" && err == nil:
				t.Fatal("json.Marshal error is nil, want non-nil")
			}
		})
	}

	for _, json := range jsonPackages {
		t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) {
			const in = "\"\xff\""
			var got string
			err := json.Unmarshal([]byte(in), &got)
			switch {
			case json.Version == "v1" && err != nil:
				t.Fatalf("json.Unmarshal error: %v", err)
			case json.Version == "v1" && got != "\ufffd":
				t.Fatalf(`json.Unmarshal = %q, want "\ufffd"`, got)
			case json.Version == "v2" && err == nil:
				t.Fatal("json.Unmarshal error is nil, want non-nil")
			}
		})
	}
}

// In v1, duplicate JSON object names are permitted by default where
// they follow the inconsistent and difficult-to-explain merge semantics of v1.
// In v2, duplicate JSON object names are rejected by default where
// they follow the merge semantics of v2 based on RFC 7396.
//
// Users of v2 can opt into the v1 behavior by setting
// AllowDuplicateNames to true in json.EncodeOptions or json.DecodeOptions:
//
//	json.MarshalOptions{...}.Marshal(json.EncodeOptions{AllowDuplicateNames: true}, ...)
//	json.UnmarshalOptions{...}.Unmarshal(json.DecodeOptions{AllowDuplicateNames: true}, ...)
//
// Per RFC 8259, the handling of duplicate names is left as undefined behavior.
// Rejecting such inputs is within the realm of valid behavior.
// Tim Bray, the author of RFC 8259 recommends that implementations should
// go beyond RFC 8259 and instead target compliance with RFC 7493,
// which makes strict decisions about behavior left undefined in RFC 8259.
// In particular, RFC 7493 rejects the presence of duplicate object names.
// See https://www.tbray.org/ongoing/When/201x/2017/12/14/RFC-8259-STD-90
//
// The lack of duplicate name rejection has correctness implications where
// roundtrip unmarshal/marshal do not result in semantically equivalent JSON.
// This is surprising behavior for users when they accidentally
// send JSON objects with duplicate names.
//
// The lack of duplicate name rejection may have security implications since it
// becomes difficult for a security tool to validate the semantic meaning of a
// JSON object since meaning is undefined in the presence of duplicate names.
// See https://labs.bishopfox.com/tech-blog/an-exploration-of-json-interoperability-vulnerabilities
//
// Related issue:
//
//	https://go.dev/issue/48298
func TestDuplicateNames(t *testing.T) {
	for _, json := range jsonPackages {
		t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) {
			const in = `{"Name":1,"Name":2}`
			var got struct{ Name int }
			err := json.Unmarshal([]byte(in), &got)
			switch {
			case json.Version == "v1" && err != nil:
				t.Fatalf("json.Unmarshal error: %v", err)
			case json.Version == "v1" && got != struct{ Name int }{2}:
				t.Fatalf(`json.Unmarshal = %v, want {2}`, got)
			case json.Version == "v2" && err == nil:
				t.Fatal("json.Unmarshal error is nil, want non-nil")
			}
		})
	}
}

// In v1, unmarshaling a JSON null into a non-empty value was inconsistent
// in that sometimes it would be ignored and other times clear the value.
// In v2, unmarshaling a JSON null into a non-empty value would consistently
// always clear the value regardless of the value's type.
//
// The purpose of this change is to have consistent behavior with how JSON nulls
// are handled during Unmarshal. This semantic detail has no effect
// when Unmarshaling into a empty value.
//
// Related issues:
//
//	https://go.dev/issue/22177
//	https://go.dev/issue/33835
func TestMergeNull(t *testing.T) {
	type Types struct {
		Bool      bool
		String    string
		Bytes     []byte
		Int       int
		Map       map[string]string
		Struct    struct{ Field string }
		Slice     []string
		Array     [1]string
		Pointer   *string
		Interface any
	}

	for _, json := range jsonPackages {
		t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) {
			// Start with a non-empty value where all fields are populated.
			in := Types{
				Bool:      true,
				String:    "old",
				Bytes:     []byte("old"),
				Int:       1234,
				Map:       map[string]string{"old": "old"},
				Struct:    struct{ Field string }{"old"},
				Slice:     []string{"old"},
				Array:     [1]string{"old"},
				Pointer:   new(string),
				Interface: "old",
			}

			// Unmarshal a JSON null into every field.
			if err := json.Unmarshal([]byte(`{
				"Bool":      null,
				"String":    null,
				"Bytes":     null,
				"Int":       null,
				"Map":       null,
				"Struct":    null,
				"Slice":     null,
				"Array":     null,
				"Pointer":   null,
				"Interface": null
			}`), &in); err != nil {
				t.Fatalf("json.Unmarshal error: %v", err)
			}

			want := map[string]Types{
				"v1": {
					Bool:   true,
					String: "old",
					Int:    1234,
					Struct: struct{ Field string }{"old"},
					Array:  [1]string{"old"},
				},
				"v2": {}, // all fields are zeroed
			}[json.Version]
			if !reflect.DeepEqual(in, want) {
				t.Fatalf("json.Unmarshal = %+v, want %+v", in, want)
			}
		})
	}
}

// In v1, merge semantics are inconsistent and difficult to explain.
// In v2, merge semantics replaces the destination value for anything
// other than a JSON object, and recursively merges JSON objects.
//
// Merge semantics in v1 are inconsistent and difficult to explain
// largely because the behavior came about organically, rather than
// having a principled approach to how the semantics should operate.
// In v2, merging follows behavior based on RFC 7396.
//
// Related issues:
//
//	https://go.dev/issue/21092
//	https://go.dev/issue/26946
//	https://go.dev/issue/27172
//	https://go.dev/issue/30701
//	https://go.dev/issue/31924
//	https://go.dev/issue/43664
func TestMergeComposite(t *testing.T) {
	type Tuple struct{ Old, New bool }
	type Composites struct {
		Slice            []Tuple
		Array            [1]Tuple
		Map              map[string]Tuple
		MapPointer       map[string]*Tuple
		Struct           struct{ Tuple Tuple }
		StructPointer    *struct{ Tuple Tuple }
		Interface        any
		InterfacePointer any
	}

	for _, json := range jsonPackages {
		t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) {
			// Start with a non-empty value where all fields are populated.
			in := Composites{
				Slice:            []Tuple{{Old: true}, {Old: true}}[:1],
				Array:            [1]Tuple{{Old: true}},
				Map:              map[string]Tuple{"Tuple": {Old: true}},
				MapPointer:       map[string]*Tuple{"Tuple": {Old: true}},
				Struct:           struct{ Tuple Tuple }{Tuple{Old: true}},
				StructPointer:    &struct{ Tuple Tuple }{Tuple{Old: true}},
				Interface:        Tuple{Old: true},
				InterfacePointer: &Tuple{Old: true},
			}

			// Unmarshal into every pre-populated field.
			if err := json.Unmarshal([]byte(`{
				"Slice":            [{"New":true}, {"New":true}],
				"Array":            [{"New":true}],
				"Map":              {"Tuple": {"New":true}},
				"MapPointer":       {"Tuple": {"New":true}},
				"Struct":           {"Tuple": {"New":true}},
				"StructPointer":    {"Tuple": {"New":true}},
				"Interface":        {"New":true},
				"InterfacePointer": {"New":true}
			}`), &in); err != nil {
				t.Fatalf("json.Unmarshal error: %v", err)
			}

			merged := Tuple{Old: true, New: true}
			replaced := Tuple{Old: false, New: true}
			want := map[string]Composites{
				"v1": {
					Slice:            []Tuple{merged, merged},               // merged
					Array:            [1]Tuple{merged},                      // merged
					Map:              map[string]Tuple{"Tuple": replaced},   // replaced
					MapPointer:       map[string]*Tuple{"Tuple": &replaced}, // replaced
					Struct:           struct{ Tuple Tuple }{merged},         // merged (same as v2)
					StructPointer:    &struct{ Tuple Tuple }{merged},        // merged (same as v2)
					Interface:        map[string]any{"New": true},           // replaced
					InterfacePointer: &merged,                               // merged (same as v2)
				},
				"v2": {
					Slice:            []Tuple{replaced, replaced},         // replaced
					Array:            [1]Tuple{replaced},                  // replaced
					Map:              map[string]Tuple{"Tuple": merged},   // merged
					MapPointer:       map[string]*Tuple{"Tuple": &merged}, // merged
					Struct:           struct{ Tuple Tuple }{merged},       // merged (same as v1)
					StructPointer:    &struct{ Tuple Tuple }{merged},      // merged (same as v1)
					Interface:        merged,                              // merged
					InterfacePointer: &merged,                             // merged (same as v1)
				},
			}[json.Version]
			if !reflect.DeepEqual(in, want) {
				t.Fatalf("json.Unmarshal = %+v, want %+v", in, want)
			}
		})
	}
}

// In v1, there was no special support for time.Duration,
// which resulted in that type simply being treated as a signed integer.
// In v2, there is now first-class support for time.Duration, where the type is
// formatted and parsed using time.Duration.String and time.ParseDuration.
//
// Users of v2 can opt into the v1 behavior by setting
// the "format:nanos" option in the `json` struct field tag:
//
//	struct {
//		Duration time.Duration `json:",format:nanos"`
//	}
//
// Related issue:
//
//	https://go.dev/issue/10275
func TestTimeDurations(t *testing.T) {
	for _, json := range jsonPackages {
		t.Run(path.Join("Marshal", json.Version), func(t *testing.T) {
			got, err := json.Marshal(time.Minute)
			switch {
			case err != nil:
				t.Fatalf("json.Marshal error: %v", err)
			case json.Version == "v1" && string(got) != "60000000000":
				t.Fatalf("json.Marshal = %s, want 60000000000", got)
			case json.Version == "v2" && string(got) != `"1m0s"`:
				t.Fatalf(`json.Marshal = %s, want "1m0s"`, got)
			}
		})
	}

	for _, json := range jsonPackages {
		t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) {
			in := map[string]string{
				"v1": "60000000000",
				"v2": `"1m0s"`,
			}[json.Version]
			var got time.Duration
			err := json.Unmarshal([]byte(in), &got)
			switch {
			case err != nil:
				t.Fatalf("json.Unmarshal error: %v", err)
			case got != time.Minute:
				t.Fatalf("json.Unmarshal = %v, want 1m0s", got)
			}
		})
	}
}

// In v1, unmarshaling a JSON number beyond the representation of a Go float
// would result in an error.
// In v2, unmarshaling a JSON number beyond the representation of a Go float
// would use the closest representable value (i.e., ±math.MaxFloatX).
//
// The rationale for the change is to ensure that
// if a JSON value is syntactically valid according to json.RawValue.IsValid,
// then it is always valid to unmarshal that into a Go any value.
func TestMaxFloats(t *testing.T) {
	for _, json := range jsonPackages {
		t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) {
			const in = `1e1000`
			var got any
			err := json.Unmarshal([]byte(in), &got)
			switch {
			case json.Version == "v1" && err == nil:
				t.Fatal("json.Unmarshal error is nil, want non-nil")
			case json.Version == "v2" && got != any(math.MaxFloat64):
				t.Fatalf("json.Unmarshal = %v, want %v", got, math.MaxFloat64)
			case json.Version == "v2" && err != nil:
				t.Fatalf("json.Unmarshal error: %v", err)
			}
		})
	}
}

// In v1, non-empty structs without any JSON serializable fields are permitted.
// In v2, non-empty structs without any JSON serializable fields are rejected.
//
// The purpose of this change is to avoid a common pitfall for new users
// where they expect JSON serialization to handle unexported fields.
// However, this does not work since Go reflection does not
// provide the package the ability to mutate such fields.
// Rejecting unserializable structs in v2 is intended to be a clear signal
// that the type is not supposed to be serialized.
func TestEmptyStructs(t *testing.T) {
	never := func(string) bool { return false }
	onlyV2 := func(v string) bool { return v == "v2" }
	values := []struct {
		in        any
		wantError func(string) bool
	}{
		// It is okay to marshal a truly empty struct in v1 and v2.
		{in: addr(struct{}{}), wantError: never},
		// In v1, a non-empty struct without exported fields
		// is equivalent to an empty struct, but is rejected in v2.
		// Note that errors.errorString type has only unexported fields.
		{in: errors.New("error"), wantError: onlyV2},
		// A mix of exported and unexported fields is permitted.
		{in: addr(struct{ Exported, unexported int }{}), wantError: never},
	}

	for _, json := range jsonPackages {
		t.Run("Marshal", func(t *testing.T) {
			for _, value := range values {
				wantError := value.wantError(json.Version)
				_, err := json.Marshal(value.in)
				switch {
				case (err == nil) && wantError:
					t.Fatalf("json.Marshal error is nil, want non-nil")
				case (err != nil) && !wantError:
					t.Fatalf("json.Marshal error: %v", err)
				}
			}
		})
	}

	for _, json := range jsonPackages {
		t.Run("Unmarshal", func(t *testing.T) {
			for _, value := range values {
				wantError := value.wantError(json.Version)
				out := reflect.New(reflect.TypeOf(value.in).Elem()).Interface()
				err := json.Unmarshal([]byte("{}"), out)
				switch {
				case (err == nil) && wantError:
					t.Fatalf("json.Unmarshal error is nil, want non-nil")
				case (err != nil) && !wantError:
					t.Fatalf("json.Unmarshal error: %v", err)
				}
			}
		})
	}
}

// In v1, embedding an unexported type with exported fields is permitted.
// If it is an embedded pointer, then the struct cannot be unmarshaled into.
// In v2, embedding an unexported type with exported fields is never permitted.
//
// The visibility of exported fields promoted through an embedded unexported
// struct type is difficult to explain. Even worse, the use of Go reflection
// does not directly correspond to what the Go language permits or rejects.
//
// The ability to marshal/unmarshal exported fields of unexported embedded types
// came about organically and has been the subject of various bugs in v1.
// Currently, the implementation in v1 performs a few hacks to make this work
// partially since the Go reflect package does not directly permit access.
// This behavior is only partial since it is impossible to unmarshal into
// the promoted fields of an embedded pointer to an unexported struct type
// since the implementation cannot allocate the containing struct value
// because the embedded field is unexported.
//
// Due to all of the subtle behavior and nuances surrounding embedded
// unexported struct types, v2 rejects all such cases and requires that authors
// explicitly mark such fields as being ignored with `json:"-"`.
//
// Related issues:
//
//	https://go.dev/issue/21353
//	https://go.dev/issue/21357
//	https://go.dev/issue/24153
func TestEmbedUnexported(t *testing.T) {
	never := func(string) bool { return false }
	onlyV2 := func(v string) bool { return v == "v2" }
	always := func(string) bool { return true }
	type Exported struct{ Field string }
	type unexported struct{ Field string }
	values := []struct {
		in                 any
		wantMarshalError   func(string) bool
		wantUnmarshalError func(string) bool
	}{
		// Embedding exported types is permitted in both v1 and v2.
		{in: struct{ Exported }{}, wantMarshalError: never, wantUnmarshalError: never},
		{in: struct{ *Exported }{}, wantMarshalError: never, wantUnmarshalError: never},
		// In v2, embedded unexported types are always rejected (unless ignored).
		{in: struct{ unexported }{}, wantMarshalError: onlyV2, wantUnmarshalError: onlyV2},
		{in: struct{ *unexported }{}, wantMarshalError: onlyV2, wantUnmarshalError: always},
		// In v2, embedded unexported types must be explicitly ignored.
		{in: struct {
			unexported `json:"-"`
		}{}, wantMarshalError: never, wantUnmarshalError: never},
		{in: struct {
			*unexported `json:"-"`
		}{}, wantMarshalError: never, wantUnmarshalError: never},
	}

	for _, json := range jsonPackages {
		t.Run("Marshal", func(t *testing.T) {
			for _, value := range values {
				wantError := value.wantMarshalError(json.Version)
				_, err := json.Marshal(value.in)
				switch {
				case (err == nil) && wantError:
					t.Fatalf("json.Marshal error is nil, want non-nil")
				case (err != nil) && !wantError:
					t.Fatalf("json.Marshal error: %v", err)
				}
			}
		})
	}

	for _, json := range jsonPackages {
		t.Run("Unmarshal", func(t *testing.T) {
			for _, value := range values {
				wantError := value.wantUnmarshalError(json.Version)
				out := reflect.New(reflect.TypeOf(value.in)).Interface()
				err := json.Unmarshal([]byte(`{"Field":"Value"}`), out)
				switch {
				case (err == nil) && wantError:
					t.Fatalf("json.Unmarshal error is nil, want non-nil")
				case (err != nil) && !wantError:
					t.Fatalf("json.Unmarshal error: %v", err)
				}
			}
		})
	}
}
