Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
v2_diff_test.go1131 linesDownload Raw Back to json
1// Copyright 2020 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.4 5//go:build goexperiment.jsonv26 7package json_test8 9import (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, and24// a rationale for why the behavior was changed.25 26var jsonPackages = []struct {27	Version   string28	Marshal   func(any) ([]byte, error)29	Unmarshal func([]byte, any) error30}{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 and41// 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 v144// where it also ignores dashes and underscores.45// This allows v2 to match fields regardless of whether the name is in46// snake_case, camelCase, or kebab-case.47//48// Related issue:49//50//	https://go.dev/issue/1475051func TestCaseSensitivity(t *testing.T) {52	type Fields struct {53		FieldA bool54		FieldB bool `json:"fooBar"`55		FieldC bool `json:"fizzBuzz,case:ignore"` // `case:ignore` is used by v2 to explicitly enable case-insensitive matching56	}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 to61			// whether the JSON member name would match the Go field name.62			type goName = string63			type jsonName = string64			onlyV1 := json.Version == "v1"65			onlyV2 := json.Version == "v2"66			allMatches := map[goName]map[jsonName]bool{67				"FieldA": {68					"FieldA": true,   // exact match69					"fielda": onlyV1, // v1 is case-insensitive by default70					"fieldA": onlyV1, // v1 is case-insensitive by default71					"FIELDA": onlyV1, // v1 is case-insensitive by default72					"FieldB": false,73					"FieldC": false,74				},75				"FieldB": {76					"fooBar":   true,   // exact match for explicitly specified JSON name77					"FooBar":   onlyV1, // v1 is case-insensitive even if an explicit JSON name is provided78					"foobar":   onlyV1, // v1 is case-insensitive even if an explicit JSON name is provided79					"FOOBAR":   onlyV1, // v1 is case-insensitive even if an explicit JSON name is provided80					"fizzBuzz": false,81					"FieldA":   false,82					"FieldB":   false, // explicit JSON name means that the Go field name is not used for matching83					"FieldC":   false,84				},85				"FieldC": {86					"fizzBuzz":  true,   // exact match for explicitly specified JSON name87					"fizzbuzz":  true,   // v2 is case-insensitive due to `case:ignore` tag88					"FIZZBUZZ":  true,   // v2 is case-insensitive due to `case:ignore` tag89					"fizz_buzz": onlyV2, // case-insensitivity in v2 ignores dashes and underscores90					"fizz-buzz": onlyV2, // case-insensitivity in v2 ignores dashes and underscores91					"fooBar":    false,92					"FieldA":    false,93					"FieldC":    false, // explicit JSON name means that the Go field name is not used for matching94					"FieldB":    false,95				},96			}97 98			for goFieldName, matches := range allMatches {99				for jsonMemberName, wantMatch := range matches {100					in := `{"` + jsonMemberName + `":true}`101					var s Fields102					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 omitted116// when marshaling if it is an empty Go value, which is defined as117// false, 0, a nil pointer, a nil interface value, and118// any empty array, slice, map, or string.119//120// In v2, the "omitempty" option specifies that a struct field is omitted121// when marshaling if it is an empty JSON value, which is defined as122// a JSON null or empty JSON string, object, or array.123//124// In v2, we also provide the "omitzero" option which specifies that a field125// 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 cover127// 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/11939134//	https://go.dev/issue/22480135//	https://go.dev/issue/29310136//	https://go.dev/issue/32675137//	https://go.dev/issue/45669138//	https://go.dev/issue/45787139//	https://go.dev/issue/50480140//	https://go.dev/issue/52803141func 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]any208			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 value216				"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 value222				"MapA":       false,223				"MapB":       false,224				"MapC":       true,225				"StructA":    onlyV1, // Struct{} is NOT an empty Go value, but {} is an empty JSON value226				"StructB":    onlyV1, // Struct{...} is NOT an empty Go value, but {} is an empty JSON value227				"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 value234				"PointerC":   true,235				"InterfaceA": false,236				"InterfaceB": onlyV1, // (*string)(nil) is NOT a nil Go interface, but null is an empty JSON value237				"InterfaceC": onlyV1, // new(string) is NOT a nil Go interface, but "" is an empty JSON value238				"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 250func addr[T any](v T) *T {251	return &v252}253 254// In v1, the "string" option specifies that Go strings, bools, and numeric255// values are encoded within a JSON string when marshaling and256// are unmarshaled from its native representation escaped within a JSON string.257// The "string" option is not applied recursively, and so does not affect258// strings, bools, and numeric values within a Go slice or map, but259// does have special handling to affect the underlying value within a pointer.260// When unmarshaling, the "string" option permits decoding from a JSON null261// escaped within a JSON string in some inconsistent cases.262//263// In v2, the "string" option specifies that only numeric values are encoded as264// a JSON number within a JSON string when marshaling and are unmarshaled265// 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 parsers271// 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 values274// be encoded within a JSON string. Since the main utility of stringification275// is for numeric values, v2 limits the effect of the "string" option276// 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 that281// the "string" option does not recursively affect numeric values282// within a composite type like a Go map, slice, or interface value.283// In v1, specifying the "string" option on composite type has no effect284// and so this would be a largely backwards compatible change.285//286// The ability to decode from a JSON null wrapped within a JSON string287// is removed in v2 because this behavior was surprising and inconsistent in v1.288//289// Related issues:290//291//	https://go.dev/issue/15624292//	https://go.dev/issue/20651293//	https://go.dev/issue/22177294//	https://go.dev/issue/32055295//	https://go.dev/issue/32117296//	https://go.dev/issue/50997297func 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 s340			}341			quoteOnlyV2 := func(s string) string {342				if json.Version == "v2" {343					s = quote(s)344				}345				return s346			}347			want := strings.Join([]string{348				`{`,349				`"String":` + quoteOnlyV1(`"string"`) + `,`, // in v1, Go strings are also stringified350				`"Bool":` + quoteOnlyV1("true") + `,`,       // in v1, Go bools are also stringified351				`"Int":` + quote("1") + `,`,352				`"Float":` + quote("1") + `,`,353				`"Map":{"Name":` + quoteOnlyV2("1") + `},`,     // in v2, numbers are recursively stringified354				`"Struct":{"Field":` + quoteOnlyV2("1") + `},`, // in v2, numbers are recursively stringified355				`"Slice":[` + quoteOnlyV2("1") + `],`,          // in v2, numbers are recursively stringified356				`"Array":[` + quoteOnlyV2("1") + `],`,          // in v2, numbers are recursively stringified357				`"PointerA":null,`,358				`"PointerB":` + quote("1") + `,`,       // in v1, numbers are stringified after a single pointer indirection359				`"PointerC":` + quoteOnlyV2("1") + `,`, // in v2, numbers are recursively stringified360				`"InterfaceA":null,`,361				`"InterfaceB":` + quoteOnlyV2("1") + ``, // in v2, numbers are recursively stringified362				`}`}, "")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 Types376			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 Types393			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 Types410			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 Types425			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 setting462// 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 the471// 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 marshal477// 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/27589483//	https://go.dev/issue/37711484func TestNilSlicesAndMaps(t *testing.T) {485	type Composites struct {486		B []byte            // always encoded in v2 as a JSON string487		S []string          // always encoded in v2 as a JSON array488		M map[string]string // always encoded in v2 as a JSON object489	}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 arrays500			}[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 array514// 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 to518// silent data loss when excess JSON array elements are discarded.519func 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]int523			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]int538			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 setting556// the "format:array" option in the `json` struct field tag:557//558//	struct {559//		B [32]byte `json:",format:array"`560//	}561func 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]byte586			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.598type CallCheck string599 600// MarshalJSON always returns a JSON string with the literal "CALLED".601func (*CallCheck) MarshalJSON() ([]byte, error) {602	return []byte(`"CALLED"`), nil603}604 605// UnmarshalJSON always stores a string with the literal "CALLED".606func (v *CallCheck) UnmarshalJSON([]byte) error {607	*v = `CALLED`608	return nil609}610 611// In v1, the implementation is inconsistent about whether it calls612// MarshalJSON and UnmarshalJSON methods declared on pointer receivers613// 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 methods618// 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/27722626//	https://go.dev/issue/33993627//	https://go.dev/issue/42508628func TestPointerReceiver(t *testing.T) {629	type Values struct {630		S []CallCheck631		A [1]CallCheck632		M map[string]CallCheck633		V CallCheck634		I any635	}636 637	for _, json := range jsonPackages {638		t.Run(path.Join("Marshal", json.Version), func(t *testing.T) {639			var cc CallCheck640			in := Values{641				S: []CallCheck{cc},642				A: [1]CallCheck{cc},             // MarshalJSON not called on v1643				M: map[string]CallCheck{"": cc}, // MarshalJSON not called on v1644				V: cc,                           // MarshalJSON not called on v1645				I: cc,                           // MarshalJSON not called on v1646			}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 called665			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 string672				},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 and702// 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/7872711//	https://go.dev/issue/33714712func TestMapDeterminism(t *testing.T) {713	const iterations = 10714	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)] = true725			}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 other743// similar classes of injection attacks (e.g., SQL injection).744// Users of JSON with HTML should either manually ensure that embedded JSON is745// properly escaped or be relying on a module like "github.com/google/safehtml"746// to handle safe interoperability of JSON and HTML.747func 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 by767// 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 difficult773// to detect until it is too late. Once it has been discovered, strict UTF-8774// behavior sometimes cannot be enabled since other logic may be depending775// on the current behavior due to Hyrum's Law.776//777// Tim Bray, the author of RFC 8259 recommends that implementations should778// 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-90782func 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 %q`, got, "\ufffd")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 string801			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 where815// they follow the inconsistent and difficult-to-explain merge semantics of v1.816// In v2, duplicate JSON object names are rejected by default where817// 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 should824// 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-90828//829// The lack of duplicate name rejection has correctness implications where830// roundtrip unmarshal/marshal do not result in semantically equivalent JSON.831// This is surprising behavior for users when they accidentally832// send JSON objects with duplicate names.833//834// The lack of duplicate name rejection may have security implications since it835// becomes difficult for a security tool to validate the semantic meaning of a836// 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-vulnerabilities838//839// Related issue:840//841//	https://go.dev/issue/48298842func 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 inconsistent861// 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 consistently863// 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 nulls866// are handled during Unmarshal. This semantic detail has no effect867// when Unmarshaling into a empty value.868//869// Related issues:870//871//	https://go.dev/issue/22177872//	https://go.dev/issue/33835873func TestMergeNull(t *testing.T) {874	type Types struct {875		Bool      bool876		String    string877		Bytes     []byte878		Int       int879		Map       map[string]string880		Struct    struct{ Field string }881		Slice     []string882		Array     [1]string883		Pointer   *string884		Interface any885	}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": null915			}`), &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 zeroed928			}[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 anything938// other than a JSON object, and recursively merges JSON objects.939//940// Merge semantics in v1 are inconsistent and difficult to explain941// largely because the behavior came about organically, rather than942// 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/21092948//	https://go.dev/issue/26946949//	https://go.dev/issue/27172950//	https://go.dev/issue/30701951//	https://go.dev/issue/31924952//	https://go.dev/issue/43664953func TestMergeComposite(t *testing.T) {954	type Tuple struct{ Old, New bool }955	type Composites struct {956		Slice            []Tuple957		Array            [1]Tuple958		Map              map[string]Tuple959		MapPointer       map[string]*Tuple960		Struct           struct{ Tuple Tuple }961		StructPointer    *struct{ Tuple Tuple }962		Interface        any963		InterfacePointer any964	}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},               // merged999					Array:            [1]Tuple{merged},                      // merged1000					Map:              map[string]Tuple{"Tuple": replaced},   // replaced1001					MapPointer:       map[string]*Tuple{"Tuple": &replaced}, // replaced1002					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},           // replaced1005					InterfacePointer: &merged,                               // merged (same as v2)1006				},1007				"v2": {1008					Slice:            []Tuple{replaced, replaced},         // replaced1009					Array:            [1]Tuple{replaced},                  // replaced1010					Map:              map[string]Tuple{"Tuple": merged},   // merged1011					MapPointer:       map[string]*Tuple{"Tuple": &merged}, // merged1012					Struct:           struct{ Tuple Tuple }{merged},       // merged (same as v1)1013					StructPointer:    &struct{ Tuple Tuple }{merged},      // merged (same as v1)1014					Interface:        merged,                              // merged1015					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 is1028// formatted and parsed using time.Duration.String and time.ParseDuration.1029//1030// Users of v2 can opt into the v1 behavior by setting1031// 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/102751040func TestTimeDurations(t *testing.T) {1041	t.SkipNow() // TODO(https://go.dev/issue/71631): The default representation of time.Duration is still undecided.1042	for _, json := range jsonPackages {1043		t.Run(path.Join("Marshal", json.Version), func(t *testing.T) {1044			got, err := json.Marshal(time.Minute)1045			switch {1046			case err != nil:1047				t.Fatalf("json.Marshal error: %v", err)1048			case json.Version == "v1" && string(got) != "60000000000":1049				t.Fatalf("json.Marshal = %s, want 60000000000", got)1050			case json.Version == "v2" && string(got) != `"1m0s"`:1051				t.Fatalf(`json.Marshal = %s, want "1m0s"`, got)1052			}1053		})1054	}1055 1056	for _, json := range jsonPackages {1057		t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) {1058			in := map[string]string{1059				"v1": "60000000000",1060				"v2": `"1m0s"`,1061			}[json.Version]1062			var got time.Duration1063			err := json.Unmarshal([]byte(in), &got)1064			switch {1065			case err != nil:1066				t.Fatalf("json.Unmarshal error: %v", err)1067			case got != time.Minute:1068				t.Fatalf("json.Unmarshal = %v, want 1m0s", got)1069			}1070		})1071	}1072}1073 1074// In v1, non-empty structs without any JSON serializable fields are permitted.1075// In v2, non-empty structs without any JSON serializable fields are rejected.1076//1077// The purpose of this change is to avoid a common pitfall for new users1078// where they expect JSON serialization to handle unexported fields.1079// However, this does not work since Go reflection does not1080// provide the package the ability to mutate such fields.1081// Rejecting unserializable structs in v2 is intended to be a clear signal1082// that the type is not supposed to be serialized.1083func TestEmptyStructs(t *testing.T) {1084	never := func(string) bool { return false }1085	onlyV2 := func(v string) bool { return v == "v2" }1086	values := []struct {1087		in        any1088		wantError func(string) bool1089	}{1090		// It is okay to marshal a truly empty struct in v1 and v2.1091		{in: addr(struct{}{}), wantError: never},1092		// In v1, a non-empty struct without exported fields1093		// is equivalent to an empty struct, but is rejected in v2.1094		// Note that errors.errorString type has only unexported fields.1095		{in: errors.New("error"), wantError: onlyV2},1096		// A mix of exported and unexported fields is permitted.1097		{in: addr(struct{ Exported, unexported int }{}), wantError: never},1098	}1099 1100	for _, json := range jsonPackages {1101		t.Run("Marshal", func(t *testing.T) {1102			for _, value := range values {1103				wantError := value.wantError(json.Version)1104				_, err := json.Marshal(value.in)1105				switch {1106				case (err == nil) && wantError:1107					t.Fatalf("json.Marshal error is nil, want non-nil")1108				case (err != nil) && !wantError:1109					t.Fatalf("json.Marshal error: %v", err)1110				}1111			}1112		})1113	}1114 1115	for _, json := range jsonPackages {1116		t.Run("Unmarshal", func(t *testing.T) {1117			for _, value := range values {1118				wantError := value.wantError(json.Version)1119				out := reflect.New(reflect.TypeOf(value.in).Elem()).Interface()1120				err := json.Unmarshal([]byte("{}"), out)1121				switch {1122				case (err == nil) && wantError:1123					t.Fatalf("json.Unmarshal error is nil, want non-nil")1124				case (err != nil) && !wantError:1125					t.Fatalf("json.Unmarshal error: %v", err)1126				}1127			}1128		})1129	}1130}1131 
codekingpro/portable-devtools · Team Ai