github.com/anthropics/anthropic-sdk-go@v1.70.1/internal/apijson/json_test.go (about) 1 package apijson 2 3 import ( 4 "reflect" 5 "strings" 6 "testing" 7 "time" 8 9 "github.com/tidwall/gjson" 10 ) 11 12 func P[T any](v T) *T { return &v } 13 14 type Primitives struct { 15 A bool `json:"a"` 16 B int `json:"b"` 17 C uint `json:"c"` 18 D float64 `json:"d"` 19 E float32 `json:"e"` 20 F []int `json:"f"` 21 } 22 23 type PrimitivePointers struct { 24 A *bool `json:"a"` 25 B *int `json:"b"` 26 C *uint `json:"c"` 27 D *float64 `json:"d"` 28 E *float32 `json:"e"` 29 F *[]int `json:"f"` 30 } 31 32 type Slices struct { 33 Slice []Primitives `json:"slices"` 34 } 35 36 type DateTime struct { 37 Date time.Time `json:"date" format:"date"` 38 DateTime time.Time `json:"date-time" format:"date-time"` 39 } 40 41 type AdditionalProperties struct { 42 A bool `json:"a"` 43 ExtraFields map[string]any `json:"-" api:"extrafields"` 44 } 45 46 type TypedAdditionalProperties struct { 47 A bool `json:"a"` 48 ExtraFields map[string]int `json:"-" api:"extrafields"` 49 } 50 51 type EmbeddedStruct struct { 52 A bool `json:"a"` 53 B string `json:"b"` 54 55 JSON EmbeddedStructJSON 56 } 57 58 type EmbeddedStructJSON struct { 59 A Field 60 B Field 61 ExtraFields map[string]Field 62 raw string 63 } 64 65 type EmbeddedStructs struct { 66 EmbeddedStruct 67 A *int `json:"a"` 68 ExtraFields map[string]any `json:"-" api:"extrafields"` 69 70 JSON EmbeddedStructsJSON 71 } 72 73 type EmbeddedStructsJSON struct { 74 A Field 75 ExtraFields map[string]Field 76 raw string 77 } 78 79 type Recursive struct { 80 Name string `json:"name"` 81 Child *Recursive `json:"child"` 82 } 83 84 type JSONFieldStruct struct { 85 A bool `json:"a"` 86 B int64 `json:"b"` 87 C string `json:"c"` 88 D string `json:"d"` 89 ExtraFields map[string]int64 `json:"" api:"extrafields"` 90 JSON JSONFieldStructJSON `json:"-" api:"metadata"` 91 } 92 93 type JSONFieldStructJSON struct { 94 A Field 95 B Field 96 C Field 97 D Field 98 ExtraFields map[string]Field 99 raw string 100 } 101 102 type UnknownStruct struct { 103 Unknown any `json:"unknown"` 104 } 105 106 type UnionStruct struct { 107 Union Union `json:"union" format:"date"` 108 } 109 110 type Union interface { 111 union() 112 } 113 114 type Inline struct { 115 InlineField Primitives `json:",inline"` 116 JSON InlineJSON `json:"-" api:"metadata"` 117 } 118 119 type InlineArray struct { 120 InlineField []string `json:",inline"` 121 JSON InlineJSON `json:"-" api:"metadata"` 122 } 123 124 type InlineJSON struct { 125 InlineField Field 126 raw string 127 } 128 129 type UnionInteger int64 130 131 func (UnionInteger) union() {} 132 133 type UnionStructA struct { 134 Type string `json:"type"` 135 A string `json:"a"` 136 B string `json:"b"` 137 } 138 139 func (UnionStructA) union() {} 140 141 type UnionStructB struct { 142 Type string `json:"type"` 143 A string `json:"a"` 144 } 145 146 func (UnionStructB) union() {} 147 148 type UnionTime time.Time 149 150 func (UnionTime) union() {} 151 152 func init() { 153 RegisterUnion[Union]("type", 154 UnionVariant{ 155 TypeFilter: gjson.String, 156 Type: reflect.TypeOf(UnionTime{}), 157 }, 158 UnionVariant{ 159 TypeFilter: gjson.Number, 160 Type: reflect.TypeOf(UnionInteger(0)), 161 }, 162 UnionVariant{ 163 TypeFilter: gjson.JSON, 164 DiscriminatorValue: "typeA", 165 Type: reflect.TypeOf(UnionStructA{}), 166 }, 167 UnionVariant{ 168 TypeFilter: gjson.JSON, 169 DiscriminatorValue: "typeB", 170 Type: reflect.TypeOf(UnionStructB{}), 171 }, 172 ) 173 } 174 175 type ComplexUnionStruct struct { 176 Union ComplexUnion `json:"union"` 177 } 178 179 type ComplexUnion interface { 180 complexUnion() 181 } 182 183 type ComplexUnionA struct { 184 Boo string `json:"boo"` 185 Foo bool `json:"foo"` 186 } 187 188 func (ComplexUnionA) complexUnion() {} 189 190 type ComplexUnionB struct { 191 Boo bool `json:"boo"` 192 Foo string `json:"foo"` 193 } 194 195 func (ComplexUnionB) complexUnion() {} 196 197 type ComplexUnionC struct { 198 Boo int64 `json:"boo"` 199 } 200 201 func (ComplexUnionC) complexUnion() {} 202 203 type ComplexUnionTypeA struct { 204 Baz int64 `json:"baz"` 205 Type TypeA `json:"type"` 206 } 207 208 func (ComplexUnionTypeA) complexUnion() {} 209 210 type TypeA string 211 212 func (t TypeA) IsKnown() bool { 213 return t == "a" 214 } 215 216 type ComplexUnionTypeB struct { 217 Baz int64 `json:"baz"` 218 Type TypeB `json:"type"` 219 } 220 221 type TypeB string 222 223 func (t TypeB) IsKnown() bool { 224 return t == "b" 225 } 226 227 type UnmarshalStruct struct { 228 Foo string `json:"foo"` 229 prop bool `json:"-"` 230 } 231 232 func (r *UnmarshalStruct) UnmarshalJSON(json []byte) error { 233 r.prop = true 234 return UnmarshalRoot(json, r) 235 } 236 237 func (ComplexUnionTypeB) complexUnion() {} 238 239 func init() { 240 RegisterUnion[ComplexUnion]("", 241 UnionVariant{ 242 TypeFilter: gjson.JSON, 243 Type: reflect.TypeOf(ComplexUnionA{}), 244 }, 245 UnionVariant{ 246 TypeFilter: gjson.JSON, 247 Type: reflect.TypeOf(ComplexUnionB{}), 248 }, 249 UnionVariant{ 250 TypeFilter: gjson.JSON, 251 Type: reflect.TypeOf(ComplexUnionC{}), 252 }, 253 UnionVariant{ 254 TypeFilter: gjson.JSON, 255 Type: reflect.TypeOf(ComplexUnionTypeA{}), 256 }, 257 UnionVariant{ 258 TypeFilter: gjson.JSON, 259 Type: reflect.TypeOf(ComplexUnionTypeB{}), 260 }, 261 ) 262 } 263 264 type MarshallingUnionStruct struct { 265 Union MarshallingUnion 266 } 267 268 func (r *MarshallingUnionStruct) UnmarshalJSON(data []byte) (err error) { 269 *r = MarshallingUnionStruct{} 270 err = UnmarshalRoot(data, &r.Union) 271 return err 272 } 273 274 func (r MarshallingUnionStruct) MarshalJSON() (data []byte, err error) { 275 return MarshalRoot(r.Union) 276 } 277 278 type MarshallingUnion interface { 279 marshallingUnion() 280 } 281 282 type MarshallingUnionA struct { 283 Boo string `json:"boo"` 284 } 285 286 func (MarshallingUnionA) marshallingUnion() {} 287 288 func (r *MarshallingUnionA) UnmarshalJSON(data []byte) (err error) { 289 return UnmarshalRoot(data, r) 290 } 291 292 type MarshallingUnionB struct { 293 Foo string `json:"foo"` 294 } 295 296 func (MarshallingUnionB) marshallingUnion() {} 297 298 func (r *MarshallingUnionB) UnmarshalJSON(data []byte) (err error) { 299 return UnmarshalRoot(data, r) 300 } 301 302 func init() { 303 RegisterUnion[MarshallingUnion]( 304 "", 305 UnionVariant{ 306 TypeFilter: gjson.JSON, 307 Type: reflect.TypeOf(MarshallingUnionA{}), 308 }, 309 UnionVariant{ 310 TypeFilter: gjson.JSON, 311 Type: reflect.TypeOf(MarshallingUnionB{}), 312 }, 313 ) 314 } 315 316 var tests = map[string]struct { 317 buf string 318 val any 319 }{ 320 "true": {"true", true}, 321 "false": {"false", false}, 322 "int": {"1", 1}, 323 "int_bigger": {"12324", 12324}, 324 "int_string_coerce": {`"65"`, 65}, 325 "int_boolean_coerce": {"true", 1}, 326 "int64": {"1", int64(1)}, 327 "int64_huge": {"123456789123456789", int64(123456789123456789)}, 328 "uint": {"1", uint(1)}, 329 "uint_bigger": {"12324", uint(12324)}, 330 "uint_coerce": {`"65"`, uint(65)}, 331 "float_1.54": {"1.54", float32(1.54)}, 332 "float_1.89": {"1.89", float64(1.89)}, 333 "string": {`"str"`, "str"}, 334 "string_int_coerce": {`12`, "12"}, 335 "array_string": {`["foo","bar"]`, []string{"foo", "bar"}}, 336 "array_int": {`[1,2]`, []int{1, 2}}, 337 "array_int_coerce": {`["1",2]`, []int{1, 2}}, 338 339 "ptr_true": {"true", P(true)}, 340 "ptr_false": {"false", P(false)}, 341 "ptr_int": {"1", P(1)}, 342 "ptr_int_bigger": {"12324", P(12324)}, 343 "ptr_int_string_coerce": {`"65"`, P(65)}, 344 "ptr_int_boolean_coerce": {"true", P(1)}, 345 "ptr_int64": {"1", P(int64(1))}, 346 "ptr_int64_huge": {"123456789123456789", P(int64(123456789123456789))}, 347 "ptr_uint": {"1", P(uint(1))}, 348 "ptr_uint_bigger": {"12324", P(uint(12324))}, 349 "ptr_uint_coerce": {`"65"`, P(uint(65))}, 350 "ptr_float_1.54": {"1.54", P(float32(1.54))}, 351 "ptr_float_1.89": {"1.89", P(float64(1.89))}, 352 353 "date_time": {`"2007-03-01T13:00:00Z"`, time.Date(2007, time.March, 1, 13, 0, 0, 0, time.UTC)}, 354 "date_time_nano_coerce": {`"2007-03-01T13:03:05.123456789Z"`, time.Date(2007, time.March, 1, 13, 3, 5, 123456789, time.UTC)}, 355 356 "date_time_missing_t_coerce": {`"2007-03-01 13:03:05Z"`, time.Date(2007, time.March, 1, 13, 3, 5, 0, time.UTC)}, 357 "date_time_missing_timezone_coerce": {`"2007-03-01T13:03:05"`, time.Date(2007, time.March, 1, 13, 3, 5, 0, time.UTC)}, 358 // note: using -1200 to minimize probability of conflicting with the local timezone of the test runner 359 // see https://en.wikipedia.org/wiki/UTC%E2%88%9212:00 360 "date_time_missing_timezone_colon_coerce": {`"2007-03-01T13:03:05-1200"`, time.Date(2007, time.March, 1, 13, 3, 5, 0, time.FixedZone("", -12*60*60))}, 361 "date_time_nano_missing_t_coerce": {`"2007-03-01 13:03:05.123456789Z"`, time.Date(2007, time.March, 1, 13, 3, 5, 123456789, time.UTC)}, 362 363 "map_string": {`{"foo":"bar"}`, map[string]string{"foo": "bar"}}, 364 "map_string_with_sjson_path_chars": {`{":a.b.c*:d*-1e.f":"bar"}`, map[string]string{":a.b.c*:d*-1e.f": "bar"}}, 365 "map_interface": {`{"a":1,"b":"str","c":false}`, map[string]any{"a": float64(1), "b": "str", "c": false}}, 366 367 "primitive_struct": { 368 `{"a":false,"b":237628372683,"c":654,"d":9999.43,"e":43.76,"f":[1,2,3,4]}`, 369 Primitives{A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}}, 370 }, 371 372 "slices": { 373 `{"slices":[{"a":false,"b":237628372683,"c":654,"d":9999.43,"e":43.76,"f":[1,2,3,4]}]}`, 374 Slices{ 375 Slice: []Primitives{{A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}}}, 376 }, 377 }, 378 379 "primitive_pointer_struct": { 380 `{"a":false,"b":237628372683,"c":654,"d":9999.43,"e":43.76,"f":[1,2,3,4,5]}`, 381 PrimitivePointers{ 382 A: P(false), 383 B: P(237628372683), 384 C: P(uint(654)), 385 D: P(9999.43), 386 E: P(float32(43.76)), 387 F: &[]int{1, 2, 3, 4, 5}, 388 }, 389 }, 390 391 "datetime_struct": { 392 `{"date":"2006-01-02","date-time":"2006-01-02T15:04:05Z"}`, 393 DateTime{ 394 Date: time.Date(2006, time.January, 2, 0, 0, 0, 0, time.UTC), 395 DateTime: time.Date(2006, time.January, 2, 15, 4, 5, 0, time.UTC), 396 }, 397 }, 398 399 "additional_properties": { 400 `{"a":true,"bar":"value","foo":true}`, 401 AdditionalProperties{ 402 A: true, 403 ExtraFields: map[string]any{ 404 "bar": "value", 405 "foo": true, 406 }, 407 }, 408 }, 409 410 "embedded_struct": { 411 `{"a":1,"b":"bar"}`, 412 EmbeddedStructs{ 413 EmbeddedStruct: EmbeddedStruct{ 414 A: true, 415 B: "bar", 416 JSON: EmbeddedStructJSON{ 417 A: Field{raw: `1`, status: valid}, 418 B: Field{raw: `"bar"`, status: valid}, 419 raw: `{"a":1,"b":"bar"}`, 420 }, 421 }, 422 A: P(1), 423 ExtraFields: map[string]any{"b": "bar"}, 424 JSON: EmbeddedStructsJSON{ 425 A: Field{raw: `1`, status: valid}, 426 ExtraFields: map[string]Field{ 427 "b": {raw: `"bar"`, status: valid}, 428 }, 429 raw: `{"a":1,"b":"bar"}`, 430 }, 431 }, 432 }, 433 434 "recursive_struct": { 435 `{"child":{"name":"Alex"},"name":"Robert"}`, 436 Recursive{Name: "Robert", Child: &Recursive{Name: "Alex"}}, 437 }, 438 439 "metadata_coerce": { 440 `{"a":"12","b":"12","c":null,"extra_typed":12,"extra_untyped":{"foo":"bar"}}`, 441 JSONFieldStruct{ 442 A: false, 443 B: 12, 444 C: "", 445 JSON: JSONFieldStructJSON{ 446 raw: `{"a":"12","b":"12","c":null,"extra_typed":12,"extra_untyped":{"foo":"bar"}}`, 447 A: Field{raw: `"12"`, status: invalid}, 448 B: Field{raw: `"12"`, status: valid}, 449 C: Field{raw: "null", status: null}, 450 D: Field{raw: "", status: missing}, 451 ExtraFields: map[string]Field{ 452 "extra_typed": { 453 raw: "12", 454 status: valid, 455 }, 456 "extra_untyped": { 457 raw: `{"foo":"bar"}`, 458 status: invalid, 459 }, 460 }, 461 }, 462 ExtraFields: map[string]int64{ 463 "extra_typed": 12, 464 "extra_untyped": 0, 465 }, 466 }, 467 }, 468 469 "unknown_struct_number": { 470 `{"unknown":12}`, 471 UnknownStruct{ 472 Unknown: 12., 473 }, 474 }, 475 476 "unknown_struct_map": { 477 `{"unknown":{"foo":"bar"}}`, 478 UnknownStruct{ 479 Unknown: map[string]any{ 480 "foo": "bar", 481 }, 482 }, 483 }, 484 485 "union_integer": { 486 `{"union":12}`, 487 UnionStruct{ 488 Union: UnionInteger(12), 489 }, 490 }, 491 492 "union_struct_discriminated_a": { 493 `{"union":{"a":"foo","b":"bar","type":"typeA"}}`, 494 UnionStruct{ 495 Union: UnionStructA{ 496 Type: "typeA", 497 A: "foo", 498 B: "bar", 499 }, 500 }, 501 }, 502 503 "union_struct_discriminated_b": { 504 `{"union":{"a":"foo","type":"typeB"}}`, 505 UnionStruct{ 506 Union: UnionStructB{ 507 Type: "typeB", 508 A: "foo", 509 }, 510 }, 511 }, 512 513 "union_struct_time": { 514 `{"union":"2010-05-23"}`, 515 UnionStruct{ 516 Union: UnionTime(time.Date(2010, 05, 23, 0, 0, 0, 0, time.UTC)), 517 }, 518 }, 519 520 "complex_union_a": { 521 `{"union":{"boo":"12","foo":true}}`, 522 ComplexUnionStruct{Union: ComplexUnionA{Boo: "12", Foo: true}}, 523 }, 524 525 "complex_union_b": { 526 `{"union":{"boo":true,"foo":"12"}}`, 527 ComplexUnionStruct{Union: ComplexUnionB{Boo: true, Foo: "12"}}, 528 }, 529 530 "complex_union_c": { 531 `{"union":{"boo":12}}`, 532 ComplexUnionStruct{Union: ComplexUnionC{Boo: 12}}, 533 }, 534 535 "complex_union_type_a": { 536 `{"union":{"baz":12,"type":"a"}}`, 537 ComplexUnionStruct{Union: ComplexUnionTypeA{Baz: 12, Type: TypeA("a")}}, 538 }, 539 540 "complex_union_type_b": { 541 `{"union":{"baz":12,"type":"b"}}`, 542 ComplexUnionStruct{Union: ComplexUnionTypeB{Baz: 12, Type: TypeB("b")}}, 543 }, 544 545 "marshalling_union_a": { 546 `{"boo":"hello"}`, 547 MarshallingUnionStruct{Union: MarshallingUnionA{Boo: "hello"}}, 548 }, 549 "marshalling_union_b": { 550 `{"foo":"hi"}`, 551 MarshallingUnionStruct{Union: MarshallingUnionB{Foo: "hi"}}, 552 }, 553 554 "unmarshal": { 555 `{"foo":"hello"}`, 556 &UnmarshalStruct{Foo: "hello", prop: true}, 557 }, 558 559 "array_of_unmarshal": { 560 `[{"foo":"hello"}]`, 561 []UnmarshalStruct{{Foo: "hello", prop: true}}, 562 }, 563 564 "inline_coerce": { 565 `{"a":false,"b":237628372683,"c":654,"d":9999.43,"e":43.76,"f":[1,2,3,4]}`, 566 Inline{ 567 InlineField: Primitives{A: false, B: 237628372683, C: 0x28e, D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}}, 568 JSON: InlineJSON{ 569 InlineField: Field{raw: "{\"a\":false,\"b\":237628372683,\"c\":654,\"d\":9999.43,\"e\":43.76,\"f\":[1,2,3,4]}", status: 3}, 570 raw: "{\"a\":false,\"b\":237628372683,\"c\":654,\"d\":9999.43,\"e\":43.76,\"f\":[1,2,3,4]}", 571 }, 572 }, 573 }, 574 575 "inline_array_coerce": { 576 `["Hello","foo","bar"]`, 577 InlineArray{ 578 InlineField: []string{"Hello", "foo", "bar"}, 579 JSON: InlineJSON{ 580 InlineField: Field{raw: `["Hello","foo","bar"]`, status: 3}, 581 raw: `["Hello","foo","bar"]`, 582 }, 583 }, 584 }, 585 } 586 587 func TestDecode(t *testing.T) { 588 for name, test := range tests { 589 t.Run(name, func(t *testing.T) { 590 result := reflect.New(reflect.TypeOf(test.val)) 591 if err := Unmarshal([]byte(test.buf), result.Interface()); err != nil { 592 t.Fatalf("deserialization of %v failed with error %v", result, err) 593 } 594 if !reflect.DeepEqual(result.Elem().Interface(), test.val) { 595 t.Fatalf("expected '%s' to deserialize to \n%#v\nbut got\n%#v", test.buf, test.val, result.Elem().Interface()) 596 } 597 }) 598 } 599 } 600 601 func TestEncode(t *testing.T) { 602 for name, test := range tests { 603 if strings.HasSuffix(name, "_coerce") { 604 continue 605 } 606 t.Run(name, func(t *testing.T) { 607 raw, err := Marshal(test.val) 608 if err != nil { 609 t.Fatalf("serialization of %v failed with error %v", test.val, err) 610 } 611 if string(raw) != test.buf { 612 t.Fatalf("expected %+#v to serialize to %s but got %s", test.val, test.buf, string(raw)) 613 } 614 }) 615 } 616 } 617 618 type StructWithDefault struct { 619 Type string `json:"type" default:"foo"` 620 } 621 622 func TestDefault(t *testing.T) { 623 value := StructWithDefault{} 624 expected := `{"type":"foo"}` 625 626 raw, err := Marshal(value) 627 if err != nil { 628 t.Fatalf("serialization of %v failed with error %v", value, err) 629 } 630 if string(raw) != expected { 631 t.Fatalf("expected %+#v to serialize to %s but got %s", value, expected, string(raw)) 632 } 633 }