94252f4bcc
From-SVN: r184034
622 lines
12 KiB
Go
622 lines
12 KiB
Go
// 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)
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|