blob: 3e96fa67ae7ca08b1338f4db84eded6b2dd152c4 [file] [log] [blame]
// Copyright 2018 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 (
"math"
"strings"
"testing"
"unicode/utf8"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
)
func Test(t *testing.T) {
const space = " \n\r\t"
var V = ValueOf
type Arr = []Value
type Obj = [][2]Value
tests := []struct {
in string
wantVal Value
wantOut string
wantOutIndent string
wantErr string
}{{
in: ``,
wantErr: `unexpected EOF`,
}, {
in: space,
wantErr: `unexpected EOF`,
}, {
in: space + `null` + space,
wantVal: V(nil),
wantOut: `null`,
wantOutIndent: `null`,
}, {
in: space + `true` + space,
wantVal: V(true),
wantOut: `true`,
wantOutIndent: `true`,
}, {
in: space + `false` + space,
wantVal: V(false),
wantOut: `false`,
wantOutIndent: `false`,
}, {
in: space + `0` + space,
wantVal: V(0.0),
wantOut: `0`,
wantOutIndent: `0`,
}, {
in: space + `"hello"` + space,
wantVal: V("hello"),
wantOut: `"hello"`,
wantOutIndent: `"hello"`,
}, {
in: space + `[]` + space,
wantVal: V(Arr{}),
wantOut: `[]`,
wantOutIndent: `[]`,
}, {
in: space + `{}` + space,
wantVal: V(Obj{}),
wantOut: `{}`,
wantOutIndent: `{}`,
}, {
in: `null#invalid`,
wantErr: `8 bytes of unconsumed input`,
}, {
in: `0#invalid`,
wantErr: `8 bytes of unconsumed input`,
}, {
in: `"hello"#invalid`,
wantErr: `8 bytes of unconsumed input`,
}, {
in: `[]#invalid`,
wantErr: `8 bytes of unconsumed input`,
}, {
in: `{}#invalid`,
wantErr: `8 bytes of unconsumed input`,
}, {
in: `[truee,true]`,
wantErr: `invalid "truee" as literal`,
}, {
in: `[falsee,false]`,
wantErr: `invalid "falsee" as literal`,
}, {
in: `[`,
wantErr: `unexpected EOF`,
}, {
in: `[{}]`,
wantVal: V(Arr{V(Obj{})}),
wantOut: "[{}]",
wantOutIndent: "[\n\t{}\n]",
}, {
in: `[{]}`,
wantErr: `invalid character ']' at start of string`,
}, {
in: `[,]`,
wantErr: `invalid "," as value`,
}, {
in: `{,}`,
wantErr: `invalid character ',' at start of string`,
}, {
in: `{"key""val"}`,
wantErr: `invalid character '"', expected ':' in object`,
}, {
in: `["elem0""elem1"]`,
wantErr: `invalid character '"', expected ']' at end of array`,
}, {
in: `{"hello"`,
wantErr: `unexpected EOF`,
}, {
in: `{"hello"}`,
wantErr: `invalid character '}', expected ':' in object`,
}, {
in: `{"hello":`,
wantErr: `unexpected EOF`,
}, {
in: `{"hello":}`,
wantErr: `invalid "}" as value`,
}, {
in: `{"hello":"goodbye"`,
wantErr: `unexpected EOF`,
}, {
in: `{"hello":"goodbye"]`,
wantErr: `invalid character ']', expected '}' at end of object`,
}, {
in: `{"hello":"goodbye"}`,
wantVal: V(Obj{{V("hello"), V("goodbye")}}),
wantOut: `{"hello":"goodbye"}`,
wantOutIndent: "{\n\t\"hello\": \"goodbye\"\n}",
}, {
in: `{"hello":"goodbye",}`,
wantErr: `invalid character '}' at start of string`,
}, {
in: `{"k":"v1","k":"v2"}`,
wantVal: V(Obj{
{V("k"), V("v1")}, {V("k"), V("v2")},
}),
wantOut: `{"k":"v1","k":"v2"}`,
wantOutIndent: "{\n\t\"k\": \"v1\",\n\t\"k\": \"v2\"\n}",
}, {
in: `{"k":{"k":{"k":"v"}}}`,
wantVal: V(Obj{
{V("k"), V(Obj{
{V("k"), V(Obj{
{V("k"), V("v")},
})},
})},
}),
wantOut: `{"k":{"k":{"k":"v"}}}`,
wantOutIndent: "{\n\t\"k\": {\n\t\t\"k\": {\n\t\t\t\"k\": \"v\"\n\t\t}\n\t}\n}",
}, {
in: `{"k":{"k":{"k":"v1","k":"v2"}}}`,
wantVal: V(Obj{
{V("k"), V(Obj{
{V("k"), V(Obj{
{V("k"), V("v1")},
{V("k"), V("v2")},
})},
})},
}),
wantOut: `{"k":{"k":{"k":"v1","k":"v2"}}}`,
wantOutIndent: "{\n\t\"k\": {\n\t\t\"k\": {\n\t\t\t\"k\": \"v1\",\n\t\t\t\"k\": \"v2\"\n\t\t}\n\t}\n}",
}, {
in: " x",
wantErr: `syntax error (line 1:3)`,
}, {
in: `["💩"x`,
wantErr: `syntax error (line 1:5)`,
}, {
in: "\n\n[\"🔥🔥🔥\"x",
wantErr: `syntax error (line 3:7)`,
}, {
in: `["👍🏻👍🏿"x`,
wantErr: `syntax error (line 1:8)`, // multi-rune emojis; could be column:6
}, {
in: "\"\x00\"",
wantErr: `invalid character '\x00' in string`,
}, {
in: "\"\xff\"",
wantErr: `invalid UTF-8 detected`,
wantVal: V(string("\xff")),
}, {
in: `"` + string(utf8.RuneError) + `"`,
wantVal: V(string(utf8.RuneError)),
wantOut: `"` + string(utf8.RuneError) + `"`,
}, {
in: `"\uFFFD"`,
wantVal: V(string(utf8.RuneError)),
wantOut: `"` + string(utf8.RuneError) + `"`,
}, {
in: `"\x"`,
wantErr: `invalid escape code "\\x" in string`,
}, {
in: `"\uXXXX"`,
wantErr: `invalid escape code "\\uXXXX" in string`,
}, {
in: `"\uDEAD"`, // unmatched surrogate pair
wantErr: `unexpected EOF`,
}, {
in: `"\uDEAD\uBEEF"`, // invalid surrogate half
wantErr: `invalid escape code "\\uBEEF" in string`,
}, {
in: `"\uD800\udead"`, // valid surrogate pair
wantVal: V("𐊭"),
wantOut: `"𐊭"`,
}, {
in: `"\u0000\"\\\/\b\f\n\r\t"`,
wantVal: V("\u0000\"\\/\b\f\n\r\t"),
wantOut: `"\u0000\"\\/\b\f\n\r\t"`,
}, {
in: `-`,
wantErr: `invalid "-" as number`,
}, {
in: `-0`,
wantVal: V(math.Copysign(0, -1)),
wantOut: `-0`,
}, {
in: `+0`,
wantErr: `invalid "+0" as value`,
}, {
in: `-+`,
wantErr: `invalid "-+" as number`,
}, {
in: `0.`,
wantErr: `invalid "0." as number`,
}, {
in: `.1`,
wantErr: `invalid ".1" as value`,
}, {
in: `0.e1`,
wantErr: `invalid "0.e1" as number`,
}, {
in: `0.0`,
wantVal: V(0.0),
wantOut: "0",
}, {
in: `01`,
wantErr: `invalid "01" as number`,
}, {
in: `0e`,
wantErr: `invalid "0e" as number`,
}, {
in: `0e0`,
wantVal: V(0.0),
wantOut: "0",
}, {
in: `0E0`,
wantVal: V(0.0),
wantOut: "0",
}, {
in: `0Ee`,
wantErr: `invalid "0Ee" as number`,
}, {
in: `-1.0E+1`,
wantVal: V(-10.0),
wantOut: "-10",
}, {
in: `
{
"firstName" : "John",
"lastName" : "Smith" ,
"isAlive" : true,
"age" : 27,
"address" : {
"streetAddress" : "21 2nd Street" ,
"city" : "New York" ,
"state" : "NY" ,
"postalCode" : "10021-3100"
},
"phoneNumbers" : [
{
"type" : "home" ,
"number" : "212 555-1234"
} ,
{
"type" : "office" ,
"number" : "646 555-4567"
} ,
{
"type" : "mobile" ,
"number" : "123 456-7890"
}
],
"children" : [] ,
"spouse" : null
}
`,
wantVal: V(Obj{
{V("firstName"), V("John")},
{V("lastName"), V("Smith")},
{V("isAlive"), V(true)},
{V("age"), V(27.0)},
{V("address"), V(Obj{
{V("streetAddress"), V("21 2nd Street")},
{V("city"), V("New York")},
{V("state"), V("NY")},
{V("postalCode"), V("10021-3100")},
})},
{V("phoneNumbers"), V(Arr{
V(Obj{
{V("type"), V("home")},
{V("number"), V("212 555-1234")},
}),
V(Obj{
{V("type"), V("office")},
{V("number"), V("646 555-4567")},
}),
V(Obj{
{V("type"), V("mobile")},
{V("number"), V("123 456-7890")},
}),
})},
{V("children"), V(Arr{})},
{V("spouse"), V(nil)},
}),
wantOut: `{"firstName":"John","lastName":"Smith","isAlive":true,"age":27,"address":{"streetAddress":"21 2nd Street","city":"New York","state":"NY","postalCode":"10021-3100"},"phoneNumbers":[{"type":"home","number":"212 555-1234"},{"type":"office","number":"646 555-4567"},{"type":"mobile","number":"123 456-7890"}],"children":[],"spouse":null}`,
wantOutIndent: `{
"firstName": "John",
"lastName": "Smith",
"isAlive": true,
"age": 27,
"address": {
"streetAddress": "21 2nd Street",
"city": "New York",
"state": "NY",
"postalCode": "10021-3100"
},
"phoneNumbers": [
{
"type": "home",
"number": "212 555-1234"
},
{
"type": "office",
"number": "646 555-4567"
},
{
"type": "mobile",
"number": "123 456-7890"
}
],
"children": [],
"spouse": null
}`,
}}
opts := cmp.Options{
cmpopts.EquateEmpty(),
cmp.Transformer("", func(v Value) interface{} {
switch v.typ {
case 0:
return nil // special case so Value{} == Value{}
case Null:
return nil
case Bool:
return v.Bool()
case Number:
return v.Number()
case String:
return v.String()
case Array:
return v.Array()
case Object:
return v.Object()
default:
panic("invalid type")
}
}),
}
for _, tt := range tests {
t.Run("", func(t *testing.T) {
if tt.in != "" || tt.wantVal.Type() != 0 || tt.wantErr != "" {
gotVal, err := Unmarshal([]byte(tt.in))
if err == nil {
if tt.wantErr != "" {
t.Errorf("Unmarshal(): got nil error, want %v", tt.wantErr)
}
} else {
if tt.wantErr == "" {
t.Errorf("Unmarshal(): got %v, want nil error", err)
} else if !strings.Contains(err.Error(), tt.wantErr) {
t.Errorf("Unmarshal(): got %v, want %v", err, tt.wantErr)
}
}
if diff := cmp.Diff(gotVal, tt.wantVal, opts); diff != "" {
t.Errorf("Unmarshal(): output mismatch (-got +want):\n%s", diff)
}
}
if tt.wantOut != "" {
gotOut, err := Marshal(tt.wantVal, "")
if err != nil {
t.Errorf("Marshal(): got %v, want nil error", err)
}
if string(gotOut) != tt.wantOut {
t.Errorf("Marshal():\ngot: %s\nwant: %s", gotOut, tt.wantOut)
}
}
if tt.wantOutIndent != "" {
gotOut, err := Marshal(tt.wantVal, "\t")
if err != nil {
t.Errorf("Marshal(Indent): got %v, want nil error", err)
}
if string(gotOut) != tt.wantOutIndent {
t.Errorf("Marshal(Indent):\ngot: %s\nwant: %s", gotOut, tt.wantOutIndent)
}
}
})
}
}