mirror of
https://github.com/golang/go
synced 2024-10-05 00:21:21 -06:00
990f9f4c00
We should, after Go 1, make them work the same as package xml, that is, make them appear in the outer struct. For now turn them off so that people do not depend on the old behavior. Fixing them is issue 3069. R=golang-dev, bradfitz CC=golang-dev https://golang.org/cl/5656102
651 lines
13 KiB
Go
651 lines
13 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)
|
|
}
|
|
}
|
|
|
|
// 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")
|
|
}
|
|
}
|