all repos

json2go @ v0.2.0

convert json to go type annotations

json2go/parser_test.go (view raw)

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
refactor: use an actual parser instead of reflection..., 11 days ago
1
package json2go
2
3
import (
4
	"encoding/json"
5
	"reflect"
6
	"testing"
7
)
8
9
func TestParser_Parse(t *testing.T) {
10
	tests := map[string]struct {
11
		inp      string
12
		expected Value
13
		err      bool
14
	}{
15
		"string value": {
16
			inp:      `"hello"`,
17
			expected: Value{Kind: StringValue, Str: "hello"},
18
		},
19
		"integer value":    {inp: `42`, expected: Value{Kind: NumberValue, Int: 42}},
20
		"negative integer": {inp: `-42`, expected: Value{Kind: NumberValue, Int: -42}},
21
		"decimal value":    {inp: `3.14`, expected: Value{Kind: DecimalValue, Float: 3.14}},
22
		"bool true":        {inp: `true`, expected: Value{Kind: BoolValue, Bool: true}},
23
		"bool false":       {inp: `false`, expected: Value{Kind: BoolValue, Bool: false}},
24
		"null":             {inp: `null`, expected: Value{Kind: NullValue}},
25
		"empty object":     {inp: `{}`, expected: Value{Kind: ObjectValue}},
26
		"empty array":      {inp: `[]`, expected: Value{Kind: ArrayValue}},
27
		"flat object": {
28
			inp: `{"name": "John", "age": 30, "active": true}`,
29
			expected: Value{Kind: ObjectValue, Object: []Field{
30
				{"name", Value{Kind: StringValue, Str: "John"}},
31
				{"age", Value{Kind: NumberValue, Int: 30}},
32
				{"active", Value{Kind: BoolValue, Bool: true}},
33
			}},
34
		},
35
		"nested object": {
36
			inp: `{"user": {"name": "John", "age": 30}}`,
37
			expected: Value{Kind: ObjectValue, Object: []Field{
38
				{"user", Value{Kind: ObjectValue, Object: []Field{
39
					{"name", Value{Kind: StringValue, Str: "John"}},
40
					{"age", Value{Kind: NumberValue, Int: 30}},
41
				}}},
42
			}},
43
		},
44
		"array of numbers": {
45
			inp: `[1, 2, 3]`,
46
			expected: Value{Kind: ArrayValue, Array: []Value{
47
				{Kind: NumberValue, Int: 1},
48
				{Kind: NumberValue, Int: 2},
49
				{Kind: NumberValue, Int: 3},
50
			}},
51
		},
52
		"array of objects": {
53
			inp: `[{"a": 1}, {"a": 2}]`,
54
			expected: Value{Kind: ArrayValue, Array: []Value{
55
				{Kind: ObjectValue, Object: []Field{{"a", Value{Kind: NumberValue, Int: 1}}}},
56
				{Kind: ObjectValue, Object: []Field{{"a", Value{Kind: NumberValue, Int: 2}}}},
57
			}},
58
		},
59
		"object with line comment": {
60
			inp: `{
61
				// this is a comment
62
				"key": "value"
63
			}`,
64
			expected: Value{Kind: ObjectValue, Object: []Field{
65
				{"key", Value{Kind: StringValue, Str: "value"}},
66
			}},
67
		},
68
		"object with block comment": {
69
			inp: `{"key": /* comment */ "value"}`,
70
			expected: Value{Kind: ObjectValue, Object: []Field{
71
				{"key", Value{Kind: StringValue, Str: "value"}},
72
			}},
73
		},
74
		"trailing comma in object": {
75
			inp: `{"key": "value",}`,
76
			expected: Value{Kind: ObjectValue, Object: []Field{
77
				{"key", Value{Kind: StringValue, Str: "value"}},
78
			}},
79
		},
80
		"trailing comma in array": {
81
			inp: `[1, 2, 3,]`,
82
			expected: Value{Kind: ArrayValue, Array: []Value{
83
				{Kind: NumberValue, Int: 1},
84
				{Kind: NumberValue, Int: 2},
85
				{Kind: NumberValue, Int: 3},
86
			}},
87
		},
88
		"unterminated object": {inp: `{"key": "value"`, err: true},
89
		"unterminated array":  {inp: `[1, 2`, err: true},
90
		"missing colon":       {inp: `{"key" "value"}`, err: true},
91
		"non-string key":      {inp: `{42: "value"}`, err: true},
92
		"trailing content":    {inp: `{} 1`, err: true},
93
	}
94
	for tname, tt := range tests {
95
		t.Run(tname, func(t *testing.T) {
96
			l := NewLexer([]byte(tt.inp))
97
			p := NewParser(l)
98
			got, err := p.Parse()
99
			if tt.err {
100
				if err == nil {
101
					t.Errorf("expected error, got nil (value=%+v)", got)
102
				}
103
				return
104
			}
105
			if err != nil {
106
				t.Fatalf("unexpected error: %v", err)
107
			}
108
			if !reflect.DeepEqual(got, tt.expected) {
109
				t.Errorf("wrong value\nexpected: %+v\ngot:      %+v", tt.expected, got)
110
			}
111
		})
112
	}
113
}
114
115
// ensures the parser handles all valid json that encoding/json accepts.
116
func FuzzParser(f *testing.F) {
117
	f.Add([]byte("null"))
118
	f.Add([]byte("true"))
119
	f.Add([]byte("false"))
120
	f.Add([]byte("0"))
121
	f.Add([]byte("1.5"))
122
	f.Add([]byte("-42"))
123
	f.Add([]byte("1e10"))
124
	f.Add([]byte("1.5e-5"))
125
	f.Add([]byte(`""`))
126
	f.Add([]byte(`"hello"`))
127
	f.Add([]byte(`"hello\nworld"`))
128
	f.Add([]byte(`"hello\\nworld"`))
129
	f.Add([]byte(`"unicode\u0041"`))
130
	f.Add([]byte(`[]`))
131
	f.Add([]byte(`[1,2,3]`))
132
	f.Add([]byte(`{}`))
133
	f.Add([]byte(`{"a":1}`))
134
	f.Add([]byte(`{"a":1,"b":2}`))
135
	f.Add([]byte(`{"nested":{"x":1}}`))
136
	f.Add([]byte(`[{"a":1}]`))
137
	f.Add([]byte(`[1,[2,[3,[4]]]]`))
138
	f.Add([]byte(`{"a":"b","c":{"d":"e"}}`))
139
	f.Add([]byte(`[null,true,false]`))
140
	f.Add([]byte(`{"unicode":"🔥"}`))
141
	f.Add([]byte(`{ "a" : 1 }`))
142
	f.Add([]byte("{\n\t\"a\": 1\n}"))
143
	f.Add([]byte(`{"a":1/*comment*/,"b":2}`))
144
	f.Add([]byte(`{"a":1//comment
145
}`))
146
147
	f.Fuzz(func(t *testing.T, data []byte) {
148
		var any interface{}
149
		stdErr := json.Unmarshal(data, &any)
150
151
		lexer := NewLexer(data)
152
		parser := NewParser(lexer)
153
		_, parseErr := parser.Parse()
154
155
		// If encoding/json accepts it, our parser should too
156
		if stdErr == nil && parseErr != nil {
157
			t.Fatalf("encoding/json accepted but our parser rejected: %s\nerror: %v", string(data), parseErr)
158
		}
159
160
		// If our parser succeeded, transpiler should succeed too
161
		if parseErr == nil {
162
			_, transpileErr := Transform("Test", string(data), true)
163
			if transpileErr != nil {
164
				t.Fatalf("parser succeeded but transpiler failed: %s\nerror: %v", string(data), transpileErr)
165
			}
166
		}
167
	})
168
}