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  }