blob: 6a6c32d292e95844571055c407f087f8676b768b [file] [log] [blame]
// 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"
"os"
"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) os.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 os.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}},
// 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)
}
}
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": &Small{Tag: "tag19"},
"20": nil,
},
EmptyMap: map[string]Small{},
Slice: []Small{{Tag: "tag20"}, {Tag: "tag21"}},
SliceP: []*Small{&Small{Tag: "tag22"}, nil, &Small{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": [],
"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": [],
"SliceP": [],
"PSlice": [
{
"Tag": "tag20"
},
{
"Tag": "tag21"
}
],
"PSliceP": [
{
"Tag": "tag22"
},
null,
{
"Tag": "tag23"
}
],
"EmptySlice": [],
"NilSlice": [],
"StringSlice": [],
"ByteSlice": "",
"Small": {
"Tag": ""
},
"PSmall": null,
"PPSmall": {
"Tag": "tag31"
},
"Interface": null,
"PInterface": 5.2
}`
var pallValueCompact = strings.Map(noSpace, pallValueIndent)