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