Skip to content
Snippets Groups Projects
decode_test.go 11.6 KiB
Newer Older
  • Learn to ignore specific revisions
  • // 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 (
    
    type T struct {
    	X string
    	Y int
    
    type tx struct {
    	x int
    }
    
    
    Russ Cox's avatar
    Russ Cox committed
    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
    }
    
    
    Russ Cox's avatar
    Russ Cox committed
    type ustruct struct {
    	M unmarshaler
    }
    
    
    var (
    	um0, um1 unmarshaler // target2 of unmarshaling
    	ump      = &um1
    	umtrue   = unmarshaler{true}
    
    	umslice  = []unmarshaler{{true}}
    
    Russ Cox's avatar
    Russ Cox committed
    	umslicep = new([]unmarshaler)
    	umstruct = ustruct{unmarshaler{true}}
    
    type unmarshalTest struct {
    	in  string
    	ptr interface{}
    	out interface{}
    
    var unmarshalTests = []unmarshalTest{
    	// basic types
    
    Robert Griesemer's avatar
    Robert Griesemer committed
    	{`true`, new(bool), true, nil},
    	{`1`, new(int), 1, nil},
    
    	{`1.2`, new(float64), 1.2, nil},
    
    Robert Griesemer's avatar
    Robert Griesemer committed
    	{`-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},
    
    Russ Cox's avatar
    Russ Cox committed
    	{`{"X": [1,2,3], "Y": 4}`, new(T), T{Y: 4}, &UnmarshalTypeError{"array", reflect.TypeOf("")}},
    
    Robert Griesemer's avatar
    Robert Griesemer committed
    	{`{"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}},
    
    	// composite tests
    
    Robert Griesemer's avatar
    Robert Griesemer committed
    	{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},
    
    Russ Cox's avatar
    Russ Cox committed
    	{`[{"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
    	}
    
    Russ Cox's avatar
    Russ Cox committed
    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 {
    
    Rob Pike's avatar
    Rob Pike committed
    		t.Fatalf("Marshal did not return InvalidUTF8Error: %T %v", err, err)
    
    func TestUnmarshal(t *testing.T) {
    	for i, tt := range unmarshalTests {
    
    		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)
    
    Russ Cox's avatar
    Russ Cox committed
    		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)
    	}
    }
    
    
    Micah Stetson's avatar
    Micah Stetson committed
    func TestHTMLEscape(t *testing.T) {
    	b, err := MarshalForHTML("foobarbaz<>&quux")
    	if err != nil {
    		t.Fatalf("MarshalForHTML error: %v", err)
    	}
    	if !bytes.Equal(b, []byte(`"foobarbaz\u003c\u003e\u0026quux"`)) {
    		t.Fatalf("Unexpected encoding of \"<>&\": %s", b)
    	}
    }
    
    
    // WrongString is a struct that's misusing the ,string modifier.
    type WrongString struct {
    	Message string `json:"result,string"`
    }
    
    type wrongStringTest struct {
    	in, err string
    }
    
    // TODO(bradfitz): as part of Issue 2331, fix these tests' expected
    // error values to be helpful, rather than the confusing messages they
    // are now.
    var wrongStringTests = []wrongStringTest{
    	{`{"result":"x"}`, "JSON decoder out of sync - data changing underfoot?"},
    	{`{"result":"foo"}`, "json: cannot unmarshal bool into Go value of type string"},
    	{`{"result":"123"}`, "json: cannot unmarshal number into Go value of type 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"`
    
    	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{}
    
    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",
    
    	String:  "16",
    	Map: map[string]Small{
    
    Robert Griesemer's avatar
    Robert Griesemer committed
    		"17": {Tag: "tag17"},
    		"18": {Tag: "tag18"},
    
    	},
    	MapP: map[string]*Small{
    
    Russ Cox's avatar
    Russ Cox committed
    		"19": {Tag: "tag19"},
    
    		"20": nil,
    	},
    	EmptyMap:    map[string]Small{},
    
    Robert Griesemer's avatar
    Robert Griesemer committed
    	Slice:       []Small{{Tag: "tag20"}, {Tag: "tag21"}},
    
    Russ Cox's avatar
    Russ Cox committed
    	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",
    
    	"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": {
    
    			"Tag": "tag17"
    
    			"Tag": "tag18"
    
    			"Tag": "tag19"
    
    	"PMap": null,
    	"PMapP": null,
    	"EmptyMap": {},
    	"NilMap": null,
    	"Slice": [
    
    			"Tag": "tag20"
    
    			"Tag": "tag21"
    
    	"SliceP": [
    
    			"Tag": "tag22"
    
    			"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,
    
    	"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": {
    
    			"Tag": "tag17"
    
    			"Tag": "tag18"
    
    			"Tag": "tag19"
    
    	"EmptyMap": null,
    	"NilMap": null,
    
    	"Slice": null,
    	"SliceP": null,
    
    	"PSlice": [
    
    			"Tag": "tag20"
    
    			"Tag": "tag21"
    
    	"PSliceP": [
    
    			"Tag": "tag22"
    
    			"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)