mirror of https://github.com/gofiber/fiber.git
1627 lines
36 KiB
Go
1627 lines
36 KiB
Go
package json
|
||
|
||
import (
|
||
"bytes"
|
||
"compress/gzip"
|
||
"encoding"
|
||
"encoding/json"
|
||
"errors"
|
||
"flag"
|
||
"fmt"
|
||
"io"
|
||
"io/ioutil"
|
||
"math"
|
||
"os"
|
||
"path/filepath"
|
||
"reflect"
|
||
"runtime"
|
||
"strconv"
|
||
"strings"
|
||
"testing"
|
||
"time"
|
||
)
|
||
|
||
// The encoding/json package does not export the msg field of json.SyntaxError,
|
||
// so we use this replacement type in tests.
|
||
type testSyntaxError struct {
|
||
msg string
|
||
Offset int64
|
||
}
|
||
|
||
func (e *testSyntaxError) Error() string { return e.msg }
|
||
|
||
var (
|
||
marshal func([]byte, interface{}) ([]byte, error)
|
||
unmarshal func([]byte, interface{}) error
|
||
escapeHTML bool
|
||
)
|
||
|
||
func TestMain(m *testing.M) {
|
||
var pkg string
|
||
flag.StringVar(&pkg, "package", ".", "The name of the package to test (encoding/json, or default to this package)")
|
||
flag.BoolVar(&escapeHTML, "escapehtml", false, "Whether to enable HTML escaping or not")
|
||
flag.Parse()
|
||
|
||
switch pkg {
|
||
case "encoding/json":
|
||
buf := &buffer{}
|
||
enc := json.NewEncoder(buf)
|
||
enc.SetEscapeHTML(escapeHTML)
|
||
|
||
marshal = func(b []byte, v interface{}) ([]byte, error) {
|
||
buf.data = b
|
||
err := enc.Encode(v)
|
||
return buf.data, err
|
||
}
|
||
|
||
unmarshal = json.Unmarshal
|
||
|
||
default:
|
||
flags := AppendFlags(0)
|
||
if escapeHTML {
|
||
flags |= EscapeHTML
|
||
}
|
||
|
||
marshal = func(b []byte, v interface{}) ([]byte, error) {
|
||
return Append(b, v, flags)
|
||
}
|
||
|
||
unmarshal = func(b []byte, v interface{}) error {
|
||
_, err := Parse(b, v, ZeroCopy)
|
||
return err
|
||
}
|
||
}
|
||
|
||
os.Exit(m.Run())
|
||
}
|
||
|
||
type point struct {
|
||
X int `json:"x"`
|
||
Y int `json:"y"`
|
||
}
|
||
|
||
type tree struct {
|
||
Value string
|
||
Left *tree
|
||
Right *tree
|
||
}
|
||
|
||
var testValues = [...]interface{}{
|
||
// constants
|
||
nil,
|
||
false,
|
||
true,
|
||
|
||
// int
|
||
int(0),
|
||
int(1),
|
||
int(42),
|
||
int(-1),
|
||
int(-42),
|
||
int8(math.MaxInt8),
|
||
int8(math.MinInt8),
|
||
int16(math.MaxInt16),
|
||
int16(math.MinInt16),
|
||
int32(math.MaxInt32),
|
||
int32(math.MinInt32),
|
||
int64(math.MaxInt64),
|
||
int64(math.MinInt64),
|
||
|
||
// uint
|
||
uint(0),
|
||
uint(1),
|
||
uintptr(0),
|
||
uintptr(1),
|
||
uint8(math.MaxUint8),
|
||
uint16(math.MaxUint16),
|
||
uint32(math.MaxUint32),
|
||
uint64(math.MaxUint64),
|
||
|
||
// float
|
||
float32(0),
|
||
float32(0.5),
|
||
float32(math.SmallestNonzeroFloat32),
|
||
float32(math.MaxFloat32),
|
||
float64(0),
|
||
float64(0.5),
|
||
float64(math.SmallestNonzeroFloat64),
|
||
float64(math.MaxFloat64),
|
||
|
||
// number
|
||
Number("0"),
|
||
Number("1234567890"),
|
||
Number("-0.5"),
|
||
Number("-1e+2"),
|
||
|
||
// string
|
||
"",
|
||
"Hello World!",
|
||
"Hello\"World!",
|
||
"Hello\\World!",
|
||
"Hello\nWorld!",
|
||
"Hello\rWorld!",
|
||
"Hello\tWorld!",
|
||
"Hello\bWorld!",
|
||
"Hello\fWorld!",
|
||
"你好",
|
||
"<",
|
||
">",
|
||
"&",
|
||
"\u001944",
|
||
"\u00c2e>",
|
||
"\u00c2V?",
|
||
"\u000e=8",
|
||
"\u001944\u00c2e>\u00c2V?\u000e=8",
|
||
"ir\u001bQJ\u007f\u0007y\u0015)",
|
||
strings.Repeat("A", 32),
|
||
strings.Repeat("A", 250),
|
||
strings.Repeat("A", 1020),
|
||
|
||
// bytes
|
||
[]byte(""),
|
||
[]byte("Hello World!"),
|
||
bytes.Repeat([]byte("A"), 250),
|
||
bytes.Repeat([]byte("A"), 1020),
|
||
|
||
// time
|
||
time.Unix(0, 0).In(time.UTC),
|
||
time.Unix(1, 42).In(time.UTC),
|
||
time.Unix(17179869184, 999999999).In(time.UTC),
|
||
time.Date(2016, 12, 20, 0, 20, 1, 0, time.UTC),
|
||
|
||
// array
|
||
[...]int{},
|
||
[...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
|
||
|
||
// slice
|
||
[]int{},
|
||
[]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
|
||
makeSlice(250),
|
||
makeSlice(1020),
|
||
[]string{"A", "B", "C"},
|
||
[]interface{}{nil, true, false, 0.5, "Hello World!"},
|
||
|
||
// map
|
||
makeMapStringBool(0),
|
||
makeMapStringBool(15),
|
||
makeMapStringBool(1020),
|
||
makeMapStringInterface(0),
|
||
makeMapStringInterface(15),
|
||
makeMapStringInterface(1020),
|
||
map[int]bool{1: false, 42: true},
|
||
map[textValue]bool{{1, 2}: true, {3, 4}: false},
|
||
map[string]*point{
|
||
"A": {1, 2},
|
||
"B": {3, 4},
|
||
"C": {5, 6},
|
||
},
|
||
map[string]RawMessage{
|
||
"A": RawMessage(`{}`),
|
||
"B": RawMessage(`null`),
|
||
"C": RawMessage(`42`),
|
||
},
|
||
|
||
// struct
|
||
struct{}{},
|
||
struct{ A int }{42},
|
||
struct{ A, B, C int }{1, 2, 3},
|
||
struct {
|
||
A int
|
||
T time.Time
|
||
S string
|
||
}{42, time.Date(2016, 12, 20, 0, 20, 1, 0, time.UTC), "Hello World!"},
|
||
// These types are interesting because they fit in a pointer so the compiler
|
||
// puts their value directly into the pointer field of the interface{} that
|
||
// is passed to Marshal.
|
||
struct{ X *int }{},
|
||
struct{ X *int }{new(int)},
|
||
struct{ X **int }{},
|
||
// Struct types with more than one pointer, those exercise the regular
|
||
// pointer handling with code that dereferences the fields.
|
||
struct{ X, Y *int }{},
|
||
struct{ X, Y *int }{new(int), new(int)},
|
||
struct {
|
||
A string `json:"name"`
|
||
B string `json:"-"`
|
||
C string `json:",omitempty"`
|
||
D map[string]interface{} `json:",string"`
|
||
e string
|
||
}{A: "Luke", D: map[string]interface{}{"answer": float64(42)}},
|
||
struct{ point }{point{1, 2}},
|
||
tree{
|
||
Value: "T",
|
||
Left: &tree{Value: "L"},
|
||
Right: &tree{Value: "R", Left: &tree{Value: "R-L"}},
|
||
},
|
||
|
||
// pointer
|
||
(*string)(nil),
|
||
new(int),
|
||
|
||
// Marshaler/Unmarshaler
|
||
jsonValue{},
|
||
jsonValue{1, 2},
|
||
|
||
// encoding.TextMarshaler/encoding.TextUnmarshaler
|
||
textValue{},
|
||
textValue{1, 2},
|
||
|
||
// RawMessage
|
||
RawMessage(`{
|
||
"answer": 42,
|
||
"hello": "world"
|
||
}`),
|
||
|
||
// fixtures
|
||
loadTestdata(filepath.Join(runtime.GOROOT(), "src/encoding/json/testdata/code.json.gz")),
|
||
}
|
||
|
||
var durationTestValues = []interface{}{
|
||
// duration
|
||
time.Nanosecond,
|
||
time.Microsecond,
|
||
time.Millisecond,
|
||
time.Second,
|
||
time.Minute,
|
||
time.Hour,
|
||
|
||
// struct with duration
|
||
struct{ D1, D2 time.Duration }{time.Millisecond, time.Hour},
|
||
}
|
||
|
||
func makeSlice(n int) []int {
|
||
s := make([]int, n)
|
||
for i := range s {
|
||
s[i] = i
|
||
}
|
||
return s
|
||
}
|
||
|
||
func makeMapStringBool(n int) map[string]bool {
|
||
m := make(map[string]bool, n)
|
||
for i := 0; i != n; i++ {
|
||
m[strconv.Itoa(i)] = true
|
||
}
|
||
return m
|
||
}
|
||
|
||
func makeMapStringInterface(n int) map[string]interface{} {
|
||
m := make(map[string]interface{}, n)
|
||
for i := 0; i != n; i++ {
|
||
m[strconv.Itoa(i)] = nil
|
||
}
|
||
return m
|
||
}
|
||
|
||
func testName(v interface{}) string {
|
||
return fmt.Sprintf("%T", v)
|
||
}
|
||
|
||
type codeResponse2 struct {
|
||
Tree *codeNode2 `json:"tree"`
|
||
Username string `json:"username"`
|
||
}
|
||
|
||
type codeNode2 struct {
|
||
Name string `json:"name"`
|
||
Kids []*codeNode `json:"kids"`
|
||
CLWeight float64 `json:"cl_weight"`
|
||
Touches int `json:"touches"`
|
||
MinT int64 `json:"min_t"`
|
||
MaxT int64 `json:"max_t"`
|
||
MeanT int64 `json:"mean_t"`
|
||
}
|
||
|
||
func loadTestdata(path string) interface{} {
|
||
f, err := os.Open(path)
|
||
if err != nil {
|
||
return err.Error()
|
||
}
|
||
defer f.Close()
|
||
|
||
r, err := gzip.NewReader(f)
|
||
if err != nil {
|
||
return err.Error()
|
||
}
|
||
defer r.Close()
|
||
|
||
testdata := new(codeResponse2)
|
||
if err := json.NewDecoder(r).Decode(testdata); err != nil {
|
||
return err.Error()
|
||
}
|
||
return testdata
|
||
}
|
||
|
||
func TestCodec(t *testing.T) {
|
||
for _, v1 := range testValues {
|
||
t.Run(testName(v1), func(t *testing.T) {
|
||
v2 := newValue(v1)
|
||
|
||
a, err := json.MarshalIndent(v1, "", "\t")
|
||
if err != nil {
|
||
t.Error(err)
|
||
return
|
||
}
|
||
a = append(a, '\n')
|
||
|
||
buf := &bytes.Buffer{}
|
||
enc := NewEncoder(buf)
|
||
enc.SetIndent("", "\t")
|
||
|
||
if err := enc.Encode(v1); err != nil {
|
||
t.Error(err)
|
||
return
|
||
}
|
||
b := buf.Bytes()
|
||
|
||
if !Valid(b) {
|
||
t.Error("invalid JSON representation")
|
||
}
|
||
|
||
if !bytes.Equal(a, b) {
|
||
t.Error("JSON representations mismatch")
|
||
t.Log("expected:", string(a))
|
||
t.Log("found: ", string(b))
|
||
}
|
||
|
||
dec := NewDecoder(bytes.NewBuffer(b))
|
||
|
||
if err := dec.Decode(v2.Interface()); err != nil {
|
||
t.Errorf("%T: %v", err, err)
|
||
return
|
||
}
|
||
|
||
x1 := v1
|
||
x2 := v2.Elem().Interface()
|
||
|
||
if !reflect.DeepEqual(x1, x2) {
|
||
t.Error("values mismatch")
|
||
t.Logf("expected: %#v", x1)
|
||
t.Logf("found: %#v", x2)
|
||
}
|
||
|
||
if b, err := ioutil.ReadAll(dec.Buffered()); err != nil {
|
||
t.Error(err)
|
||
} else if len(b) != 0 {
|
||
t.Errorf("leftover trailing bytes in the decoder: %q", b)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// TestCodecDuration isolates testing of time.Duration. The stdlib un/marshals
|
||
// this type as integers whereas this library un/marshals formatted string
|
||
// values. Therefore, plugging durations into TestCodec would cause fail since
|
||
// it checks equality on the marshaled strings from the two libraries.
|
||
func TestCodecDuration(t *testing.T) {
|
||
for _, v1 := range durationTestValues {
|
||
t.Run(testName(v1), func(t *testing.T) {
|
||
v2 := newValue(v1)
|
||
|
||
// encode using stdlib. (will be an int)
|
||
std, err := json.MarshalIndent(v1, "", "\t")
|
||
if err != nil {
|
||
t.Error(err)
|
||
return
|
||
}
|
||
std = append(std, '\n')
|
||
|
||
// decode using our decoder. (reads int to duration)
|
||
dec := NewDecoder(bytes.NewBuffer([]byte(std)))
|
||
|
||
if err := dec.Decode(v2.Interface()); err != nil {
|
||
t.Errorf("%T: %v", err, err)
|
||
return
|
||
}
|
||
|
||
x1 := v1
|
||
x2 := v2.Elem().Interface()
|
||
|
||
if !reflect.DeepEqual(x1, x2) {
|
||
t.Error("values mismatch")
|
||
t.Logf("expected: %#v", x1)
|
||
t.Logf("found: %#v", x2)
|
||
}
|
||
|
||
// encoding using our encoder. (writes duration as string)
|
||
buf := &bytes.Buffer{}
|
||
enc := NewEncoder(buf)
|
||
enc.SetIndent("", "\t")
|
||
|
||
if err := enc.Encode(v1); err != nil {
|
||
t.Error(err)
|
||
return
|
||
}
|
||
b := buf.Bytes()
|
||
|
||
if !Valid(b) {
|
||
t.Error("invalid JSON representation")
|
||
}
|
||
|
||
if reflect.DeepEqual(std, b) {
|
||
t.Error("encoded durations should not match stdlib")
|
||
t.Logf("got: %s", b)
|
||
}
|
||
|
||
// decode using our decoder. (reads string to duration)
|
||
dec = NewDecoder(bytes.NewBuffer([]byte(std)))
|
||
|
||
if err := dec.Decode(v2.Interface()); err != nil {
|
||
t.Errorf("%T: %v", err, err)
|
||
return
|
||
}
|
||
|
||
x1 = v1
|
||
x2 = v2.Elem().Interface()
|
||
|
||
if !reflect.DeepEqual(x1, x2) {
|
||
t.Error("values mismatch")
|
||
t.Logf("expected: %#v", x1)
|
||
t.Logf("found: %#v", x2)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func newValue(model interface{}) reflect.Value {
|
||
if model == nil {
|
||
return reflect.New(reflect.TypeOf(&model).Elem())
|
||
}
|
||
return reflect.New(reflect.TypeOf(model))
|
||
}
|
||
|
||
func BenchmarkMarshal(b *testing.B) {
|
||
j := make([]byte, 0, 128*1024)
|
||
|
||
for _, v := range testValues {
|
||
b.Run(testName(v), func(b *testing.B) {
|
||
if marshal == nil {
|
||
return
|
||
}
|
||
|
||
for i := 0; i != b.N; i++ {
|
||
j, _ = marshal(j[:0], v)
|
||
}
|
||
|
||
b.SetBytes(int64(len(j)))
|
||
})
|
||
}
|
||
}
|
||
|
||
func BenchmarkUnmarshal(b *testing.B) {
|
||
for _, v := range testValues {
|
||
b.Run(testName(v), func(b *testing.B) {
|
||
if unmarshal == nil {
|
||
return
|
||
}
|
||
|
||
x := v
|
||
if d, ok := x.(time.Duration); ok {
|
||
x = duration(d)
|
||
}
|
||
|
||
j, _ := json.Marshal(x)
|
||
x = newValue(v).Interface()
|
||
|
||
for i := 0; i != b.N; i++ {
|
||
unmarshal(j, x)
|
||
}
|
||
|
||
b.SetBytes(int64(len(j)))
|
||
})
|
||
}
|
||
}
|
||
|
||
type buffer struct{ data []byte }
|
||
|
||
func (buf *buffer) Write(b []byte) (int, error) {
|
||
buf.data = append(buf.data, b...)
|
||
return len(b), nil
|
||
}
|
||
|
||
func (buf *buffer) WriteString(s string) (int, error) {
|
||
buf.data = append(buf.data, s...)
|
||
return len(s), nil
|
||
}
|
||
|
||
type jsonValue struct {
|
||
x int32
|
||
y int32
|
||
}
|
||
|
||
func (v jsonValue) MarshalJSON() ([]byte, error) {
|
||
return Marshal([2]int32{v.x, v.y})
|
||
}
|
||
|
||
func (v *jsonValue) UnmarshalJSON(b []byte) error {
|
||
var a [2]int32
|
||
err := Unmarshal(b, &a)
|
||
v.x = a[0]
|
||
v.y = a[1]
|
||
return err
|
||
}
|
||
|
||
type textValue struct {
|
||
x int32
|
||
y int32
|
||
}
|
||
|
||
func (v textValue) MarshalText() ([]byte, error) {
|
||
return []byte(fmt.Sprintf("(%d,%d)", v.x, v.y)), nil
|
||
}
|
||
|
||
func (v *textValue) UnmarshalText(b []byte) error {
|
||
_, err := fmt.Sscanf(string(b), "(%d,%d)", &v.x, &v.y)
|
||
return err
|
||
}
|
||
|
||
type duration time.Duration
|
||
|
||
func (d duration) MarshalJSON() ([]byte, error) {
|
||
return []byte(`"` + time.Duration(d).String() + `"`), nil
|
||
}
|
||
|
||
func (d *duration) UnmarshalJSON(b []byte) error {
|
||
var s string
|
||
if err := json.Unmarshal(b, &s); err != nil {
|
||
return err
|
||
}
|
||
x, err := time.ParseDuration(s)
|
||
*d = duration(x)
|
||
return err
|
||
}
|
||
|
||
var (
|
||
_ Marshaler = jsonValue{}
|
||
_ Marshaler = duration(0)
|
||
|
||
_ encoding.TextMarshaler = textValue{}
|
||
|
||
_ Unmarshaler = (*jsonValue)(nil)
|
||
_ Unmarshaler = (*duration)(nil)
|
||
|
||
_ encoding.TextUnmarshaler = (*textValue)(nil)
|
||
)
|
||
|
||
func TestDecodeStructFieldCaseInsensitive(t *testing.T) {
|
||
b := []byte(`{ "type": "changed" }`)
|
||
s := struct {
|
||
Type string
|
||
}{"unchanged"}
|
||
|
||
if err := Unmarshal(b, &s); err != nil {
|
||
t.Error(err)
|
||
}
|
||
|
||
if s.Type != "changed" {
|
||
t.Error("s.Type: expected to be changed but found", s.Type)
|
||
}
|
||
}
|
||
|
||
func TestDecodeLines(t *testing.T) {
|
||
tests := []struct {
|
||
desc string
|
||
reader io.Reader
|
||
expectCount int
|
||
}{
|
||
|
||
// simple
|
||
|
||
{
|
||
desc: "bare object",
|
||
reader: strings.NewReader("{\"Good\":true}"),
|
||
expectCount: 1,
|
||
},
|
||
{
|
||
desc: "multiple objects on one line",
|
||
reader: strings.NewReader("{\"Good\":true}{\"Good\":true}\n"),
|
||
expectCount: 2,
|
||
},
|
||
{
|
||
desc: "object spanning multiple lines",
|
||
reader: strings.NewReader("{\n\"Good\":true\n}\n"),
|
||
expectCount: 1,
|
||
},
|
||
|
||
// whitespace handling
|
||
|
||
{
|
||
desc: "trailing newline",
|
||
reader: strings.NewReader("{\"Good\":true}\n{\"Good\":true}\n"),
|
||
expectCount: 2,
|
||
},
|
||
{
|
||
desc: "multiple trailing newlines",
|
||
reader: strings.NewReader("{\"Good\":true}\n{\"Good\":true}\n\n"),
|
||
expectCount: 2,
|
||
},
|
||
{
|
||
desc: "blank lines",
|
||
reader: strings.NewReader("{\"Good\":true}\n\n{\"Good\":true}"),
|
||
expectCount: 2,
|
||
},
|
||
{
|
||
desc: "no trailing newline",
|
||
reader: strings.NewReader("{\"Good\":true}\n{\"Good\":true}"),
|
||
expectCount: 2,
|
||
},
|
||
{
|
||
desc: "leading whitespace",
|
||
reader: strings.NewReader(" {\"Good\":true}\n\t{\"Good\":true}"),
|
||
expectCount: 2,
|
||
},
|
||
|
||
// multiple reads
|
||
|
||
{
|
||
desc: "one object, multiple reads",
|
||
reader: io.MultiReader(
|
||
strings.NewReader("{"),
|
||
strings.NewReader("\"Good\": true"),
|
||
strings.NewReader("}\n"),
|
||
),
|
||
expectCount: 1,
|
||
},
|
||
|
||
// EOF reads
|
||
|
||
{
|
||
desc: "one object + EOF",
|
||
reader: &eofReader{"{\"Good\":true}\n"},
|
||
expectCount: 1,
|
||
},
|
||
{
|
||
desc: "leading whitespace + EOF",
|
||
reader: &eofReader{"\n{\"Good\":true}\n"},
|
||
expectCount: 1,
|
||
},
|
||
{
|
||
desc: "multiple objects + EOF",
|
||
reader: &eofReader{"{\"Good\":true}\n{\"Good\":true}\n"},
|
||
expectCount: 2,
|
||
},
|
||
{
|
||
desc: "one object + multiple reads + EOF",
|
||
reader: io.MultiReader(
|
||
strings.NewReader("{"),
|
||
strings.NewReader(" \"Good\": true"),
|
||
&eofReader{"}\n"},
|
||
),
|
||
expectCount: 1,
|
||
},
|
||
{
|
||
desc: "multiple objects + multiple reads + EOF",
|
||
reader: io.MultiReader(
|
||
strings.NewReader("{"),
|
||
strings.NewReader(" \"Good\": true}{\"Good\": true}"),
|
||
&eofReader{"\n"},
|
||
),
|
||
expectCount: 2,
|
||
},
|
||
|
||
{
|
||
// the 2nd object should be discarded, as 42 cannot be cast to bool
|
||
desc: "unmarshal error while decoding",
|
||
reader: strings.NewReader("{\"Good\":true}\n{\"Good\":42}\n{\"Good\":true}\n"),
|
||
expectCount: 2,
|
||
},
|
||
{
|
||
// the 2nd object should be discarded, as 42 cannot be cast to bool
|
||
desc: "unmarshal error while decoding last object",
|
||
reader: strings.NewReader("{\"Good\":true}\n{\"Good\":42}\n"),
|
||
expectCount: 1,
|
||
},
|
||
}
|
||
|
||
type obj struct {
|
||
Good bool
|
||
}
|
||
|
||
for _, test := range tests {
|
||
t.Run(test.desc, func(t *testing.T) {
|
||
d := NewDecoder(test.reader)
|
||
var count int
|
||
var err error
|
||
for {
|
||
var o obj
|
||
err = d.Decode(&o)
|
||
if err != nil {
|
||
if err == io.EOF {
|
||
break
|
||
}
|
||
|
||
switch err.(type) {
|
||
case *SyntaxError, *UnmarshalTypeError, *UnmarshalFieldError:
|
||
t.Log("unmarshal error", err)
|
||
continue
|
||
}
|
||
|
||
t.Error("decode error", err)
|
||
break
|
||
}
|
||
if !o.Good {
|
||
t.Errorf("object was not unmarshaled correctly: %#v", o)
|
||
}
|
||
count++
|
||
}
|
||
|
||
if err != nil && err != io.EOF {
|
||
t.Error(err)
|
||
}
|
||
|
||
if count != test.expectCount {
|
||
t.Errorf("expected %d objects, got %d", test.expectCount, count)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// eofReader is a simple io.Reader that reads its full contents _and_ returns
|
||
// and EOF in the first call. Subsequent Read calls only return EOF.
|
||
type eofReader struct {
|
||
s string
|
||
}
|
||
|
||
func (r *eofReader) Read(p []byte) (n int, err error) {
|
||
n = copy(p, r.s)
|
||
r.s = r.s[n:]
|
||
if r.s == "" {
|
||
err = io.EOF
|
||
}
|
||
return
|
||
}
|
||
|
||
func TestDontMatchCaseIncensitiveStructFields(t *testing.T) {
|
||
b := []byte(`{ "type": "changed" }`)
|
||
s := struct {
|
||
Type string
|
||
}{"unchanged"}
|
||
|
||
if _, err := Parse(b, &s, DontMatchCaseInsensitiveStructFields); err != nil {
|
||
t.Error(err)
|
||
}
|
||
|
||
if s.Type != "unchanged" {
|
||
t.Error("s.Type: expected to be unchanged but found", s.Type)
|
||
}
|
||
}
|
||
|
||
func TestMarshalFuzzBugs(t *testing.T) {
|
||
tests := []struct {
|
||
value interface{}
|
||
output string
|
||
}{
|
||
{ // html sequences are escaped even in RawMessage
|
||
value: struct {
|
||
P RawMessage
|
||
}{P: RawMessage(`"<"`)},
|
||
output: "{\"P\":\"\\u003c\"}",
|
||
},
|
||
{ // raw message output is compacted
|
||
value: struct {
|
||
P RawMessage
|
||
}{P: RawMessage(`{"" :{}}`)},
|
||
output: "{\"P\":{\"\":{}}}",
|
||
},
|
||
}
|
||
|
||
for _, test := range tests {
|
||
t.Run("", func(t *testing.T) {
|
||
b, err := Marshal(test.value)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
|
||
if string(b) != test.output {
|
||
t.Error("values mismatch")
|
||
t.Logf("expected: %#v", test.output)
|
||
t.Logf("found: %#v", string(b))
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestUnmarshalFuzzBugs(t *testing.T) {
|
||
tests := []struct {
|
||
input string
|
||
value interface{}
|
||
}{
|
||
{ // non-UTF8 sequences must be converted to the utf8.RuneError character.
|
||
input: "[\"00000\xef\"]",
|
||
value: []interface{}{"00000<30>"},
|
||
},
|
||
{ // UTF16 surrogate followed by null character
|
||
input: "[\"\\ud800\\u0000\"]",
|
||
value: []interface{}{"<22>\x00"},
|
||
},
|
||
{ // UTF16 surrogate followed by ascii character
|
||
input: "[\"\\uDF00\\u000e\"]",
|
||
value: []interface{}{"<22>\x0e"},
|
||
},
|
||
{ // UTF16 surrogate followed by unicode character
|
||
input: "[[\"\\uDF00\\u0800\"]]",
|
||
value: []interface{}{[]interface{}{"<22>ࠀ"}},
|
||
},
|
||
{ // invalid UTF16 surrogate sequenced followed by a valid UTF16 surrogate sequence
|
||
input: "[\"\\udf00\\udb00\\udf00\"]",
|
||
value: []interface{}{"<22>\U000d0300"},
|
||
},
|
||
{ // decode single-element slice into []byte field
|
||
input: "{\"f\":[0],\"0\":[0]}",
|
||
value: struct{ F []byte }{F: []byte{0}},
|
||
},
|
||
{ // decode multi-element slice into []byte field
|
||
input: "{\"F\":[3,1,1,1,9,9]}",
|
||
value: struct{ F []byte }{F: []byte{3, 1, 1, 1, 9, 9}},
|
||
},
|
||
{ // decode string with escape sequence into []byte field
|
||
input: "{\"F\":\"0p00\\r\"}",
|
||
value: struct{ F []byte }{F: []byte("ҝ4")},
|
||
},
|
||
{ // decode unicode code points which fold into ascii characters
|
||
input: "{\"ſ\":\"8\"}",
|
||
value: struct {
|
||
S int `json:",string"`
|
||
}{S: 8},
|
||
},
|
||
{ // decode unicode code points which don't fold into ascii characters
|
||
input: "{\"İ\":\"\"}",
|
||
value: struct{ I map[string]string }{I: nil},
|
||
},
|
||
{ // override pointer-to-pointer field clears the inner pointer only
|
||
input: "{\"o\":0,\"o\":null}",
|
||
value: struct{ O **int }{O: new(*int)},
|
||
},
|
||
{ // subsequent occurrences of a map field retain keys previously loaded
|
||
input: "{\"i\":{\"\":null},\"i\":{}}",
|
||
value: struct{ I map[string]string }{I: map[string]string{"": ""}},
|
||
},
|
||
{ // an empty string is an invalid JSON input
|
||
input: "",
|
||
},
|
||
{ // ASCII character below 0x20 are invalid JSON input
|
||
input: "[\"\b\"]",
|
||
},
|
||
{ // random byte before any value
|
||
input: "\xad",
|
||
},
|
||
{ // cloud be the beginning of a false value but not
|
||
input: "f",
|
||
value: false,
|
||
},
|
||
{ // random ASCII character
|
||
input: "}",
|
||
value: []interface{}{},
|
||
},
|
||
{ // random byte after valid JSON, decoded to a nil type
|
||
input: "0\x93",
|
||
},
|
||
{ // random byte after valid JSON, decoded to a int type
|
||
input: "0\x93",
|
||
value: 0,
|
||
},
|
||
{ // random byte after valid JSON, decoded to a slice type
|
||
input: "0\x93",
|
||
value: []interface{}{},
|
||
},
|
||
{ // decode integer into slice
|
||
input: "0",
|
||
value: []interface{}{},
|
||
},
|
||
{ // decode integer with trailing space into slice
|
||
input: "0\t",
|
||
value: []interface{}{},
|
||
},
|
||
{ // decode integer with leading random bytes into slice
|
||
input: "\b0",
|
||
value: []interface{}{},
|
||
},
|
||
{ // decode string into slice followed by number
|
||
input: "\"\"0",
|
||
value: []interface{}{},
|
||
},
|
||
{ // decode what looks like an object followed by a number into a string
|
||
input: "{0",
|
||
value: "",
|
||
},
|
||
{ // decode what looks like an object followed by a number into a map
|
||
input: "{0",
|
||
value: map[string]string{},
|
||
},
|
||
{ // decode string into string with trailing random byte
|
||
input: "\"\"\f",
|
||
value: "",
|
||
},
|
||
{ // decode weird number value into nil
|
||
input: "-00",
|
||
},
|
||
{ // decode an invalid escaped sequence
|
||
input: "\"\\0\"",
|
||
value: "",
|
||
},
|
||
{ // decode what looks like an array followed by a number into a slice
|
||
input: "[9E600",
|
||
value: []interface{}{},
|
||
},
|
||
{ // decode a number which is too large to fit in a float64
|
||
input: "[1e900]",
|
||
value: []interface{}{},
|
||
},
|
||
{ // many nested arrays openings
|
||
input: "[[[[[[",
|
||
value: []interface{}{},
|
||
},
|
||
{ // decode a map with value type mismatch and missing closing character
|
||
input: "{\"\":0",
|
||
value: map[string]string{},
|
||
},
|
||
{ // decode a struct with value type mismatch and missing closing character
|
||
input: "{\"E\":\"\"",
|
||
value: struct{ E uint8 }{},
|
||
},
|
||
{ // decode a map with value type mismatch
|
||
input: "{\"\":0}",
|
||
value: map[string]string{},
|
||
},
|
||
{ // decode number with exponent into integer field
|
||
input: "{\"e\":0e0}",
|
||
value: struct{ E uint8 }{},
|
||
},
|
||
{ // decode invalid integer representation into integer field
|
||
input: "{\"e\":00}",
|
||
value: struct{ E uint8 }{},
|
||
},
|
||
{ // decode unterminated array into byte slice
|
||
input: "{\"F\":[",
|
||
value: struct{ F []byte }{},
|
||
},
|
||
{ // attempt to decode string into in
|
||
input: "{\"S\":\"\"}",
|
||
value: struct {
|
||
S int `json:",string"`
|
||
}{},
|
||
},
|
||
{ // decode object with null key into map
|
||
input: "{null:0}",
|
||
value: map[string]interface{}{},
|
||
},
|
||
{ // decode unquoted integer into struct field with string tag
|
||
input: "{\"S\":0}",
|
||
value: struct {
|
||
S int `json:",string"`
|
||
}{},
|
||
},
|
||
{ // invalid base64 content when decoding string into byte slice
|
||
input: "{\"F\":\"0\"}",
|
||
value: struct{ F []byte }{},
|
||
},
|
||
{ // decode an object with a "null" string as key
|
||
input: "{\"null\":null}",
|
||
value: struct {
|
||
S int `json:",string"`
|
||
}{},
|
||
},
|
||
{ // decode an invalid floating point number representation into an integer field with string tag
|
||
input: "{\"s\":8e800}",
|
||
value: struct {
|
||
S int `json:",string"`
|
||
}{},
|
||
},
|
||
{ // decode a string with leading zeroes into an integer field with string tag
|
||
input: "{\"S\":\"00\"}",
|
||
value: struct {
|
||
S int `json:",string"`
|
||
}{},
|
||
},
|
||
{ // decode a string with invalid leading sign and zeroes into an integer field with string tag
|
||
input: "{\"S\":\"+00\"}",
|
||
value: struct {
|
||
S int `json:",string"`
|
||
}{},
|
||
},
|
||
{ // decode a string with valid leading sign and zeroes into an integer field with string tag
|
||
input: "{\"S\":\"-00\"}",
|
||
value: struct {
|
||
S int `json:",string"`
|
||
}{},
|
||
},
|
||
{ // decode non-ascii string into integer field with string tag
|
||
input: "{\"ſ\":\"\xbf\"}",
|
||
value: struct {
|
||
S int `json:",string"`
|
||
}{},
|
||
},
|
||
{ // decode a valid floating point number representation into an integer field with string tag
|
||
input: "{\"S\":0.0}",
|
||
value: struct {
|
||
S int `json:",string"`
|
||
}{},
|
||
},
|
||
{ // decode string with invalid leading sign to integer field with string tag
|
||
input: "{\"S\":\"+0\"}",
|
||
value: struct {
|
||
S int `json:",string"`
|
||
}{},
|
||
},
|
||
{ // decode string with valid leading sign to integer field with string tag
|
||
input: "{\"S\":\"-0\"}",
|
||
value: struct {
|
||
S int `json:",string"`
|
||
}{},
|
||
},
|
||
{ // decode string with object representation to integer field with string tag
|
||
input: "{\"s\":{}}",
|
||
value: struct {
|
||
S int `json:",string"`
|
||
}{},
|
||
},
|
||
{ // decoding integer with leading zeroes
|
||
input: "{\"o\":00}",
|
||
value: struct{ O **int }{},
|
||
},
|
||
{ // codeding string with invalid float representation into integer field with string tag
|
||
input: "{\"s\":\"0.\"}",
|
||
value: struct {
|
||
S int `json:",string"`
|
||
}{},
|
||
},
|
||
{ // malformed negative integer in object value
|
||
input: "{\"N\":-00}",
|
||
value: struct{ N *int }{},
|
||
},
|
||
{ // integer overflow
|
||
input: "{\"a\":9223372036854775808}",
|
||
value: struct {
|
||
A int `json:",omitempty"`
|
||
}{},
|
||
},
|
||
{ // decode string with number followed by random byte into integer field with string tag
|
||
input: "{\"s\":\"0]\"}",
|
||
value: struct {
|
||
S int `json:",string"`
|
||
}{},
|
||
},
|
||
{ // decode object into integer field
|
||
input: "{\"n\":{}}",
|
||
value: struct{ N *int }{},
|
||
},
|
||
{ // decode negative integer into unsigned type
|
||
input: "{\"E\":-0}",
|
||
value: struct{ E uint8 }{},
|
||
},
|
||
{ // decode string with number followed by random byte into integer field with string tag
|
||
input: "{\"s\":\"03<30>\"}",
|
||
value: struct {
|
||
S int `json:",string"`
|
||
}{},
|
||
},
|
||
{ // decode string with leading zeroes into integer field with string tag
|
||
input: "{\"s\":\"03\"}",
|
||
value: struct {
|
||
S int `json:",string"`
|
||
}{S: 3},
|
||
},
|
||
{ // decode string containing what looks like an object into integer field with string tag
|
||
input: "{\"S\":\"{}\"}",
|
||
value: struct {
|
||
S int `json:",string"`
|
||
}{},
|
||
},
|
||
{ // decode an empty string followed by the same field with a null value into a byte slice
|
||
input: "{\"F\":\"\",\"F\":null}",
|
||
value: struct{ F []byte }{},
|
||
},
|
||
{ // decode string containing a float into an integer field with string tag
|
||
input: "{\"S\":\"0e0\"}",
|
||
value: struct {
|
||
S int `json:",string"`
|
||
}{},
|
||
},
|
||
{ // decode string with negative sign into a an integer field with string tag
|
||
input: "{\"s\":\"-\"}",
|
||
value: struct {
|
||
S int `json:",string"`
|
||
}{},
|
||
},
|
||
{ // decode string with positive sign into a an integer field with string tag
|
||
input: "{\"s\":\"+\"}",
|
||
value: struct {
|
||
S int `json:",string"`
|
||
}{},
|
||
},
|
||
{ // decode an integer into a json unmarshaler
|
||
input: "{\"q\":0}",
|
||
value: struct {
|
||
Q testMarshaller
|
||
}{},
|
||
},
|
||
// This test fails because it appears that the encoding/json package
|
||
// will decode "q" before "s", so it returns an error about "q" being of
|
||
// the wrong type while this package will prase object keys in the order
|
||
// that they appear in the JSON input, so it detects the error from "s"
|
||
// first.
|
||
//
|
||
//{
|
||
// input: "{\"s\":0,\"q\":0}",
|
||
// value: struct {
|
||
// Q testMarshaller
|
||
// S int `json:",string"`
|
||
// }{},
|
||
//},
|
||
}
|
||
|
||
for _, test := range tests {
|
||
t.Run("", func(t *testing.T) {
|
||
var ptr1 interface{}
|
||
var ptr2 interface{}
|
||
|
||
if test.value != nil {
|
||
ptr1 = reflect.New(reflect.TypeOf(test.value)).Interface()
|
||
ptr2 = reflect.New(reflect.TypeOf(test.value)).Interface()
|
||
}
|
||
|
||
err1 := json.Unmarshal([]byte(test.input), ptr1)
|
||
err2 := Unmarshal([]byte(test.input), ptr2)
|
||
|
||
if reflect.TypeOf(err1) != reflect.TypeOf(err2) {
|
||
t.Error("errors mismatch")
|
||
t.Logf("expected: %T: %v", err1, err1)
|
||
t.Logf("found: %T: %v", err2, err2)
|
||
} else if err1 == nil && test.value != nil {
|
||
if value := reflect.ValueOf(ptr2).Elem().Interface(); !reflect.DeepEqual(test.value, value) {
|
||
t.Error("values mismatch")
|
||
t.Logf("expected: %#v", test.value)
|
||
t.Logf("found: %#v", value)
|
||
}
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func BenchmarkEasyjsonUnmarshalSmallStruct(b *testing.B) {
|
||
type Hashtag struct {
|
||
Indices []int `json:"indices"`
|
||
Text string `json:"text"`
|
||
}
|
||
|
||
//easyjson:json
|
||
type Entities struct {
|
||
Hashtags []Hashtag `json:"hashtags"`
|
||
Urls []*string `json:"urls"`
|
||
UserMentions []*string `json:"user_mentions"`
|
||
}
|
||
|
||
var json = []byte(`{"hashtags":[{"indices":[5, 10],"text":"some-text"}],"urls":[],"user_mentions":[]}`)
|
||
|
||
for i := 0; i < b.N; i++ {
|
||
var value Entities
|
||
if err := Unmarshal(json, &value); err != nil {
|
||
b.Fatal(err)
|
||
}
|
||
}
|
||
}
|
||
|
||
type testMarshaller struct {
|
||
v string
|
||
}
|
||
|
||
func (m *testMarshaller) MarshalJSON() ([]byte, error) {
|
||
return Marshal(m.v)
|
||
}
|
||
|
||
func (m *testMarshaller) UnmarshalJSON(data []byte) error {
|
||
return Unmarshal(data, &m.v)
|
||
}
|
||
|
||
func TestGithubIssue11(t *testing.T) {
|
||
// https://github.com/segmentio/encoding/issues/11
|
||
v := struct{ F float64 }{
|
||
F: math.NaN(),
|
||
}
|
||
|
||
_, err := Marshal(v)
|
||
if err == nil {
|
||
t.Error("no error returned when marshalling NaN value")
|
||
} else if s := err.Error(); !strings.Contains(s, "NaN") {
|
||
t.Error("error returned when marshalling NaN value does not mention 'NaN':", s)
|
||
} else {
|
||
t.Log(s)
|
||
}
|
||
}
|
||
|
||
type Issue13 struct {
|
||
Stringer fmt.Stringer
|
||
Field int `json:"MyInt"`
|
||
}
|
||
|
||
type S string
|
||
|
||
func (s S) String() string { return string(s) }
|
||
|
||
func TestGithubIssue13(t *testing.T) {
|
||
// https://github.com/segmentio/encoding/issues/13
|
||
v := Issue13{}
|
||
|
||
b, err := Marshal(v)
|
||
if err != nil {
|
||
t.Error("unexpected errror:", err)
|
||
} else {
|
||
t.Log(string(b))
|
||
}
|
||
|
||
v = Issue13{Stringer: S("")}
|
||
if err := Unmarshal([]byte(`{"Stringer":null}`), &v); err != nil {
|
||
t.Error("unexpected error:", err)
|
||
}
|
||
if v.Stringer != nil {
|
||
t.Error("Stringer field was not overwritten")
|
||
}
|
||
|
||
v = Issue13{}
|
||
if err := Unmarshal([]byte(`{"Stringer":"whatever"}`), &v); err == nil {
|
||
t.Error("expected error but decoding string value into nil fmt.Stringer but got <nil>")
|
||
}
|
||
|
||
v = Issue13{Stringer: S("")}
|
||
if err := Unmarshal([]byte(`{"Stringer":"whatever"}`), &v); err == nil {
|
||
t.Error("expected error but decoding string value into non-pointer fmt.Stringer but got <nil>")
|
||
}
|
||
|
||
s := S("")
|
||
v = Issue13{Stringer: &s}
|
||
if err := Unmarshal([]byte(`{"Stringer":"whatever"}`), &v); err != nil {
|
||
t.Error("unexpected error decoding string value into pointer fmt.Stringer:", err)
|
||
}
|
||
}
|
||
|
||
func TestGithubIssue15(t *testing.T) {
|
||
// https://github.com/segmentio/encoding/issues/15
|
||
tests := []struct {
|
||
m interface{}
|
||
s string
|
||
}{
|
||
{
|
||
m: map[uint]bool{1: true, 123: true, 333: true, 42: true},
|
||
s: `{"1":true,"123":true,"333":true,"42":true}`,
|
||
},
|
||
{
|
||
m: map[int]bool{-1: true, -123: true, 333: true, 42: true},
|
||
s: `{"-1":true,"-123":true,"333":true,"42":true}`,
|
||
},
|
||
}
|
||
|
||
for _, test := range tests {
|
||
b, _ := Marshal(test.m)
|
||
|
||
if string(b) != test.s {
|
||
t.Error("map with integer keys must be ordered by their string representation, got", string(b))
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
type sliceA []byte
|
||
|
||
func (sliceA) MarshalJSON() ([]byte, error) {
|
||
return []byte(`"A"`), nil
|
||
}
|
||
|
||
type sliceB []byte
|
||
|
||
func (sliceB) MarshalText() ([]byte, error) {
|
||
return []byte("B"), nil
|
||
}
|
||
|
||
type mapA map[string]string
|
||
|
||
func (mapA) MarshalJSON() ([]byte, error) {
|
||
return []byte(`"A"`), nil
|
||
}
|
||
|
||
type mapB map[string]string
|
||
|
||
func (mapB) MarshalText() ([]byte, error) {
|
||
return []byte("B"), nil
|
||
}
|
||
|
||
type intPtrA int
|
||
|
||
func (*intPtrA) MarshalJSON() ([]byte, error) {
|
||
return []byte(`"A"`), nil
|
||
}
|
||
|
||
type intPtrB int
|
||
|
||
func (*intPtrB) MarshalText() ([]byte, error) {
|
||
return []byte("B"), nil
|
||
}
|
||
|
||
type structA struct{ I intPtrA }
|
||
type structB struct{ I intPtrB }
|
||
type structC struct{ M Marshaler }
|
||
type structD struct{ M encoding.TextMarshaler }
|
||
|
||
func TestGithubIssue16(t *testing.T) {
|
||
// https://github.com/segmentio/encoding/issues/16
|
||
tests := []struct {
|
||
value interface{}
|
||
output string
|
||
}{
|
||
{value: sliceA(nil), output: `"A"`},
|
||
{value: sliceB(nil), output: `"B"`},
|
||
{value: mapA(nil), output: `"A"`},
|
||
{value: mapB(nil), output: `"B"`},
|
||
{value: intPtrA(1), output: `1`},
|
||
{value: intPtrB(2), output: `2`},
|
||
{value: new(intPtrA), output: `"A"`},
|
||
{value: new(intPtrB), output: `"B"`},
|
||
{value: (*intPtrA)(nil), output: `null`},
|
||
{value: (*intPtrB)(nil), output: `null`},
|
||
{value: structA{I: 1}, output: `{"I":1}`},
|
||
{value: structB{I: 2}, output: `{"I":2}`},
|
||
{value: structC{}, output: `{"M":null}`},
|
||
{value: structD{}, output: `{"M":null}`},
|
||
{value: &structA{I: 1}, output: `{"I":"A"}`},
|
||
{value: &structB{I: 2}, output: `{"I":"B"}`},
|
||
{value: &structC{}, output: `{"M":null}`},
|
||
{value: &structD{}, output: `{"M":null}`},
|
||
}
|
||
|
||
for _, test := range tests {
|
||
t.Run(fmt.Sprintf("%T", test.value), func(t *testing.T) {
|
||
if b, _ := Marshal(test.value); string(b) != test.output {
|
||
t.Errorf(`%s != %s`, string(b), test.output)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestDecoderInputOffset(t *testing.T) {
|
||
checkOffset := func(o, expected int64) {
|
||
if o != expected {
|
||
t.Error("unexpected input offset", o, expected)
|
||
}
|
||
}
|
||
|
||
b := []byte(`{"userId": "blah"}{"userId": "blah"}
|
||
{"userId": "blah"}{"num": 0}`)
|
||
d := NewDecoder(bytes.NewReader(b))
|
||
|
||
var expected int64
|
||
checkOffset(d.InputOffset(), expected)
|
||
|
||
var a struct {
|
||
UserId string `json:"userId"`
|
||
}
|
||
|
||
if err := d.Decode(&a); err != nil {
|
||
t.Error("unexpected decode error", err)
|
||
}
|
||
expected = int64(18)
|
||
checkOffset(d.InputOffset(), expected)
|
||
|
||
if err := d.Decode(&a); err != nil {
|
||
t.Error("unexpected decode error", err)
|
||
}
|
||
expected = int64(38)
|
||
checkOffset(d.InputOffset(), expected)
|
||
|
||
if err := d.Decode(&a); err != nil {
|
||
t.Error("unexpected decode error", err)
|
||
}
|
||
expected = int64(56)
|
||
checkOffset(d.InputOffset(), expected)
|
||
|
||
var z struct {
|
||
Num int64 `json:"num"`
|
||
}
|
||
if err := d.Decode(&z); err != nil {
|
||
t.Error("unexpected decode error", err)
|
||
}
|
||
expected = int64(66)
|
||
checkOffset(d.InputOffset(), expected)
|
||
}
|
||
|
||
func TestGithubIssue18(t *testing.T) {
|
||
// https://github.com/segmentio/encoding/issues/18
|
||
b := []byte(`{
|
||
"userId": "blah",
|
||
}`)
|
||
|
||
d := NewDecoder(bytes.NewReader(b))
|
||
|
||
var a struct {
|
||
UserId string `json:"userId"`
|
||
}
|
||
switch err := d.Decode(&a).(type) {
|
||
case *SyntaxError:
|
||
default:
|
||
t.Error("expected syntax error but found:", err)
|
||
}
|
||
|
||
for i := 1; i <= 18; i++ { // up to the invalid ',' character
|
||
d := NewDecoder(bytes.NewReader(b[:i])) // cut somewhere in the middle
|
||
switch err := d.Decode(&a); err {
|
||
case io.ErrUnexpectedEOF:
|
||
default:
|
||
t.Error("expected 'unexpected EOF' error but found:", err)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestGithubIssue23(t *testing.T) {
|
||
t.Run("marshal-1", func(t *testing.T) {
|
||
type d struct{ S map[string]string }
|
||
|
||
b, _ := Marshal(map[string]d{"1": {S: map[string]string{"2": "3"}}})
|
||
if string(b) != `{"1":{"S":{"2":"3"}}}` {
|
||
t.Error(string(b))
|
||
}
|
||
})
|
||
|
||
t.Run("marshal-2", func(t *testing.T) {
|
||
type testInner struct {
|
||
InnerMap map[string]string `json:"inner_map"`
|
||
}
|
||
|
||
type testOuter struct {
|
||
OuterMap map[string]testInner `json:"outer_map"`
|
||
}
|
||
|
||
b, _ := Marshal(testOuter{
|
||
OuterMap: map[string]testInner{
|
||
"outer": {
|
||
InnerMap: map[string]string{"inner": "value"},
|
||
},
|
||
},
|
||
})
|
||
|
||
if string(b) != `{"outer_map":{"outer":{"inner_map":{"inner":"value"}}}}` {
|
||
t.Error(string(b))
|
||
}
|
||
})
|
||
|
||
t.Run("marshal-3", func(t *testing.T) {
|
||
type A struct{ A map[string]string }
|
||
type B struct{ B map[string]A }
|
||
type C struct{ C map[string]B }
|
||
|
||
b, _ := Marshal(C{
|
||
C: map[string]B{
|
||
"1": B{
|
||
B: map[string]A{
|
||
"2": A{
|
||
A: map[string]string{"3": "!"},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
})
|
||
|
||
if string(b) != `{"C":{"1":{"B":{"2":{"A":{"3":"!"}}}}}}` {
|
||
t.Error(string(b))
|
||
}
|
||
})
|
||
|
||
t.Run("unmarshal-1", func(t *testing.T) {
|
||
var d struct{ S map[string]string }
|
||
|
||
if err := Unmarshal([]byte(`{"1":{"S":{"2":"3"}}}`), &d); err != nil {
|
||
t.Error(err)
|
||
}
|
||
})
|
||
}
|
||
|
||
func TestGithubIssue26(t *testing.T) {
|
||
type interfaceType interface{}
|
||
|
||
var value interfaceType
|
||
var data = []byte(`{}`)
|
||
|
||
if err := Unmarshal(data, &value); err != nil {
|
||
t.Error(err)
|
||
}
|
||
}
|
||
|
||
func TestGithubIssue28(t *testing.T) {
|
||
type A struct {
|
||
Err error `json:"err"`
|
||
}
|
||
|
||
if b, err := Marshal(&A{Err: errors.New("ABC")}); err != nil {
|
||
t.Error(err)
|
||
} else if string(b) != `{"err":{}}` {
|
||
t.Error(string(b))
|
||
}
|
||
|
||
}
|
||
|
||
func TestGithubIssue41(t *testing.T) {
|
||
expectedString := `{"Zero":0,"Three":3}`
|
||
type M struct {
|
||
One int
|
||
Two int
|
||
}
|
||
type N struct {
|
||
Zero int
|
||
*M
|
||
Three int
|
||
}
|
||
|
||
if b, err := Marshal(N{Three: 3}); err != nil {
|
||
t.Error(err)
|
||
} else if string(b) != expectedString {
|
||
t.Error(
|
||
"got: ", string(b),
|
||
"expected: ", expectedString,
|
||
)
|
||
}
|
||
|
||
}
|
||
|
||
func TestGithubIssue44(t *testing.T) {
|
||
var out rawJsonString
|
||
if err := Unmarshal([]byte("null"), &out); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if out != "null" {
|
||
t.Errorf("wanted \"null\" but got %q", out)
|
||
}
|
||
}
|
||
|
||
type rawJsonString string
|
||
|
||
func (r *rawJsonString) UnmarshalJSON(b []byte) error {
|
||
if len(b) == 0 {
|
||
*r = "null"
|
||
} else {
|
||
*r = rawJsonString(b)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func TestSetTrustRawMessage(t *testing.T) {
|
||
buf := &bytes.Buffer{}
|
||
enc := NewEncoder(buf)
|
||
enc.SetTrustRawMessage(true)
|
||
|
||
// "Good" values are encoded in the regular way
|
||
m := map[string]json.RawMessage{
|
||
"k": json.RawMessage(`"value"`),
|
||
}
|
||
if err := enc.Encode(m); err != nil {
|
||
t.Error(err)
|
||
}
|
||
|
||
b := buf.Bytes()
|
||
exp := []byte(`{"k":"value"}`)
|
||
exp = append(exp, '\n')
|
||
if bytes.Compare(exp, b) != 0 {
|
||
t.Error(
|
||
"unexpected encoding:",
|
||
"expected", exp,
|
||
"got", b,
|
||
)
|
||
}
|
||
|
||
// "Bad" values are encoded without checking and throwing an error
|
||
buf.Reset()
|
||
m = map[string]json.RawMessage{
|
||
"k": json.RawMessage(`bad"value`),
|
||
}
|
||
if err := enc.Encode(m); err != nil {
|
||
t.Error(err)
|
||
}
|
||
|
||
b = buf.Bytes()
|
||
exp = []byte(`{"k":bad"value}`)
|
||
exp = append(exp, '\n')
|
||
if bytes.Compare(exp, b) != 0 {
|
||
t.Error(
|
||
"unexpected encoding:",
|
||
"expected", exp,
|
||
"got", b,
|
||
)
|
||
}
|
||
}
|