| // Copyright 2010 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package json | |
| import ( | |
| "bytes" | |
| "fmt" | |
| "reflect" | |
| "strings" | |
| "testing" | |
| ) | |
| type T struct { | |
| X string | |
| Y int | |
| Z int `json:"-"` | |
| } | |
| type tx struct { | |
| x int | |
| } | |
| var txType = reflect.TypeOf((*tx)(nil)).Elem() | |
| // A type that can unmarshal itself. | |
| type unmarshaler struct { | |
| T bool | |
| } | |
| func (u *unmarshaler) UnmarshalJSON(b []byte) error { | |
| *u = unmarshaler{true} // All we need to see that UnmarshalJson is called. | |
| return nil | |
| } | |
| type ustruct struct { | |
| M unmarshaler | |
| } | |
| var ( | |
| um0, um1 unmarshaler // target2 of unmarshaling | |
| ump = &um1 | |
| umtrue = unmarshaler{true} | |
| umslice = []unmarshaler{{true}} | |
| umslicep = new([]unmarshaler) | |
| umstruct = ustruct{unmarshaler{true}} | |
| ) | |
| type unmarshalTest struct { | |
| in string | |
| ptr interface{} | |
| out interface{} | |
| err error | |
| } | |
| var unmarshalTests = []unmarshalTest{ | |
| // basic types | |
| {`true`, new(bool), true, nil}, | |
| {`1`, new(int), 1, nil}, | |
| {`1.2`, new(float64), 1.2, nil}, | |
| {`-5`, new(int16), int16(-5), nil}, | |
| {`"a\u1234"`, new(string), "a\u1234", nil}, | |
| {`"http:\/\/"`, new(string), "http://", nil}, | |
| {`"g-clef: \uD834\uDD1E"`, new(string), "g-clef: \U0001D11E", nil}, | |
| {`"invalid: \uD834x\uDD1E"`, new(string), "invalid: \uFFFDx\uFFFD", nil}, | |
| {"null", new(interface{}), nil, nil}, | |
| {`{"X": [1,2,3], "Y": 4}`, new(T), T{Y: 4}, &UnmarshalTypeError{"array", reflect.TypeOf("")}}, | |
| {`{"x": 1}`, new(tx), tx{}, &UnmarshalFieldError{"x", txType, txType.Field(0)}}, | |
| // Z has a "-" tag. | |
| {`{"Y": 1, "Z": 2}`, new(T), T{Y: 1}, nil}, | |
| // syntax errors | |
| {`{"X": "foo", "Y"}`, nil, nil, &SyntaxError{"invalid character '}' after object key", 17}}, | |
| {`[1, 2, 3+]`, nil, nil, &SyntaxError{"invalid character '+' after array element", 9}}, | |
| // array tests | |
| {`[1, 2, 3]`, new([3]int), [3]int{1, 2, 3}, nil}, | |
| {`[1, 2, 3]`, new([1]int), [1]int{1}, nil}, | |
| {`[1, 2, 3]`, new([5]int), [5]int{1, 2, 3, 0, 0}, nil}, | |
| // composite tests | |
| {allValueIndent, new(All), allValue, nil}, | |
| {allValueCompact, new(All), allValue, nil}, | |
| {allValueIndent, new(*All), &allValue, nil}, | |
| {allValueCompact, new(*All), &allValue, nil}, | |
| {pallValueIndent, new(All), pallValue, nil}, | |
| {pallValueCompact, new(All), pallValue, nil}, | |
| {pallValueIndent, new(*All), &pallValue, nil}, | |
| {pallValueCompact, new(*All), &pallValue, nil}, | |
| // unmarshal interface test | |
| {`{"T":false}`, &um0, umtrue, nil}, // use "false" so test will fail if custom unmarshaler is not called | |
| {`{"T":false}`, &ump, &umtrue, nil}, | |
| {`[{"T":false}]`, &umslice, umslice, nil}, | |
| {`[{"T":false}]`, &umslicep, &umslice, nil}, | |
| {`{"M":{"T":false}}`, &umstruct, umstruct, nil}, | |
| } | |
| func TestMarshal(t *testing.T) { | |
| b, err := Marshal(allValue) | |
| if err != nil { | |
| t.Fatalf("Marshal allValue: %v", err) | |
| } | |
| if string(b) != allValueCompact { | |
| t.Errorf("Marshal allValueCompact") | |
| diff(t, b, []byte(allValueCompact)) | |
| return | |
| } | |
| b, err = Marshal(pallValue) | |
| if err != nil { | |
| t.Fatalf("Marshal pallValue: %v", err) | |
| } | |
| if string(b) != pallValueCompact { | |
| t.Errorf("Marshal pallValueCompact") | |
| diff(t, b, []byte(pallValueCompact)) | |
| return | |
| } | |
| } | |
| func TestMarshalBadUTF8(t *testing.T) { | |
| s := "hello\xffworld" | |
| b, err := Marshal(s) | |
| if err == nil { | |
| t.Fatal("Marshal bad UTF8: no error") | |
| } | |
| if len(b) != 0 { | |
| t.Fatal("Marshal returned data") | |
| } | |
| if _, ok := err.(*InvalidUTF8Error); !ok { | |
| t.Fatalf("Marshal did not return InvalidUTF8Error: %T %v", err, err) | |
| } | |
| } | |
| func TestUnmarshal(t *testing.T) { | |
| for i, tt := range unmarshalTests { | |
| var scan scanner | |
| in := []byte(tt.in) | |
| if err := checkValid(in, &scan); err != nil { | |
| if !reflect.DeepEqual(err, tt.err) { | |
| t.Errorf("#%d: checkValid: %#v", i, err) | |
| continue | |
| } | |
| } | |
| if tt.ptr == nil { | |
| continue | |
| } | |
| // v = new(right-type) | |
| v := reflect.New(reflect.TypeOf(tt.ptr).Elem()) | |
| if err := Unmarshal([]byte(in), v.Interface()); !reflect.DeepEqual(err, tt.err) { | |
| t.Errorf("#%d: %v want %v", i, err, tt.err) | |
| continue | |
| } | |
| if !reflect.DeepEqual(v.Elem().Interface(), tt.out) { | |
| t.Errorf("#%d: mismatch\nhave: %#+v\nwant: %#+v", i, v.Elem().Interface(), tt.out) | |
| data, _ := Marshal(v.Elem().Interface()) | |
| println(string(data)) | |
| data, _ = Marshal(tt.out) | |
| println(string(data)) | |
| continue | |
| } | |
| } | |
| } | |
| func TestUnmarshalMarshal(t *testing.T) { | |
| initBig() | |
| var v interface{} | |
| if err := Unmarshal(jsonBig, &v); err != nil { | |
| t.Fatalf("Unmarshal: %v", err) | |
| } | |
| b, err := Marshal(v) | |
| if err != nil { | |
| t.Fatalf("Marshal: %v", err) | |
| } | |
| if bytes.Compare(jsonBig, b) != 0 { | |
| t.Errorf("Marshal jsonBig") | |
| diff(t, b, jsonBig) | |
| return | |
| } | |
| } | |
| func TestLargeByteSlice(t *testing.T) { | |
| s0 := make([]byte, 2000) | |
| for i := range s0 { | |
| s0[i] = byte(i) | |
| } | |
| b, err := Marshal(s0) | |
| if err != nil { | |
| t.Fatalf("Marshal: %v", err) | |
| } | |
| var s1 []byte | |
| if err := Unmarshal(b, &s1); err != nil { | |
| t.Fatalf("Unmarshal: %v", err) | |
| } | |
| if bytes.Compare(s0, s1) != 0 { | |
| t.Errorf("Marshal large byte slice") | |
| diff(t, s0, s1) | |
| } | |
| } | |
| type Xint struct { | |
| X int | |
| } | |
| func TestUnmarshalInterface(t *testing.T) { | |
| var xint Xint | |
| var i interface{} = &xint | |
| if err := Unmarshal([]byte(`{"X":1}`), &i); err != nil { | |
| t.Fatalf("Unmarshal: %v", err) | |
| } | |
| if xint.X != 1 { | |
| t.Fatalf("Did not write to xint") | |
| } | |
| } | |
| func TestUnmarshalPtrPtr(t *testing.T) { | |
| var xint Xint | |
| pxint := &xint | |
| if err := Unmarshal([]byte(`{"X":1}`), &pxint); err != nil { | |
| t.Fatalf("Unmarshal: %v", err) | |
| } | |
| if xint.X != 1 { | |
| t.Fatalf("Did not write to xint") | |
| } | |
| } | |
| func TestEscape(t *testing.T) { | |
| const input = `"foobar"<html>` | |
| const expected = `"\"foobar\"\u003chtml\u003e"` | |
| b, err := Marshal(input) | |
| if err != nil { | |
| t.Fatalf("Marshal error: %v", err) | |
| } | |
| if s := string(b); s != expected { | |
| t.Errorf("Encoding of [%s] was [%s], want [%s]", input, s, expected) | |
| } | |
| } | |
| // WrongString is a struct that's misusing the ,string modifier. | |
| type WrongString struct { | |
| Message string `json:"result,string"` | |
| } | |
| type wrongStringTest struct { | |
| in, err string | |
| } | |
| var wrongStringTests = []wrongStringTest{ | |
| {`{"result":"x"}`, `json: invalid use of ,string struct tag, trying to unmarshal "x" into string`}, | |
| {`{"result":"foo"}`, `json: invalid use of ,string struct tag, trying to unmarshal "foo" into string`}, | |
| {`{"result":"123"}`, `json: invalid use of ,string struct tag, trying to unmarshal "123" into string`}, | |
| } | |
| // If people misuse the ,string modifier, the error message should be | |
| // helpful, telling the user that they're doing it wrong. | |
| func TestErrorMessageFromMisusedString(t *testing.T) { | |
| for n, tt := range wrongStringTests { | |
| r := strings.NewReader(tt.in) | |
| var s WrongString | |
| err := NewDecoder(r).Decode(&s) | |
| got := fmt.Sprintf("%v", err) | |
| if got != tt.err { | |
| t.Errorf("%d. got err = %q, want %q", n, got, tt.err) | |
| } | |
| } | |
| } | |
| func noSpace(c rune) rune { | |
| if isSpace(c) { | |
| return -1 | |
| } | |
| return c | |
| } | |
| type All struct { | |
| Bool bool | |
| Int int | |
| Int8 int8 | |
| Int16 int16 | |
| Int32 int32 | |
| Int64 int64 | |
| Uint uint | |
| Uint8 uint8 | |
| Uint16 uint16 | |
| Uint32 uint32 | |
| Uint64 uint64 | |
| Uintptr uintptr | |
| Float32 float32 | |
| Float64 float64 | |
| Foo string `json:"bar"` | |
| Foo2 string `json:"bar2,dummyopt"` | |
| IntStr int64 `json:",string"` | |
| PBool *bool | |
| PInt *int | |
| PInt8 *int8 | |
| PInt16 *int16 | |
| PInt32 *int32 | |
| PInt64 *int64 | |
| PUint *uint | |
| PUint8 *uint8 | |
| PUint16 *uint16 | |
| PUint32 *uint32 | |
| PUint64 *uint64 | |
| PUintptr *uintptr | |
| PFloat32 *float32 | |
| PFloat64 *float64 | |
| String string | |
| PString *string | |
| Map map[string]Small | |
| MapP map[string]*Small | |
| PMap *map[string]Small | |
| PMapP *map[string]*Small | |
| EmptyMap map[string]Small | |
| NilMap map[string]Small | |
| Slice []Small | |
| SliceP []*Small | |
| PSlice *[]Small | |
| PSliceP *[]*Small | |
| EmptySlice []Small | |
| NilSlice []Small | |
| StringSlice []string | |
| ByteSlice []byte | |
| Small Small | |
| PSmall *Small | |
| PPSmall **Small | |
| Interface interface{} | |
| PInterface *interface{} | |
| unexported int | |
| } | |
| type Small struct { | |
| Tag string | |
| } | |
| var allValue = All{ | |
| Bool: true, | |
| Int: 2, | |
| Int8: 3, | |
| Int16: 4, | |
| Int32: 5, | |
| Int64: 6, | |
| Uint: 7, | |
| Uint8: 8, | |
| Uint16: 9, | |
| Uint32: 10, | |
| Uint64: 11, | |
| Uintptr: 12, | |
| Float32: 14.1, | |
| Float64: 15.1, | |
| Foo: "foo", | |
| Foo2: "foo2", | |
| IntStr: 42, | |
| String: "16", | |
| Map: map[string]Small{ | |
| "17": {Tag: "tag17"}, | |
| "18": {Tag: "tag18"}, | |
| }, | |
| MapP: map[string]*Small{ | |
| "19": {Tag: "tag19"}, | |
| "20": nil, | |
| }, | |
| EmptyMap: map[string]Small{}, | |
| Slice: []Small{{Tag: "tag20"}, {Tag: "tag21"}}, | |
| SliceP: []*Small{{Tag: "tag22"}, nil, {Tag: "tag23"}}, | |
| EmptySlice: []Small{}, | |
| StringSlice: []string{"str24", "str25", "str26"}, | |
| ByteSlice: []byte{27, 28, 29}, | |
| Small: Small{Tag: "tag30"}, | |
| PSmall: &Small{Tag: "tag31"}, | |
| Interface: 5.2, | |
| } | |
| var pallValue = All{ | |
| PBool: &allValue.Bool, | |
| PInt: &allValue.Int, | |
| PInt8: &allValue.Int8, | |
| PInt16: &allValue.Int16, | |
| PInt32: &allValue.Int32, | |
| PInt64: &allValue.Int64, | |
| PUint: &allValue.Uint, | |
| PUint8: &allValue.Uint8, | |
| PUint16: &allValue.Uint16, | |
| PUint32: &allValue.Uint32, | |
| PUint64: &allValue.Uint64, | |
| PUintptr: &allValue.Uintptr, | |
| PFloat32: &allValue.Float32, | |
| PFloat64: &allValue.Float64, | |
| PString: &allValue.String, | |
| PMap: &allValue.Map, | |
| PMapP: &allValue.MapP, | |
| PSlice: &allValue.Slice, | |
| PSliceP: &allValue.SliceP, | |
| PPSmall: &allValue.PSmall, | |
| PInterface: &allValue.Interface, | |
| } | |
| var allValueIndent = `{ | |
| "Bool": true, | |
| "Int": 2, | |
| "Int8": 3, | |
| "Int16": 4, | |
| "Int32": 5, | |
| "Int64": 6, | |
| "Uint": 7, | |
| "Uint8": 8, | |
| "Uint16": 9, | |
| "Uint32": 10, | |
| "Uint64": 11, | |
| "Uintptr": 12, | |
| "Float32": 14.1, | |
| "Float64": 15.1, | |
| "bar": "foo", | |
| "bar2": "foo2", | |
| "IntStr": "42", | |
| "PBool": null, | |
| "PInt": null, | |
| "PInt8": null, | |
| "PInt16": null, | |
| "PInt32": null, | |
| "PInt64": null, | |
| "PUint": null, | |
| "PUint8": null, | |
| "PUint16": null, | |
| "PUint32": null, | |
| "PUint64": null, | |
| "PUintptr": null, | |
| "PFloat32": null, | |
| "PFloat64": null, | |
| "String": "16", | |
| "PString": null, | |
| "Map": { | |
| "17": { | |
| "Tag": "tag17" | |
| }, | |
| "18": { | |
| "Tag": "tag18" | |
| } | |
| }, | |
| "MapP": { | |
| "19": { | |
| "Tag": "tag19" | |
| }, | |
| "20": null | |
| }, | |
| "PMap": null, | |
| "PMapP": null, | |
| "EmptyMap": {}, | |
| "NilMap": null, | |
| "Slice": [ | |
| { | |
| "Tag": "tag20" | |
| }, | |
| { | |
| "Tag": "tag21" | |
| } | |
| ], | |
| "SliceP": [ | |
| { | |
| "Tag": "tag22" | |
| }, | |
| null, | |
| { | |
| "Tag": "tag23" | |
| } | |
| ], | |
| "PSlice": null, | |
| "PSliceP": null, | |
| "EmptySlice": [], | |
| "NilSlice": null, | |
| "StringSlice": [ | |
| "str24", | |
| "str25", | |
| "str26" | |
| ], | |
| "ByteSlice": "Gxwd", | |
| "Small": { | |
| "Tag": "tag30" | |
| }, | |
| "PSmall": { | |
| "Tag": "tag31" | |
| }, | |
| "PPSmall": null, | |
| "Interface": 5.2, | |
| "PInterface": null | |
| }` | |
| var allValueCompact = strings.Map(noSpace, allValueIndent) | |
| var pallValueIndent = `{ | |
| "Bool": false, | |
| "Int": 0, | |
| "Int8": 0, | |
| "Int16": 0, | |
| "Int32": 0, | |
| "Int64": 0, | |
| "Uint": 0, | |
| "Uint8": 0, | |
| "Uint16": 0, | |
| "Uint32": 0, | |
| "Uint64": 0, | |
| "Uintptr": 0, | |
| "Float32": 0, | |
| "Float64": 0, | |
| "bar": "", | |
| "bar2": "", | |
| "IntStr": "0", | |
| "PBool": true, | |
| "PInt": 2, | |
| "PInt8": 3, | |
| "PInt16": 4, | |
| "PInt32": 5, | |
| "PInt64": 6, | |
| "PUint": 7, | |
| "PUint8": 8, | |
| "PUint16": 9, | |
| "PUint32": 10, | |
| "PUint64": 11, | |
| "PUintptr": 12, | |
| "PFloat32": 14.1, | |
| "PFloat64": 15.1, | |
| "String": "", | |
| "PString": "16", | |
| "Map": null, | |
| "MapP": null, | |
| "PMap": { | |
| "17": { | |
| "Tag": "tag17" | |
| }, | |
| "18": { | |
| "Tag": "tag18" | |
| } | |
| }, | |
| "PMapP": { | |
| "19": { | |
| "Tag": "tag19" | |
| }, | |
| "20": null | |
| }, | |
| "EmptyMap": null, | |
| "NilMap": null, | |
| "Slice": null, | |
| "SliceP": null, | |
| "PSlice": [ | |
| { | |
| "Tag": "tag20" | |
| }, | |
| { | |
| "Tag": "tag21" | |
| } | |
| ], | |
| "PSliceP": [ | |
| { | |
| "Tag": "tag22" | |
| }, | |
| null, | |
| { | |
| "Tag": "tag23" | |
| } | |
| ], | |
| "EmptySlice": null, | |
| "NilSlice": null, | |
| "StringSlice": null, | |
| "ByteSlice": null, | |
| "Small": { | |
| "Tag": "" | |
| }, | |
| "PSmall": null, | |
| "PPSmall": { | |
| "Tag": "tag31" | |
| }, | |
| "Interface": null, | |
| "PInterface": 5.2 | |
| }` | |
| var pallValueCompact = strings.Map(noSpace, pallValueIndent) | |
| func TestRefUnmarshal(t *testing.T) { | |
| type S struct { | |
| // Ref is defined in encode_test.go. | |
| R0 Ref | |
| R1 *Ref | |
| } | |
| want := S{ | |
| R0: 12, | |
| R1: new(Ref), | |
| } | |
| *want.R1 = 12 | |
| var got S | |
| if err := Unmarshal([]byte(`{"R0":"ref","R1":"ref"}`), &got); err != nil { | |
| t.Fatalf("Unmarshal: %v", err) | |
| } | |
| if !reflect.DeepEqual(got, want) { | |
| t.Errorf("got %+v, want %+v", got, want) | |
| } | |
| } | |
| // Test that anonymous fields are ignored. | |
| // We may assign meaning to them later. | |
| func TestAnonymous(t *testing.T) { | |
| type S struct { | |
| T | |
| N int | |
| } | |
| data, err := Marshal(new(S)) | |
| if err != nil { | |
| t.Fatalf("Marshal: %v", err) | |
| } | |
| want := `{"N":0}` | |
| if string(data) != want { | |
| t.Fatalf("Marshal = %#q, want %#q", string(data), want) | |
| } | |
| var s S | |
| if err := Unmarshal([]byte(`{"T": 1, "T": {"Y": 1}, "N": 2}`), &s); err != nil { | |
| t.Fatalf("Unmarshal: %v", err) | |
| } | |
| if s.N != 2 { | |
| t.Fatal("Unmarshal: did not set N") | |
| } | |
| if s.T.Y != 0 { | |
| t.Fatal("Unmarshal: did set T.Y") | |
| } | |
| } |