├── .bench
├── .gitignore
├── bench_test.go
├── code.go
├── code_decoder.go
├── code_encoder.go
└── testdata
│ └── code.json.gz
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── generator
├── decoder
│ ├── decoder.tmpl
│ ├── decoder.tmpl.go
│ ├── generator.go
│ ├── generator_test.go
│ └── template.go
├── encoder
│ ├── encoder.tmpl
│ ├── encoder.tmpl.go
│ ├── generator.go
│ ├── generator_test.go
│ └── template.go
├── generator.go
└── test
│ ├── .fixtures
│ ├── nested
│ │ ├── decode.go
│ │ ├── encode.go
│ │ └── types.go
│ └── simple
│ │ ├── decode.go
│ │ ├── encode.go
│ │ └── types.go
│ └── test.go
├── main.go
├── scanner
├── .gitignore
├── scanner.go
├── scanner_test.go
└── token.go
└── writer
├── writer.go
└── writer_test.go
/.bench/.gitignore:
--------------------------------------------------------------------------------
1 | *.prof
2 | *.test
3 |
--------------------------------------------------------------------------------
/.bench/bench_test.go:
--------------------------------------------------------------------------------
1 | package bench
2 |
3 | import (
4 | "bytes"
5 | "compress/gzip"
6 | "io/ioutil"
7 | "os"
8 | "testing"
9 | )
10 |
11 | var codeJSON []byte
12 | var codeStruct = &codeResponse{}
13 |
14 | func codeInit() {
15 | f, err := os.Open("testdata/code.json.gz")
16 | if err != nil {
17 | panic(err)
18 | }
19 | defer f.Close()
20 | gz, err := gzip.NewReader(f)
21 | if err != nil {
22 | panic(err)
23 | }
24 | data, err := ioutil.ReadAll(gz)
25 | if err != nil {
26 | panic(err)
27 | }
28 |
29 | codeJSON = data
30 |
31 | if err := NewcodeResponseJSONDecoder(bytes.NewBuffer(codeJSON)).Decode(&codeStruct); err != nil {
32 | panic("decode code.json: " + err.Error())
33 | }
34 |
35 | var b bytes.Buffer
36 | if err = NewcodeResponseJSONEncoder(&b).Encode(codeStruct); err != nil {
37 | panic("encode code.json: " + err.Error())
38 | }
39 | data = b.Bytes()
40 |
41 | if !bytes.Equal(data, codeJSON) {
42 | println("different lengths", len(data), len(codeJSON))
43 | for i := 0; i < len(data) && i < len(codeJSON); i++ {
44 | if data[i] != codeJSON[i] {
45 | println("re-marshal: changed at byte", i)
46 | println("orig: ", string(codeJSON[i-10:i+10]))
47 | println("new: ", string(data[i-10:i+10]))
48 | break
49 | }
50 | }
51 | panic("re-marshal code.json: different result")
52 | }
53 | }
54 |
55 | func BenchmarkCodeEncoder(b *testing.B) {
56 | if codeJSON == nil {
57 | b.StopTimer()
58 | codeInit()
59 | b.StartTimer()
60 | }
61 | enc := NewcodeResponseJSONEncoder(ioutil.Discard)
62 | for i := 0; i < b.N; i++ {
63 | if err := enc.Encode(codeStruct); err != nil {
64 | b.Fatal("Encode:", err)
65 | }
66 | }
67 | b.SetBytes(int64(len(codeJSON)))
68 | }
69 |
70 | func BenchmarkCodeDecoder(b *testing.B) {
71 | if codeJSON == nil {
72 | b.StopTimer()
73 | codeInit()
74 | b.StartTimer()
75 | }
76 | var buf bytes.Buffer
77 | dec := NewcodeResponseJSONDecoder(&buf)
78 | r := &codeResponse{}
79 | for i := 0; i < b.N; i++ {
80 | buf.Write(codeJSON)
81 | // hide EOF
82 | buf.WriteByte('\n')
83 | buf.WriteByte('\n')
84 | buf.WriteByte('\n')
85 | if err := dec.Decode(&r); err != nil {
86 | b.Fatal("Decode:", err)
87 | }
88 | }
89 | b.SetBytes(int64(len(codeJSON)))
90 | }
91 |
--------------------------------------------------------------------------------
/.bench/code.go:
--------------------------------------------------------------------------------
1 | package bench
2 |
3 | type codeResponse struct {
4 | Tree *codeNode `json:"tree"`
5 | Username string `json:"username"`
6 | }
7 |
8 | type codeNode struct {
9 | Name string `json:"name"`
10 | Kids []*codeNode `json:"kids"`
11 | CLWeight float64 `json:"cl_weight"`
12 | Touches int `json:"touches"`
13 | MinT int64 `json:"min_t"`
14 | MaxT int64 `json:"max_t"`
15 | MeanT int64 `json:"mean_t"`
16 | }
17 |
--------------------------------------------------------------------------------
/.bench/code_decoder.go:
--------------------------------------------------------------------------------
1 | package bench
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "github.com/benbjohnson/megajson/scanner"
7 | "io"
8 | )
9 |
10 | type codeResponseJSONDecoder struct {
11 | s scanner.Scanner
12 | }
13 |
14 | func NewcodeResponseJSONDecoder(r io.Reader) *codeResponseJSONDecoder {
15 | return &codeResponseJSONDecoder{s: scanner.NewScanner(r)}
16 | }
17 |
18 | func NewcodeResponseJSONScanDecoder(s scanner.Scanner) *codeResponseJSONDecoder {
19 | return &codeResponseJSONDecoder{s: s}
20 | }
21 |
22 | func (e *codeResponseJSONDecoder) Decode(ptr **codeResponse) error {
23 | s := e.s
24 | if tok, tokval, err := s.Scan(); err != nil {
25 | return err
26 | } else if tok == scanner.TNULL {
27 | *ptr = nil
28 | return nil
29 | } else if tok != scanner.TLBRACE {
30 | return fmt.Errorf("Unexpected %s at %d: %s; expected '{'", scanner.TokenName(tok), s.Pos(), string(tokval))
31 | }
32 |
33 | // Create the object if it doesn't exist.
34 | if *ptr == nil {
35 | *ptr = &codeResponse{}
36 | }
37 | v := *ptr
38 |
39 | // Loop over key/value pairs.
40 | index := 0
41 | for {
42 | // Read in key.
43 | var key string
44 | tok, tokval, err := s.Scan()
45 | if err != nil {
46 | return err
47 | } else if tok == scanner.TRBRACE {
48 | return nil
49 | } else if tok == scanner.TCOMMA {
50 | if index == 0 {
51 | return fmt.Errorf("Unexpected comma at %d", s.Pos())
52 | }
53 | if tok, tokval, err = s.Scan(); err != nil {
54 | return err
55 | }
56 | }
57 |
58 | if tok != scanner.TSTRING {
59 | return fmt.Errorf("Unexpected %s at %d: %s; expected '{' or string", scanner.TokenName(tok), s.Pos(), string(tokval))
60 | } else {
61 | key = string(tokval)
62 | }
63 |
64 | // Read in the colon.
65 | if tok, tokval, err := s.Scan(); err != nil {
66 | return err
67 | } else if tok != scanner.TCOLON {
68 | return fmt.Errorf("Unexpected %s at %d: %s; expected colon", scanner.TokenName(tok), s.Pos(), string(tokval))
69 | }
70 |
71 | switch key {
72 |
73 | case "tree":
74 | v := &v.Tree
75 |
76 | if err := NewcodeNodeJSONScanDecoder(s).Decode(v); err != nil {
77 | return err
78 | }
79 |
80 | case "username":
81 | v := &v.Username
82 |
83 | if err := s.ReadString(v); err != nil {
84 | return err
85 | }
86 |
87 | }
88 |
89 | index++
90 | }
91 |
92 | return nil
93 | }
94 |
95 | func (e *codeResponseJSONDecoder) DecodeArray(ptr *[]*codeResponse) error {
96 | s := e.s
97 | if tok, _, err := s.Scan(); err != nil {
98 | return err
99 | } else if tok != scanner.TLBRACKET {
100 | return errors.New("Expected '['")
101 | }
102 |
103 | slice := make([]*codeResponse, 0)
104 |
105 | // Loop over items.
106 | index := 0
107 | for {
108 | tok, tokval, err := s.Scan()
109 | if err != nil {
110 | return err
111 | } else if tok == scanner.TRBRACKET {
112 | *ptr = slice
113 | return nil
114 | } else if tok == scanner.TCOMMA {
115 | if index == 0 {
116 | return fmt.Errorf("Unexpected comma in array at %d", s.Pos())
117 | }
118 | if tok, tokval, err = s.Scan(); err != nil {
119 | return err
120 | }
121 | }
122 | s.Unscan(tok, tokval)
123 |
124 | item := &codeResponse{}
125 | if err := e.Decode(&item); err != nil {
126 | return err
127 | }
128 | slice = append(slice, item)
129 |
130 | index++
131 | }
132 | }
133 |
134 | type codeNodeJSONDecoder struct {
135 | s scanner.Scanner
136 | }
137 |
138 | func NewcodeNodeJSONDecoder(r io.Reader) *codeNodeJSONDecoder {
139 | return &codeNodeJSONDecoder{s: scanner.NewScanner(r)}
140 | }
141 |
142 | func NewcodeNodeJSONScanDecoder(s scanner.Scanner) *codeNodeJSONDecoder {
143 | return &codeNodeJSONDecoder{s: s}
144 | }
145 |
146 | func (e *codeNodeJSONDecoder) Decode(ptr **codeNode) error {
147 | s := e.s
148 | if tok, tokval, err := s.Scan(); err != nil {
149 | return err
150 | } else if tok == scanner.TNULL {
151 | *ptr = nil
152 | return nil
153 | } else if tok != scanner.TLBRACE {
154 | return fmt.Errorf("Unexpected %s at %d: %s; expected '{'", scanner.TokenName(tok), s.Pos(), string(tokval))
155 | }
156 |
157 | // Create the object if it doesn't exist.
158 | if *ptr == nil {
159 | *ptr = &codeNode{}
160 | }
161 | v := *ptr
162 |
163 | // Loop over key/value pairs.
164 | index := 0
165 | for {
166 | // Read in key.
167 | var key string
168 | tok, tokval, err := s.Scan()
169 | if err != nil {
170 | return err
171 | } else if tok == scanner.TRBRACE {
172 | return nil
173 | } else if tok == scanner.TCOMMA {
174 | if index == 0 {
175 | return fmt.Errorf("Unexpected comma at %d", s.Pos())
176 | }
177 | if tok, tokval, err = s.Scan(); err != nil {
178 | return err
179 | }
180 | }
181 |
182 | if tok != scanner.TSTRING {
183 | return fmt.Errorf("Unexpected %s at %d: %s; expected '{' or string", scanner.TokenName(tok), s.Pos(), string(tokval))
184 | } else {
185 | key = string(tokval)
186 | }
187 |
188 | // Read in the colon.
189 | if tok, tokval, err := s.Scan(); err != nil {
190 | return err
191 | } else if tok != scanner.TCOLON {
192 | return fmt.Errorf("Unexpected %s at %d: %s; expected colon", scanner.TokenName(tok), s.Pos(), string(tokval))
193 | }
194 |
195 | switch key {
196 |
197 | case "name":
198 | v := &v.Name
199 |
200 | if err := s.ReadString(v); err != nil {
201 | return err
202 | }
203 |
204 | case "kids":
205 | v := &v.Kids
206 |
207 | if err := NewcodeNodeJSONScanDecoder(s).DecodeArray(v); err != nil {
208 | return err
209 | }
210 |
211 | case "cl_weight":
212 | v := &v.CLWeight
213 |
214 | if err := s.ReadFloat64(v); err != nil {
215 | return err
216 | }
217 |
218 | case "touches":
219 | v := &v.Touches
220 |
221 | if err := s.ReadInt(v); err != nil {
222 | return err
223 | }
224 |
225 | case "min_t":
226 | v := &v.MinT
227 |
228 | if err := s.ReadInt64(v); err != nil {
229 | return err
230 | }
231 |
232 | case "max_t":
233 | v := &v.MaxT
234 |
235 | if err := s.ReadInt64(v); err != nil {
236 | return err
237 | }
238 |
239 | case "mean_t":
240 | v := &v.MeanT
241 |
242 | if err := s.ReadInt64(v); err != nil {
243 | return err
244 | }
245 |
246 | }
247 |
248 | index++
249 | }
250 |
251 | return nil
252 | }
253 |
254 | func (e *codeNodeJSONDecoder) DecodeArray(ptr *[]*codeNode) error {
255 | s := e.s
256 | if tok, _, err := s.Scan(); err != nil {
257 | return err
258 | } else if tok != scanner.TLBRACKET {
259 | return errors.New("Expected '['")
260 | }
261 |
262 | slice := make([]*codeNode, 0)
263 |
264 | // Loop over items.
265 | index := 0
266 | for {
267 | tok, tokval, err := s.Scan()
268 | if err != nil {
269 | return err
270 | } else if tok == scanner.TRBRACKET {
271 | *ptr = slice
272 | return nil
273 | } else if tok == scanner.TCOMMA {
274 | if index == 0 {
275 | return fmt.Errorf("Unexpected comma in array at %d", s.Pos())
276 | }
277 | if tok, tokval, err = s.Scan(); err != nil {
278 | return err
279 | }
280 | }
281 | s.Unscan(tok, tokval)
282 |
283 | item := &codeNode{}
284 | if err := e.Decode(&item); err != nil {
285 | return err
286 | }
287 | slice = append(slice, item)
288 |
289 | index++
290 | }
291 | }
292 |
--------------------------------------------------------------------------------
/.bench/code_encoder.go:
--------------------------------------------------------------------------------
1 | package bench
2 |
3 | import (
4 | "github.com/benbjohnson/megajson/writer"
5 | "io"
6 | )
7 |
8 | type codeResponseJSONEncoder struct {
9 | w *writer.Writer
10 | }
11 |
12 | func NewcodeResponseJSONEncoder(w io.Writer) *codeResponseJSONEncoder {
13 | return &codeResponseJSONEncoder{w: writer.NewWriter(w)}
14 | }
15 |
16 | func NewcodeResponseJSONRawEncoder(w *writer.Writer) *codeResponseJSONEncoder {
17 | return &codeResponseJSONEncoder{w: w}
18 | }
19 |
20 | func (e *codeResponseJSONEncoder) Encode(v *codeResponse) error {
21 | if err := e.RawEncode(v); err != nil {
22 | return err
23 | }
24 | if err := e.w.Flush(); err != nil {
25 | return err
26 | }
27 | return nil
28 | }
29 |
30 | func (e *codeResponseJSONEncoder) RawEncode(v *codeResponse) error {
31 | if v == nil {
32 | return e.w.WriteNull()
33 | }
34 |
35 | if err := e.w.WriteByte('{'); err != nil {
36 | return err
37 | }
38 |
39 | // Write key and colon.
40 | if err := e.w.WriteString("tree"); err != nil {
41 | return err
42 | }
43 | if err := e.w.WriteByte(':'); err != nil {
44 | return err
45 | }
46 |
47 | // Write value.
48 | {
49 | v := v.Tree
50 |
51 | if err := NewcodeNodeJSONRawEncoder(e.w).RawEncode(v); err != nil {
52 | return err
53 | }
54 |
55 | }
56 |
57 | if err := e.w.WriteByte(','); err != nil {
58 | return err
59 | }
60 |
61 | // Write key and colon.
62 | if err := e.w.WriteString("username"); err != nil {
63 | return err
64 | }
65 | if err := e.w.WriteByte(':'); err != nil {
66 | return err
67 | }
68 |
69 | // Write value.
70 | {
71 | v := v.Username
72 |
73 | if err := e.w.WriteString(v); err != nil {
74 | return err
75 | }
76 |
77 | }
78 |
79 | if err := e.w.WriteByte('}'); err != nil {
80 | return err
81 | }
82 | return nil
83 | }
84 |
85 | type codeNodeJSONEncoder struct {
86 | w *writer.Writer
87 | }
88 |
89 | func NewcodeNodeJSONEncoder(w io.Writer) *codeNodeJSONEncoder {
90 | return &codeNodeJSONEncoder{w: writer.NewWriter(w)}
91 | }
92 |
93 | func NewcodeNodeJSONRawEncoder(w *writer.Writer) *codeNodeJSONEncoder {
94 | return &codeNodeJSONEncoder{w: w}
95 | }
96 |
97 | func (e *codeNodeJSONEncoder) Encode(v *codeNode) error {
98 | if err := e.RawEncode(v); err != nil {
99 | return err
100 | }
101 | if err := e.w.Flush(); err != nil {
102 | return err
103 | }
104 | return nil
105 | }
106 |
107 | func (e *codeNodeJSONEncoder) RawEncode(v *codeNode) error {
108 | if v == nil {
109 | return e.w.WriteNull()
110 | }
111 |
112 | if err := e.w.WriteByte('{'); err != nil {
113 | return err
114 | }
115 |
116 | // Write key and colon.
117 | if err := e.w.WriteString("name"); err != nil {
118 | return err
119 | }
120 | if err := e.w.WriteByte(':'); err != nil {
121 | return err
122 | }
123 |
124 | // Write value.
125 | {
126 | v := v.Name
127 |
128 | if err := e.w.WriteString(v); err != nil {
129 | return err
130 | }
131 |
132 | }
133 |
134 | if err := e.w.WriteByte(','); err != nil {
135 | return err
136 | }
137 |
138 | // Write key and colon.
139 | if err := e.w.WriteString("kids"); err != nil {
140 | return err
141 | }
142 | if err := e.w.WriteByte(':'); err != nil {
143 | return err
144 | }
145 |
146 | // Write value.
147 | {
148 | v := v.Kids
149 |
150 | if err := e.w.WriteByte('['); err != nil {
151 | return err
152 | }
153 |
154 | for index, v := range v {
155 | if index > 0 {
156 | if err := e.w.WriteByte(','); err != nil {
157 | return err
158 | }
159 | }
160 | if err := NewcodeNodeJSONRawEncoder(e.w).RawEncode(v); err != nil {
161 | return err
162 | }
163 | }
164 |
165 | if err := e.w.WriteByte(']'); err != nil {
166 | return err
167 | }
168 |
169 | }
170 |
171 | if err := e.w.WriteByte(','); err != nil {
172 | return err
173 | }
174 |
175 | // Write key and colon.
176 | if err := e.w.WriteString("cl_weight"); err != nil {
177 | return err
178 | }
179 | if err := e.w.WriteByte(':'); err != nil {
180 | return err
181 | }
182 |
183 | // Write value.
184 | {
185 | v := v.CLWeight
186 |
187 | if err := e.w.WriteFloat64(v); err != nil {
188 | return err
189 | }
190 |
191 | }
192 |
193 | if err := e.w.WriteByte(','); err != nil {
194 | return err
195 | }
196 |
197 | // Write key and colon.
198 | if err := e.w.WriteString("touches"); err != nil {
199 | return err
200 | }
201 | if err := e.w.WriteByte(':'); err != nil {
202 | return err
203 | }
204 |
205 | // Write value.
206 | {
207 | v := v.Touches
208 |
209 | if err := e.w.WriteInt(v); err != nil {
210 | return err
211 | }
212 |
213 | }
214 |
215 | if err := e.w.WriteByte(','); err != nil {
216 | return err
217 | }
218 |
219 | // Write key and colon.
220 | if err := e.w.WriteString("min_t"); err != nil {
221 | return err
222 | }
223 | if err := e.w.WriteByte(':'); err != nil {
224 | return err
225 | }
226 |
227 | // Write value.
228 | {
229 | v := v.MinT
230 |
231 | if err := e.w.WriteInt64(v); err != nil {
232 | return err
233 | }
234 |
235 | }
236 |
237 | if err := e.w.WriteByte(','); err != nil {
238 | return err
239 | }
240 |
241 | // Write key and colon.
242 | if err := e.w.WriteString("max_t"); err != nil {
243 | return err
244 | }
245 | if err := e.w.WriteByte(':'); err != nil {
246 | return err
247 | }
248 |
249 | // Write value.
250 | {
251 | v := v.MaxT
252 |
253 | if err := e.w.WriteInt64(v); err != nil {
254 | return err
255 | }
256 |
257 | }
258 |
259 | if err := e.w.WriteByte(','); err != nil {
260 | return err
261 | }
262 |
263 | // Write key and colon.
264 | if err := e.w.WriteString("mean_t"); err != nil {
265 | return err
266 | }
267 | if err := e.w.WriteByte(':'); err != nil {
268 | return err
269 | }
270 |
271 | // Write value.
272 | {
273 | v := v.MeanT
274 |
275 | if err := e.w.WriteInt64(v); err != nil {
276 | return err
277 | }
278 |
279 | }
280 |
281 | if err := e.w.WriteByte('}'); err != nil {
282 | return err
283 | }
284 | return nil
285 | }
286 |
--------------------------------------------------------------------------------
/.bench/testdata/code.json.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/benbjohnson/megajson/775aeecdf8b39e0d17749ca8eb4c7d4a5a0662e3/.bench/testdata/code.json.gz
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled Object files, Static and Dynamic libs (Shared Objects)
2 | *.o
3 | *.a
4 | *.so
5 |
6 | # Folders
7 | _obj
8 | _test
9 |
10 | # Architecture specific extensions/prefixes
11 | *.[568vq]
12 | [568vq].out
13 |
14 | *.cgo1.go
15 | *.cgo2.c
16 | _cgo_defun.c
17 | _cgo_gotypes.go
18 | _cgo_export.*
19 |
20 | _testmain.go
21 |
22 | *.exe
23 |
24 | megajson
25 | test/cpu.out
26 | test/test.test
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013 Ben Johnson
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | PKG=./...
2 | TEST=.
3 | BENCH=.
4 | TMPL=$(wildcard generator/**/*.tmpl)
5 | TMPLBIN=$(patsubst %,%.go,${TMPL})
6 | COVERPROFILE=/tmp/c.out
7 |
8 | default:
9 | @echo "usage:"
10 | @echo " make bindata"
11 | @echo " make bench"
12 | @echo " make cloc"
13 | @echo " make PKG=./pkgname cover"
14 | @echo " make fmt"
15 | @echo
16 |
17 |
18 | bindata: $(TMPLBIN)
19 | echo $(TMPLBIN)
20 |
21 | generator/encoder/encoder.tmpl.go: generator/encoder/encoder.tmpl
22 | cat $< | go-bindata -func tmplsrc -pkg encoder | gofmt > $@
23 |
24 | generator/decoder/decoder.tmpl.go: generator/decoder/decoder.tmpl
25 | cat $< | go-bindata -func tmplsrc -pkg decoder | gofmt > $@
26 |
27 |
28 | bench: benchpreq
29 | go test -v -test.bench=$(BENCH) ./.bench
30 |
31 | bench-cpu: benchpreq
32 | cd .bench; \
33 | rm ./.bench.test; \
34 | go test -c; \
35 | ./.bench.test -test.v -test.bench=$(BENCH) -test.benchtime=30s -test.cpuprofile=cpu.prof; \
36 | go tool pprof .bench.test cpu.prof
37 |
38 | benchpreq: bindata
39 | go run main.go -- .bench/code.go
40 |
41 | cloc:
42 | cloc --not-match-f=_test.go --not-match-d=test --not-match-d=.bench .
43 |
44 | cover: coverpreq fmt
45 | go test -coverprofile=$(COVERPROFILE) $(PKG)
46 | go tool cover -html=$(COVERPROFILE)
47 | rm $(COVERPROFILE)
48 |
49 | coverpreq:
50 | @if [[ -z "$(PKG)" ]]; then \
51 | echo "usage: make PKG=./mypkg cover"; \
52 | exit 1; \
53 | fi
54 |
55 | fmt:
56 | go fmt ./...
57 |
58 | test: bindata
59 | go test -i -test.run=$(TEST) $(PKG)
60 | go test -v -test.run=$(TEST) $(PKG)
61 |
62 | goveralls: bindata
63 | goveralls -package=./... $(COVERALLS_TOKEN)
64 |
65 | .PHONY: assets test
66 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # megajson 
2 |
3 | > High performance Go JSON encoder and decoder.
4 |
5 | > *Notice: This tool is unmaintained. Please use [ffjson](https://github.com/pquerna/ffjson) instead.*
6 |
7 | ## Overview
8 |
9 | Go's builtin JSON support works great to provide simple, runtime JSON encoding and decoding.
10 | However, it's based on reflection so it has a few drawbacks:
11 |
12 | * *Performance* - The reflection library is slow and isn't optimized by the compiler at compile time.
13 |
14 | * *Public Fields Only* - The reflection library can only reflect on exported fields.
15 | That means that you can't marshal private fields to JSON.
16 |
17 | Megajson is built to get around some of these limitations.
18 | It's a code generation tool that uses the `go/parser` and `go/ast` packages to write custom encoders and decoders for your types.
19 | These encoders and decoders know your types so the reflection package is not necessary.
20 |
21 |
22 | ### Performance
23 |
24 | Megajson encodes and decodes at approximately two times the speed of the `encoding/json` package using the built-in `encoding/json` test data in Go 1.2.
25 | This is just a benchmark though.
26 | Your mileage may vary.
27 |
28 | Please test megajson encoders using real data for actual results.
29 | This library is primarily focused on performance so performance improvement pull requests are very welcome.
30 |
31 |
32 | ## Installation
33 |
34 | Installing megajson is easy.
35 | Simply `go get` from the command line:
36 |
37 | ```sh
38 | $ go get github.com/benbjohnson/megajson
39 | ```
40 |
41 | And you're ready to go.
42 |
43 |
44 | ## Usage
45 |
46 | Running megajson is simple.
47 | Just provide the files or directories that you want to generate encoders and decoders for:
48 |
49 | ```sh
50 | $ megajson mypkg/my_file.go
51 | ```
52 |
53 | Two new files will be generated:
54 |
55 | ```
56 | mypkg/my_file_encoder.go
57 | mypkg/my_file_decoder.go
58 | ```
59 |
60 | They live in the same package as your `my_file.go` code so they're ready to go.
61 |
62 | Once your encoders and decoders are generated, you can use them just like the `json.Encoder` and `json.Decoder` except they're named after your types.
63 | For a struct type inside `my_file.go` called `MyStruct`, the generated code can be used like this:
64 |
65 | ```go
66 | err := NewMyStructEncoder(writer).Encode(val)
67 | err := NewMyStructDecoder(reader).Decode(&val)
68 | ```
69 |
70 |
71 | ## Supported Types
72 |
73 | The following struct field types are supported:
74 |
75 | * `string`
76 | * `int`, `int64`
77 | * `uint`, `uint64`
78 | * `float32`, `float64`
79 | * `bool`
80 | * Pointers to structs which have been megajsonified.
81 | * Arrays of pointers to structs which have megajsonified.
82 |
83 | If you have a type that you would like to see supported, please add an issue to the GitHub page.
84 |
85 |
--------------------------------------------------------------------------------
/generator/decoder/decoder.tmpl:
--------------------------------------------------------------------------------
1 | package {{.Name.Name}}
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "io"
7 | "github.com/benbjohnson/megajson/scanner"
8 | )
9 |
10 | {{range types .}}
11 | type {{.Name.Name}}JSONDecoder struct {
12 | s scanner.Scanner
13 | }
14 |
15 | func New{{.Name.Name}}JSONDecoder(r io.Reader) *{{.Name.Name}}JSONDecoder {
16 | return &{{.Name.Name}}JSONDecoder{s: scanner.NewScanner(r)}
17 | }
18 |
19 | func New{{.Name.Name}}JSONScanDecoder(s scanner.Scanner) *{{.Name.Name}}JSONDecoder {
20 | return &{{.Name.Name}}JSONDecoder{s: s}
21 | }
22 |
23 | func (e *{{.Name.Name}}JSONDecoder) Decode(ptr **{{.Name.Name}}) error {
24 | s := e.s
25 | if tok, tokval, err := s.Scan(); err != nil {
26 | return err
27 | } else if tok == scanner.TNULL {
28 | *ptr = nil
29 | return nil
30 | } else if tok != scanner.TLBRACE {
31 | return fmt.Errorf("Unexpected %s at %d: %s; expected '{'", scanner.TokenName(tok), s.Pos(), string(tokval))
32 | }
33 |
34 | // Create the object if it doesn't exist.
35 | if *ptr == nil {
36 | *ptr = &{{.Name.Name}}{}
37 | }
38 | v := *ptr
39 |
40 | // Loop over key/value pairs.
41 | index := 0
42 | for {
43 | // Read in key.
44 | var key string
45 | tok, tokval, err := s.Scan()
46 | if err != nil {
47 | return err
48 | } else if tok == scanner.TRBRACE {
49 | return nil
50 | } else if tok == scanner.TCOMMA {
51 | if index == 0 {
52 | return fmt.Errorf("Unexpected comma at %d", s.Pos())
53 | }
54 | if tok, tokval, err = s.Scan(); err != nil {
55 | return err
56 | }
57 | }
58 |
59 | if tok != scanner.TSTRING {
60 | return fmt.Errorf("Unexpected %s at %d: %s; expected '{' or string", scanner.TokenName(tok), s.Pos(), string(tokval))
61 | } else {
62 | key = string(tokval)
63 | }
64 |
65 | // Read in the colon.
66 | if tok, tokval, err := s.Scan(); err != nil {
67 | return err
68 | } else if tok != scanner.TCOLON {
69 | return fmt.Errorf("Unexpected %s at %d: %s; expected colon", scanner.TokenName(tok), s.Pos(), string(tokval))
70 | }
71 |
72 | switch key {
73 | {{range fields .}}
74 | {{if keyname .}}
75 | case {{keyname . | printf "%q"}}:
76 | v := &v.{{fieldname .}}
77 |
78 | {{if isprimitivetype .}}
79 | {{if istype . "string"}}
80 | if err := s.ReadString(v); err != nil {
81 | return err
82 | }
83 | {{end}}
84 | {{if istype . "int"}}
85 | if err := s.ReadInt(v); err != nil {
86 | return err
87 | }
88 | {{end}}
89 | {{if istype . "int64"}}
90 | if err := s.ReadInt64(v); err != nil {
91 | return err
92 | }
93 | {{end}}
94 | {{if istype . "uint"}}
95 | if err := s.ReadUint(v); err != nil {
96 | return err
97 | }
98 | {{end}}
99 | {{if istype . "uint64"}}
100 | if err := s.ReadUint64(v); err != nil {
101 | return err
102 | }
103 | {{end}}
104 | {{if istype . "float32"}}
105 | if err := s.ReadFloat32(v); err != nil {
106 | return err
107 | }
108 | {{end}}
109 | {{if istype . "float64"}}
110 | if err := s.ReadFloat64(v); err != nil {
111 | return err
112 | }
113 | {{end}}
114 | {{if istype . "bool"}}
115 | if err := s.ReadBool(v); err != nil {
116 | return err
117 | }
118 | {{end}}
119 | {{end}}
120 | {{if istype . "*"}}
121 | if err := New{{subtype .}}JSONScanDecoder(s).Decode(v); err != nil {
122 | return err
123 | }
124 | {{end}}
125 | {{if istype . "[]"}}
126 | if err := New{{subtype .}}JSONScanDecoder(s).DecodeArray(v); err != nil {
127 | return err
128 | }
129 | {{end}}
130 | {{end}}
131 | {{end}}
132 | }
133 |
134 | index++
135 | }
136 |
137 | return nil
138 | }
139 |
140 | func (e *{{.Name.Name}}JSONDecoder) DecodeArray(ptr *[]*{{.Name.Name}}) error {
141 | s := e.s
142 | if tok, _, err := s.Scan(); err != nil {
143 | return err
144 | } else if tok != scanner.TLBRACKET {
145 | return errors.New("Expected '['")
146 | }
147 |
148 | slice := make([]*{{.Name.Name}}, 0)
149 |
150 | // Loop over items.
151 | index := 0
152 | for {
153 | tok, tokval, err := s.Scan()
154 | if err != nil {
155 | return err
156 | } else if tok == scanner.TRBRACKET {
157 | *ptr = slice
158 | return nil
159 | } else if tok == scanner.TCOMMA {
160 | if index == 0 {
161 | return fmt.Errorf("Unexpected comma in array at %d", s.Pos())
162 | }
163 | if tok, tokval, err = s.Scan(); err != nil {
164 | return err
165 | }
166 | }
167 | s.Unscan(tok, tokval)
168 |
169 | item := &{{.Name.Name}}{}
170 | if err := e.Decode(&item); err != nil {
171 | return err
172 | }
173 | slice = append(slice, item)
174 |
175 | index++
176 | }
177 | }
178 |
179 | {{end}}
180 |
--------------------------------------------------------------------------------
/generator/decoder/decoder.tmpl.go:
--------------------------------------------------------------------------------
1 | package decoder
2 |
3 | import (
4 | "bytes"
5 | "compress/gzip"
6 | "io"
7 | )
8 |
9 | // tmplsrc returns raw, uncompressed file data.
10 | func tmplsrc() []byte {
11 | gz, err := gzip.NewReader(bytes.NewBuffer([]byte{
12 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x00, 0xff, 0xbc, 0x57,
13 | 0xdf, 0x4f, 0xe3, 0x38, 0x10, 0x7e, 0x4e, 0xfe, 0x8a, 0x21, 0x12, 0x90,
14 | 0xb0, 0x28, 0x45, 0x77, 0xab, 0x7d, 0xe0, 0xd4, 0x07, 0x96, 0x63, 0x4f,
15 | 0x7b, 0xd7, 0x2d, 0x27, 0xa0, 0x4f, 0x68, 0x75, 0x72, 0x53, 0x17, 0x4c,
16 | 0x5b, 0x3b, 0x67, 0xbb, 0xdd, 0x45, 0xb9, 0xfe, 0xef, 0x37, 0x63, 0x37,
17 | 0xfd, 0x91, 0x92, 0x20, 0xa0, 0xbb, 0x2f, 0xc5, 0xb5, 0x67, 0xe6, 0xfb,
18 | 0x66, 0xbe, 0x31, 0x9e, 0xe6, 0x2c, 0x1b, 0xb1, 0x3b, 0x0e, 0x45, 0x91,
19 | 0x76, 0xd9, 0x84, 0xbb, 0x8f, 0xf9, 0x3c, 0x0c, 0xc5, 0x24, 0x57, 0xda,
20 | 0x42, 0x1c, 0x06, 0x11, 0xd7, 0x5a, 0x69, 0x13, 0xe1, 0x6a, 0x38, 0xb1,
21 | 0xf4, 0x47, 0x28, 0xfa, 0xbc, 0x13, 0xf6, 0x7e, 0xda, 0x4f, 0x33, 0x35,
22 | 0x69, 0xf5, 0xb9, 0xec, 0x3f, 0xa8, 0x7b, 0x69, 0x94, 0x6c, 0x4d, 0xf8,
23 | 0x1d, 0x7b, 0xa0, 0x85, 0xc9, 0x98, 0x94, 0x5c, 0x47, 0x61, 0x12, 0x86,
24 | 0x45, 0xa1, 0x99, 0x44, 0x14, 0xfb, 0x98, 0x73, 0x03, 0x29, 0x02, 0xd0,
25 | 0xaa, 0x02, 0xfa, 0xe7, 0xf5, 0x65, 0xf7, 0x77, 0x9e, 0xa9, 0x01, 0xd7,
26 | 0x60, 0xac, 0x9e, 0x66, 0x16, 0x8a, 0x30, 0x30, 0xb0, 0x08, 0x94, 0x5e,
27 | 0xfb, 0xbf, 0x21, 0xb2, 0x1b, 0x4e, 0x65, 0x06, 0x5d, 0xfe, 0xad, 0x36,
28 | 0x40, 0xac, 0x41, 0xa8, 0xf4, 0x8a, 0x33, 0x5c, 0x27, 0x70, 0x54, 0x0f,
29 | 0x84, 0x08, 0x9a, 0xdb, 0xa9, 0x96, 0x70, 0x50, 0x6b, 0x54, 0x98, 0xd3,
30 | 0x25, 0x09, 0x04, 0x5d, 0xf0, 0x88, 0x75, 0x32, 0x6f, 0xe6, 0x42, 0x86,
31 | 0x25, 0x9f, 0xad, 0x34, 0x76, 0xc3, 0x6a, 0xc5, 0x20, 0xe6, 0x0d, 0x01,
32 | 0x13, 0xf0, 0x8b, 0x38, 0xb7, 0x1a, 0x8e, 0x2a, 0x76, 0x09, 0x38, 0x85,
33 | 0x7d, 0xb1, 0x4f, 0xdb, 0xc0, 0x53, 0x13, 0x06, 0x62, 0x08, 0x56, 0x8d,
34 | 0x8e, 0xe9, 0x63, 0xc6, 0xc6, 0xc7, 0x64, 0x42, 0x67, 0xc6, 0xd1, 0x8f,
35 | 0x93, 0xdf, 0xdc, 0xc6, 0x5e, 0x1b, 0xa4, 0x18, 0x93, 0x63, 0x49, 0x17,
36 | 0x77, 0xc3, 0x60, 0x0e, 0x7c, 0x6c, 0x38, 0xf8, 0x10, 0xd0, 0x6e, 0x2f,
37 | 0x53, 0xbf, 0xe9, 0xf6, 0x3a, 0x1d, 0x67, 0x7e, 0x44, 0x44, 0x9c, 0xf7,
38 | 0xca, 0xd7, 0x7d, 0xd9, 0xf4, 0xdd, 0x5b, 0xf3, 0xed, 0x7c, 0xbc, 0x3a,
39 | 0x3b, 0xbf, 0x58, 0x07, 0xc3, 0x76, 0x4c, 0x2f, 0x88, 0xfa, 0x30, 0x8e,
40 | 0x7a, 0x92, 0x7f, 0xcf, 0x79, 0x66, 0xf9, 0x00, 0xf6, 0x0d, 0x30, 0x0b,
41 | 0xfb, 0x83, 0x53, 0x5c, 0x21, 0xcf, 0x72, 0xfb, 0xb0, 0x38, 0x8c, 0x8e,
42 | 0x57, 0xe1, 0xd4, 0x88, 0x4b, 0xca, 0x3f, 0x46, 0x9c, 0x04, 0xf7, 0xd3,
43 | 0xbf, 0x95, 0x89, 0x69, 0x61, 0xb5, 0x90, 0x77, 0xb1, 0xcf, 0x3b, 0x49,
44 | 0x90, 0x51, 0x18, 0x06, 0xad, 0x16, 0x9c, 0x6b, 0xce, 0x2c, 0x76, 0xf0,
45 | 0x3d, 0x07, 0xd5, 0x7f, 0xc0, 0x90, 0xc4, 0x51, 0x58, 0x18, 0x28, 0x6e,
46 | 0xe4, 0xa1, 0x45, 0x1c, 0x61, 0x6c, 0xea, 0x0a, 0xe7, 0x93, 0x5b, 0xd5,
47 | 0x66, 0x91, 0x6c, 0x45, 0xca, 0x62, 0x4e, 0xb1, 0x83, 0x19, 0x95, 0x95,
48 | 0x2c, 0x3c, 0x4c, 0x47, 0xa9, 0x1c, 0xd4, 0x0c, 0x7b, 0x60, 0xc4, 0x1f,
49 | 0x5b, 0x48, 0x61, 0xca, 0x21, 0x67, 0x42, 0x1b, 0x0a, 0x2d, 0x07, 0xfc,
50 | 0x3b, 0x99, 0x9f, 0x84, 0xc1, 0xd0, 0x0b, 0x46, 0x2e, 0xd4, 0xe5, 0x20,
51 | 0x24, 0x39, 0xa0, 0x51, 0x30, 0x63, 0xce, 0x77, 0x91, 0x08, 0x6e, 0x34,
52 | 0xe9, 0x88, 0xc7, 0x48, 0xb8, 0xa2, 0xe5, 0x86, 0x98, 0x0d, 0x6a, 0x5e,
53 | 0xad, 0x14, 0xd9, 0xd0, 0xb0, 0xc1, 0xe5, 0xfc, 0xf2, 0xcb, 0x97, 0x33,
54 | 0xef, 0x41, 0xe5, 0x73, 0x09, 0xe1, 0xf9, 0x89, 0xdf, 0x7a, 0x46, 0x58,
55 | 0xfc, 0x57, 0x33, 0x61, 0x5e, 0xdb, 0x68, 0xa9, 0x18, 0xa5, 0x40, 0x75,
56 | 0x0c, 0x9e, 0x6c, 0xd9, 0x86, 0x8e, 0xad, 0xa4, 0xe9, 0x62, 0x90, 0xd6,
57 | 0xc1, 0x13, 0xbd, 0x77, 0x7d, 0x73, 0xf5, 0xb9, 0xfb, 0xc7, 0x46, 0xa6,
58 | 0x2f, 0x6e, 0x3e, 0x50, 0x7a, 0xa1, 0xc9, 0xab, 0xda, 0xb0, 0x2c, 0xaa,
59 | 0xe3, 0x40, 0xfa, 0xb6, 0x2b, 0x36, 0x25, 0xfd, 0xb5, 0x8e, 0xa0, 0x66,
60 | 0xcd, 0xd4, 0x58, 0xc9, 0x34, 0x7c, 0xba, 0x3c, 0x4d, 0x37, 0xba, 0xa9,
61 | 0x0b, 0xf6, 0x36, 0x24, 0xed, 0x5c, 0x76, 0xdf, 0x50, 0x1a, 0x47, 0xf0,
62 | 0x95, 0x25, 0xa1, 0x7c, 0xcd, 0x37, 0x61, 0xb3, 0x7b, 0xd7, 0xf2, 0x44,
63 | 0xa2, 0x7c, 0x6a, 0x86, 0x82, 0x8f, 0x07, 0xfe, 0xad, 0x09, 0x68, 0x17,
64 | 0x99, 0xa3, 0x89, 0xc4, 0xb8, 0xe5, 0x5e, 0xc6, 0xa8, 0x9c, 0xc5, 0x72,
65 | 0x17, 0xfe, 0x83, 0x1c, 0x11, 0xec, 0x10, 0xa2, 0xfd, 0x7f, 0xa3, 0xf9,
66 | 0xfc, 0xd4, 0x35, 0x89, 0xbb, 0x9f, 0x07, 0xb3, 0xb4, 0x28, 0x5c, 0xc4,
67 | 0x65, 0x00, 0x77, 0xe8, 0xc2, 0x0a, 0x83, 0x6e, 0x13, 0x61, 0xc5, 0x8c,
68 | 0xbb, 0x57, 0x6d, 0x11, 0x7e, 0x79, 0xea, 0x37, 0x21, 0x5a, 0x88, 0x5f,
69 | 0x9e, 0x96, 0x17, 0xcf, 0x69, 0x40, 0x8a, 0x5d, 0xfb, 0xfc, 0x66, 0x4f,
70 | 0xb5, 0xea, 0x56, 0xb7, 0x96, 0x4d, 0x4f, 0x28, 0x5c, 0x0e, 0x6a, 0x20,
71 | 0x31, 0x9b, 0x7a, 0xbc, 0xcf, 0xd2, 0xee, 0x1a, 0xec, 0xc3, 0xfb, 0x46,
72 | 0xb8, 0x0f, 0xef, 0x77, 0x0a, 0x38, 0x6d, 0x4c, 0xaf, 0x27, 0x76, 0x9c,
73 | 0xdf, 0xf4, 0x99, 0x04, 0x7b, 0x62, 0xe7, 0x19, 0x0e, 0xc7, 0x8a, 0xd9,
74 | 0x5f, 0x7f, 0xa9, 0xc7, 0xfc, 0xe4, 0x0d, 0x76, 0x0f, 0xda, 0x94, 0xe8,
75 | 0x27, 0x6f, 0xb0, 0x53, 0xd0, 0xbe, 0x52, 0xe3, 0x7a, 0xc4, 0x8f, 0x78,
76 | 0xfa, 0x26, 0xb8, 0xcd, 0xf5, 0x06, 0xf2, 0xd1, 0x12, 0x76, 0x85, 0xea,
77 | 0xc6, 0x39, 0x33, 0xed, 0x97, 0xf7, 0x79, 0x6b, 0x9a, 0x4b, 0xd2, 0xc5,
78 | 0x40, 0x55, 0x43, 0xaa, 0xca, 0xe9, 0x59, 0x16, 0xb7, 0x5f, 0xdf, 0x42,
79 | 0xe3, 0x4c, 0x6b, 0xf6, 0xf8, 0x6a, 0x2e, 0xab, 0xe5, 0x6a, 0xe5, 0x5f,
80 | 0x42, 0x7a, 0x9f, 0xdf, 0xbd, 0xf3, 0x33, 0xd0, 0xda, 0x0b, 0xff, 0xa2,
81 | 0x99, 0xd3, 0x73, 0x73, 0x83, 0xe7, 0xed, 0xd7, 0x97, 0x8c, 0x9e, 0xff,
82 | 0xbc, 0x65, 0xea, 0xdc, 0x9a, 0x1c, 0xff, 0xba, 0xb8, 0xa9, 0xb8, 0xe0,
83 | 0x8f, 0x1a, 0x1a, 0xe7, 0xe3, 0xe8, 0x62, 0xf9, 0x4a, 0xdf, 0x1e, 0x46,
84 | 0x8b, 0x89, 0xcf, 0x8c, 0x45, 0xc6, 0x09, 0x7b, 0xc2, 0x46, 0x3c, 0xde,
85 | 0x22, 0x7e, 0x0c, 0x27, 0x49, 0x75, 0x60, 0x13, 0x96, 0x4f, 0xea, 0xc6,
86 | 0xb4, 0x1f, 0x3b, 0x83, 0x95, 0xb9, 0x95, 0x93, 0xa6, 0x23, 0xff, 0x73,
87 | 0xa7, 0x32, 0x9c, 0x36, 0x18, 0x09, 0xfd, 0x83, 0xc7, 0x33, 0x7c, 0xee,
88 | 0xd3, 0x9e, 0x24, 0xe2, 0xf1, 0x5a, 0xb0, 0xc4, 0x35, 0x2b, 0x96, 0xdf,
89 | 0xbd, 0xd5, 0xdb, 0x63, 0xf6, 0xda, 0x95, 0xe2, 0xe5, 0xc5, 0x3d, 0x20,
90 | 0xfb, 0x67, 0xa7, 0x1e, 0x02, 0x74, 0x9d, 0xd0, 0x06, 0x96, 0xe7, 0x78,
91 | 0x39, 0x62, 0xf7, 0xf5, 0xd8, 0x89, 0x9d, 0x54, 0xee, 0xc8, 0x9c, 0x7e,
92 | 0xe8, 0xfa, 0x0b, 0xf4, 0x7f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xda, 0xa5,
93 | 0x93, 0x69, 0x57, 0x0f, 0x00, 0x00,
94 | }))
95 |
96 | if err != nil {
97 | panic("Decompression failed: " + err.Error())
98 | }
99 |
100 | var b bytes.Buffer
101 | io.Copy(&b, gz)
102 | gz.Close()
103 |
104 | return b.Bytes()
105 | }
106 |
--------------------------------------------------------------------------------
/generator/decoder/generator.go:
--------------------------------------------------------------------------------
1 | package decoder
2 |
3 | import (
4 | "bytes"
5 | "go/ast"
6 | "go/format"
7 | "io"
8 | )
9 |
10 | // Generator writes a generated JSON decoder to a writer.
11 | type Generator interface {
12 | Generate(io.Writer, *ast.File) error
13 | }
14 |
15 | type generator struct{
16 | }
17 |
18 | // NewGenerator creates a new Generator instance.
19 | func NewGenerator() Generator {
20 | return &generator{}
21 | }
22 |
23 | // Generator writes the generated decoder to the writer.
24 | func (g *generator) Generate(w io.Writer, f *ast.File) error {
25 | // Ignore files without type specs.
26 | if len(types(f)) == 0 {
27 | return nil
28 | }
29 |
30 | // Generate code and the format the source code.
31 | var buf bytes.Buffer
32 | if err := tmpl.Execute(&buf, f); err != nil {
33 | return err
34 | }
35 | b, err := format.Source(buf.Bytes())
36 | if err != nil {
37 | return err
38 | }
39 |
40 | _, err = w.Write(b)
41 | return err
42 | }
43 |
--------------------------------------------------------------------------------
/generator/decoder/generator_test.go:
--------------------------------------------------------------------------------
1 | package decoder
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "go/ast"
7 | "go/parser"
8 | "go/token"
9 | "os"
10 | "os/exec"
11 | "path/filepath"
12 | "testing"
13 |
14 | "github.com/benbjohnson/megajson/generator/test"
15 | "github.com/stretchr/testify/assert"
16 | )
17 |
18 | // Ensures a basic sanity check when generating the decoder.
19 | func TestWriteTypeGenerator(t *testing.T) {
20 | src := `
21 | package foo
22 | type Foo struct {
23 | Name string
24 | Age int
25 | }
26 | `
27 | f, _ := parser.ParseFile(token.NewFileSet(), "foo.go", src, 0)
28 | err := NewGenerator().Generate(bytes.NewBufferString(src), f)
29 | assert.NoError(t, err)
30 | }
31 |
32 | // Ensures that a simple struct can be decoded from JSON.
33 | func TestGenerateSimple(t *testing.T) {
34 | out, err := execute("simple")
35 | assert.NoError(t, err)
36 | assert.Equal(t, out, `|foo|200|189273|2392|172389984|182.23|19380.1312|true|`)
37 | }
38 |
39 | // Ensures that a complex nested struct can be decoded from JSON.
40 | func TestGenerateDecodeNested(t *testing.T) {
41 | out, err := execute("nested")
42 | assert.NoError(t, err)
43 | assert.Equal(t, out, `|foo|John|20||2|Jane|60|Jack|-13|`)
44 | }
45 |
46 | // execute generates a decoder against a fixture, executes the main prorgam, and returns the results.
47 | func execute(name string) (ret string, err error) {
48 | test.Test(name, func(path string) {
49 | var file *ast.File
50 | file, err = parser.ParseFile(token.NewFileSet(), filepath.Join(path, "types.go"), nil, 0)
51 | if err != nil {
52 | return
53 | }
54 |
55 | // Generate decoder.
56 | f, _ := os.Create(filepath.Join(path, "decoder.go"))
57 | if err = NewGenerator().Generate(f, file); err != nil {
58 | fmt.Println("generate error:", err.Error())
59 | return
60 | }
61 | f.Close()
62 |
63 | // Execute fixture.
64 | out, _ := exec.Command("go", "run", filepath.Join(path, "decode.go"), filepath.Join(path, "decoder.go"), filepath.Join(path, "types.go")).CombinedOutput()
65 | ret = string(out)
66 | })
67 | return
68 | }
69 |
--------------------------------------------------------------------------------
/generator/decoder/template.go:
--------------------------------------------------------------------------------
1 | package decoder
2 |
3 | import (
4 | "go/ast"
5 | "reflect"
6 | "strings"
7 | "text/template"
8 | )
9 |
10 | var tmpl *template.Template
11 |
12 | func init() {
13 | tmpl = template.Must(template.New("decoder.tmpl").Funcs(template.FuncMap{
14 | "types": types,
15 | "fields": fields,
16 | "istype": istype,
17 | "isprimitivetype": isprimitivetype,
18 | "subtype": subtype,
19 | "fieldname": fieldname,
20 | "keyname": keyname,
21 | }).Parse(string(tmplsrc())))
22 | }
23 |
24 | // types retrieves all a list of all available struct type specs in a file.
25 | func types(f *ast.File) []*ast.TypeSpec {
26 | s := make([]*ast.TypeSpec, 0)
27 | for _, decl := range f.Decls {
28 | if decl, ok := decl.(*ast.GenDecl); ok {
29 | for _, spec := range decl.Specs {
30 | if spec, ok := spec.(*ast.TypeSpec); ok {
31 | if _, ok := spec.Type.(*ast.StructType); ok {
32 | s = append(s, spec)
33 | }
34 | }
35 | }
36 | }
37 | }
38 | return s
39 | }
40 |
41 | // fields retrieves all fields from a struct type spec.
42 | func fields(spec *ast.TypeSpec) []*ast.Field {
43 | s := make([]*ast.Field, 0)
44 | if structType, ok := spec.Type.(*ast.StructType); ok {
45 | for _, field := range structType.Fields.List {
46 | if keyname(field) != "" {
47 | s = append(s, field)
48 | }
49 | }
50 | }
51 | return s
52 | }
53 |
54 | // getType returns the name of the type of the field.
55 | func getType(field *ast.Field) string {
56 | if ident, ok := field.Type.(*ast.Ident); ok {
57 | return ident.Name
58 | } else if _, ok := field.Type.(*ast.StarExpr); ok {
59 | return "*"
60 | } else if _, ok := field.Type.(*ast.ArrayType); ok {
61 | return "[]"
62 | }
63 | return ""
64 | }
65 |
66 | // istype returns true if the field is a given type.
67 | func istype(field *ast.Field, typ string) bool {
68 | return getType(field) == typ
69 | }
70 |
71 | // isprimitivetype returns true if the field is a primitive type.
72 | func isprimitivetype(field *ast.Field) bool {
73 | switch getType(field) {
74 | case "string", "int", "int64", "uint", "uint64", "float32", "float64", "bool":
75 | return true
76 | }
77 | return false
78 | }
79 |
80 | // subtype returns the subtype of a pointer or array.
81 | func subtype(field *ast.Field) string {
82 | if typ, ok := field.Type.(*ast.StarExpr); ok {
83 | if ident, ok := typ.X.(*ast.Ident); ok {
84 | return ident.Name
85 | }
86 | } else if typ, ok := field.Type.(*ast.ArrayType); ok {
87 | if typ, ok := typ.Elt.(*ast.StarExpr); ok {
88 | if ident, ok := typ.X.(*ast.Ident); ok {
89 | return ident.Name
90 | }
91 | }
92 | if ident, ok := typ.Elt.(*ast.Ident); ok {
93 | return ident.Name
94 | }
95 | }
96 | return ""
97 | }
98 |
99 | // fieldname returns the first name in a field.
100 | func fieldname(field *ast.Field) string {
101 | return field.Names[0].Name
102 | }
103 |
104 | // keyname returns the JSON key to be used for a field.
105 | func keyname(field *ast.Field) string {
106 | tags := tags(field)
107 |
108 | if len(tags) > 0 {
109 | if len(tags[0]) == 0 {
110 | return fieldname(field)
111 | } else if tags[0] == "-" {
112 | return ""
113 | } else {
114 | return tags[0]
115 | }
116 | } else {
117 | return fieldname(field)
118 | }
119 | }
120 |
121 | // tags returns the JSON tags on a field.
122 | func tags(field *ast.Field) []string {
123 | var tag string
124 | if field.Tag != nil {
125 | tag = field.Tag.Value[1 : len(field.Tag.Value)-1]
126 | tag = reflect.StructTag(tag).Get("json")
127 | }
128 | return strings.Split(tag, ",")
129 | }
130 |
--------------------------------------------------------------------------------
/generator/encoder/encoder.tmpl:
--------------------------------------------------------------------------------
1 | package {{.Name.Name}}
2 |
3 | import (
4 | "io"
5 | "github.com/benbjohnson/megajson/writer"
6 | )
7 |
8 | {{range types .}}
9 | type {{.Name.Name}}JSONEncoder struct {
10 | w *writer.Writer
11 | }
12 |
13 | func New{{.Name.Name}}JSONEncoder(w io.Writer) *{{.Name.Name}}JSONEncoder {
14 | return &{{.Name.Name}}JSONEncoder{w: writer.NewWriter(w)}
15 | }
16 |
17 | func New{{.Name.Name}}JSONRawEncoder(w *writer.Writer) *{{.Name.Name}}JSONEncoder {
18 | return &{{.Name.Name}}JSONEncoder{w: w}
19 | }
20 |
21 | func (e *{{.Name.Name}}JSONEncoder) Encode(v *{{.Name.Name}}) error {
22 | if err := e.RawEncode(v); err != nil {
23 | return err
24 | }
25 | if err := e.w.Flush(); err != nil {
26 | return err
27 | }
28 | return nil
29 | }
30 |
31 | func (e *{{.Name.Name}}JSONEncoder) RawEncode(v *{{.Name.Name}}) error {
32 | if v == nil {
33 | return e.w.WriteNull()
34 | }
35 |
36 | if err := e.w.WriteByte('{'); err != nil {
37 | return err
38 | }
39 |
40 | {{range $index, $field := fields .}}
41 | {{if $index}}
42 | if err := e.w.WriteByte(','); err != nil {
43 | return err
44 | }
45 | {{end}}
46 |
47 | // Write key and colon.
48 | if err := e.w.WriteString({{keyname . | printf "%q"}}); err != nil {
49 | return err
50 | }
51 | if err := e.w.WriteByte(':'); err != nil {
52 | return err
53 | }
54 |
55 | // Write value.
56 | {
57 | v := v.{{fieldname .}}
58 |
59 | {{if isprimitivetype .}}
60 | {{if istype . "string"}}
61 | if err := e.w.WriteString(v); err != nil {
62 | return err
63 | }
64 | {{end}}
65 | {{if istype . "int"}}
66 | if err := e.w.WriteInt(v); err != nil {
67 | return err
68 | }
69 | {{end}}
70 | {{if istype . "int64"}}
71 | if err := e.w.WriteInt64(v); err != nil {
72 | return err
73 | }
74 | {{end}}
75 | {{if istype . "uint"}}
76 | if err := e.w.WriteUint(v); err != nil {
77 | return err
78 | }
79 | {{end}}
80 | {{if istype . "uint64"}}
81 | if err := e.w.WriteUint64(v); err != nil {
82 | return err
83 | }
84 | {{end}}
85 | {{if istype . "float32"}}
86 | if err := e.w.WriteFloat32(v); err != nil {
87 | return err
88 | }
89 | {{end}}
90 | {{if istype . "float64"}}
91 | if err := e.w.WriteFloat64(v); err != nil {
92 | return err
93 | }
94 | {{end}}
95 | {{if istype . "bool"}}
96 | if err := e.w.WriteBool(v); err != nil {
97 | return err
98 | }
99 | {{end}}
100 | {{end}}
101 | {{if istype . "*"}}
102 | if err := New{{subtype .}}JSONRawEncoder(e.w).RawEncode(v); err != nil {
103 | return err
104 | }
105 | {{end}}
106 | {{if istype . "[]"}}
107 | if err := e.w.WriteByte('['); err != nil {
108 | return err
109 | }
110 |
111 | for index, v := range v {
112 | if index > 0 {
113 | if err := e.w.WriteByte(','); err != nil {
114 | return err
115 | }
116 | }
117 | if err := New{{subtype .}}JSONRawEncoder(e.w).RawEncode(v); err != nil {
118 | return err
119 | }
120 | }
121 |
122 | if err := e.w.WriteByte(']'); err != nil {
123 | return err
124 | }
125 | {{end}}
126 | }
127 | {{end}}
128 |
129 | if err := e.w.WriteByte('}'); err != nil {
130 | return err
131 | }
132 | return nil
133 | }
134 | {{end}}
135 |
--------------------------------------------------------------------------------
/generator/encoder/encoder.tmpl.go:
--------------------------------------------------------------------------------
1 | package encoder
2 |
3 | import (
4 | "bytes"
5 | "compress/gzip"
6 | "io"
7 | )
8 |
9 | // tmplsrc returns raw, uncompressed file data.
10 | func tmplsrc() []byte {
11 | gz, err := gzip.NewReader(bytes.NewBuffer([]byte{
12 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x00, 0xff, 0xac, 0x96,
13 | 0xcf, 0x6f, 0x9b, 0x30, 0x14, 0xc7, 0xcf, 0xf8, 0xaf, 0x78, 0x43, 0xdd,
14 | 0x0a, 0x55, 0x45, 0xa6, 0xad, 0xea, 0xa1, 0x53, 0x77, 0xa8, 0xb4, 0x48,
15 | 0xdb, 0x21, 0x93, 0x56, 0x55, 0x3b, 0x54, 0x3d, 0x10, 0x62, 0x12, 0xb7,
16 | 0xc4, 0xce, 0xc0, 0xc0, 0x22, 0x8f, 0xff, 0x7d, 0xfe, 0x01, 0x49, 0xf8,
17 | 0xdd, 0x4d, 0x5c, 0x82, 0xb1, 0x9f, 0xdf, 0xe7, 0xfb, 0xbe, 0xc0, 0x73,
18 | 0x76, 0x7e, 0xf0, 0xe2, 0xaf, 0x31, 0x08, 0xe1, 0x2d, 0xfc, 0x2d, 0xd6,
19 | 0x3f, 0x45, 0x81, 0x10, 0xd9, 0xee, 0x58, 0xcc, 0xc1, 0x41, 0x96, 0x4d,
20 | 0x98, 0x2d, 0x7f, 0xd7, 0x84, 0x6f, 0xd2, 0xa5, 0x17, 0xb0, 0xed, 0x6c,
21 | 0x89, 0xe9, 0xf2, 0x99, 0x6d, 0x68, 0xc2, 0xe8, 0x6c, 0x8b, 0xd7, 0xfe,
22 | 0xb3, 0x1a, 0xe4, 0x31, 0xe1, 0x38, 0xb6, 0x91, 0x8b, 0x90, 0x10, 0xb1,
23 | 0x4f, 0x65, 0x4e, 0xbe, 0xdf, 0xe1, 0x04, 0x3c, 0x99, 0x4e, 0x8d, 0x1a,
24 | 0x88, 0x6f, 0xf7, 0xdf, 0x17, 0x5f, 0x68, 0xc0, 0x56, 0x38, 0x86, 0x84,
25 | 0xc7, 0x69, 0xc0, 0x41, 0x20, 0x2b, 0x87, 0x0b, 0x93, 0xc8, 0xfb, 0xa9,
26 | 0x2f, 0x48, 0x4a, 0x09, 0x53, 0x1a, 0xc0, 0x02, 0xe7, 0xbd, 0xfb, 0x9d,
27 | 0x1c, 0x08, 0x2b, 0x37, 0xb8, 0x70, 0xd1, 0xcf, 0x91, 0x80, 0x18, 0xf3,
28 | 0x34, 0xa6, 0xf0, 0xae, 0x37, 0x48, 0xe4, 0x37, 0x50, 0x4a, 0x90, 0x4c,
29 | 0x93, 0xd4, 0xc9, 0xdd, 0x62, 0x58, 0xc9, 0x0f, 0x3f, 0x3f, 0x8a, 0xa9,
30 | 0x97, 0x30, 0x8d, 0xa2, 0x23, 0xdf, 0xc1, 0x03, 0x09, 0x5d, 0x30, 0x03,
31 | 0x27, 0x6b, 0x06, 0xb9, 0x80, 0xe3, 0x98, 0x69, 0x22, 0x09, 0xd5, 0x18,
32 | 0x6e, 0x6e, 0x01, 0x7b, 0x07, 0xe1, 0x4e, 0xe6, 0x7e, 0xd2, 0xd3, 0x6f,
33 | 0x6e, 0x81, 0x92, 0x48, 0xc5, 0x55, 0xd2, 0xe4, 0x2c, 0xb2, 0x8a, 0xfa,
34 | 0xbe, 0xdc, 0x9b, 0x47, 0x69, 0xb2, 0x71, 0x46, 0x37, 0x95, 0xb7, 0x72,
35 | 0xf5, 0xb5, 0x15, 0x9c, 0x48, 0x1a, 0x2e, 0x22, 0x83, 0xdb, 0x36, 0x56,
36 | 0x2a, 0xd3, 0xbe, 0x2f, 0xd2, 0x28, 0x72, 0x5c, 0x25, 0xa1, 0x29, 0x5c,
37 | 0x2f, 0xdf, 0xed, 0x39, 0x76, 0xce, 0xc5, 0xf9, 0x98, 0x7e, 0x64, 0x55,
38 | 0x6f, 0xf3, 0x19, 0xa1, 0x2b, 0xfc, 0xfb, 0x12, 0xce, 0x42, 0x82, 0xa3,
39 | 0x95, 0x4a, 0xa6, 0x07, 0xe6, 0x05, 0xb7, 0x64, 0x98, 0xa4, 0x98, 0x18,
40 | 0x7d, 0xdf, 0x0f, 0xbd, 0xec, 0x80, 0xd6, 0xb1, 0x96, 0x65, 0x32, 0x62,
41 | 0xba, 0x52, 0xdf, 0xa2, 0x65, 0xcd, 0x66, 0xa0, 0x13, 0xc0, 0x0b, 0xde,
42 | 0x83, 0x4f, 0x57, 0x10, 0xb0, 0x88, 0x51, 0x0f, 0x75, 0x52, 0xee, 0x79,
43 | 0x4c, 0xe8, 0xda, 0x11, 0x42, 0x06, 0x53, 0x69, 0x1b, 0x78, 0xf0, 0x07,
44 | 0x76, 0x72, 0x8e, 0x87, 0x60, 0xbf, 0xfd, 0x65, 0x4b, 0x1b, 0xdb, 0xfc,
45 | 0x1a, 0xbe, 0xe8, 0x4e, 0x6c, 0xe4, 0xdf, 0x74, 0xc9, 0x6f, 0x6c, 0x3f,
46 | 0x95, 0x9c, 0xf9, 0x51, 0x8a, 0x95, 0x54, 0x1d, 0x98, 0xa9, 0x8c, 0x99,
47 | 0x27, 0x84, 0x36, 0xcf, 0xc8, 0x33, 0x35, 0x1a, 0x07, 0x49, 0x22, 0x95,
48 | 0x6e, 0x09, 0x27, 0x19, 0xd6, 0x4d, 0xc3, 0x98, 0x7b, 0x58, 0x34, 0x73,
49 | 0x60, 0x27, 0xba, 0x46, 0xbb, 0x5c, 0x1c, 0x70, 0xa1, 0xe3, 0xad, 0x6e,
50 | 0xbb, 0x6d, 0x2a, 0x3e, 0x3a, 0xde, 0x01, 0x94, 0xee, 0x0d, 0xd1, 0xbe,
51 | 0x52, 0x3e, 0x25, 0xea, 0xfa, 0x6a, 0x04, 0x76, 0x7d, 0x35, 0x19, 0x2e,
52 | 0x1d, 0x29, 0xed, 0x81, 0x4c, 0x58, 0x5b, 0x3a, 0x5a, 0xdc, 0x03, 0x99,
53 | 0xb4, 0xba, 0x30, 0x62, 0x3e, 0xff, 0xf8, 0x61, 0x88, 0x38, 0x37, 0x21,
54 | 0xd3, 0x22, 0x87, 0x8b, 0x9c, 0x9b, 0x90, 0xc9, 0x90, 0x4b, 0xc6, 0xa2,
55 | 0x21, 0xde, 0x9d, 0x5c, 0xff, 0x6f, 0x58, 0x6d, 0x58, 0xc3, 0x5e, 0x54,
56 | 0xcc, 0x23, 0x52, 0x9f, 0x8e, 0x49, 0xba, 0xac, 0x3e, 0xde, 0xc6, 0xe1,
57 | 0x28, 0x15, 0xb9, 0xc3, 0x67, 0x4e, 0x5b, 0xd1, 0x88, 0x88, 0xc7, 0xa7,
58 | 0xb6, 0x8a, 0x46, 0xcb, 0x7a, 0xec, 0xec, 0xb8, 0x6d, 0x8e, 0xbe, 0x84,
59 | 0xf2, 0x7c, 0x29, 0x5b, 0xbd, 0x6e, 0x56, 0xa6, 0xfb, 0x67, 0xd5, 0x26,
60 | 0xc5, 0x56, 0xab, 0xf0, 0x19, 0xde, 0x1f, 0x3c, 0xfc, 0xc7, 0x5e, 0xdf,
61 | 0xe1, 0x7a, 0x69, 0xbb, 0xd5, 0x7a, 0x86, 0x53, 0x18, 0xda, 0xf3, 0x8c,
62 | 0xcb, 0x82, 0x7b, 0xc5, 0x3f, 0xbd, 0xd2, 0xb6, 0xda, 0xe3, 0x29, 0xd0,
63 | 0xc9, 0xc1, 0xd5, 0x9b, 0xba, 0x18, 0x3d, 0x78, 0xeb, 0x7f, 0x1c, 0xaa,
64 | 0x94, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0xbe, 0x5e, 0x00, 0x61, 0xaf,
65 | 0x0a, 0x00, 0x00,
66 | }))
67 |
68 | if err != nil {
69 | panic("Decompression failed: " + err.Error())
70 | }
71 |
72 | var b bytes.Buffer
73 | io.Copy(&b, gz)
74 | gz.Close()
75 |
76 | return b.Bytes()
77 | }
78 |
--------------------------------------------------------------------------------
/generator/encoder/generator.go:
--------------------------------------------------------------------------------
1 | package encoder
2 |
3 | import (
4 | "bytes"
5 | "go/ast"
6 | "go/format"
7 | "io"
8 | )
9 |
10 | // Generator writes a generated JSON decoder to a writer.
11 | type Generator interface {
12 | Generate(io.Writer, *ast.File) error
13 | }
14 |
15 | type generator struct{
16 | }
17 |
18 | // NewGenerator creates a new Generator instance.
19 | func NewGenerator() Generator {
20 | return &generator{}
21 | }
22 |
23 | // Generator writes the generated decoder to the writer.
24 | func (g *generator) Generate(w io.Writer, f *ast.File) error {
25 | // Ignore files without type specs.
26 | if len(types(f)) == 0 {
27 | return nil
28 | }
29 |
30 | // Generate code and the format the source code.
31 | var buf bytes.Buffer
32 | if err := tmpl.Execute(&buf, f); err != nil {
33 | return err
34 | }
35 | b, err := format.Source(buf.Bytes())
36 | if err != nil {
37 | return err
38 | }
39 |
40 | _, err = w.Write(b)
41 | return err
42 | }
43 |
--------------------------------------------------------------------------------
/generator/encoder/generator_test.go:
--------------------------------------------------------------------------------
1 | package encoder
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "go/ast"
7 | "go/parser"
8 | "go/token"
9 | "os"
10 | "os/exec"
11 | "path/filepath"
12 | "testing"
13 |
14 | "github.com/benbjohnson/megajson/generator/test"
15 | "github.com/stretchr/testify/assert"
16 | )
17 |
18 | // Ensures a basic sanity check when generating the encoder.
19 | func TestWriteTypeEncoder(t *testing.T) {
20 | src := `
21 | package foo
22 | type Foo struct {
23 | Name string
24 | Age int
25 | }
26 | `
27 | f, _ := parser.ParseFile(token.NewFileSet(), "foo.go", src, 0)
28 | err := NewGenerator().Generate(bytes.NewBufferString(src), f)
29 | assert.NoError(t, err)
30 | }
31 |
32 | // Ensures that a simple struct can be encoded to JSON.
33 | func TestGenerateEncodeSimple(t *testing.T) {
34 | out, err := execute("simple")
35 | assert.NoError(t, err)
36 | assert.Equal(t, out, `{"StringX":"foo","IntX":200,"Int64X":189273,"myuint":2392,"Uint64X":172389984,"Float32X":182.23,"Float64X":19380.1312,"BoolX":true}`)
37 | }
38 |
39 | // Ensures that a nested struct can be encoded to JSON.
40 | func TestGenerateEncodeNested(t *testing.T) {
41 | out, err := execute("nested")
42 | assert.NoError(t, err)
43 | assert.Equal(t, out, `{"StringX":"foo","BX":{"Name":"John","Age":20},"BY":null,"Bn":[{"Name":"Jane","Age":60}],"Bn2":[]}`)
44 | }
45 |
46 | // execute generates an encoder against a fixture, executes the main prorgam, and returns the results.
47 | func execute(name string) (ret string, err error) {
48 | test.Test(name, func(path string) {
49 | var file *ast.File
50 | file, err = parser.ParseFile(token.NewFileSet(), filepath.Join(path, "types.go"), nil, 0)
51 | if err != nil {
52 | return
53 | }
54 |
55 | // Generate decoder.
56 | f, _ := os.Create(filepath.Join(path, "encoder.go"))
57 | if err = NewGenerator().Generate(f, file); err != nil {
58 | fmt.Println("generate error:", err.Error())
59 | return
60 | }
61 | f.Close()
62 |
63 | // Execute fixture.
64 | out, _ := exec.Command("go", "run", filepath.Join(path, "encode.go"), filepath.Join(path, "encoder.go"), filepath.Join(path, "types.go")).CombinedOutput()
65 | ret = string(out)
66 | })
67 | return
68 | }
69 |
--------------------------------------------------------------------------------
/generator/encoder/template.go:
--------------------------------------------------------------------------------
1 | package encoder
2 |
3 | import (
4 | "go/ast"
5 | "reflect"
6 | "strings"
7 | "text/template"
8 | )
9 |
10 | var tmpl *template.Template
11 |
12 | func init() {
13 | tmpl = template.Must(template.New("decoder.tmpl").Funcs(template.FuncMap{
14 | "types": types,
15 | "fields": fields,
16 | "istype": istype,
17 | "isprimitivetype": isprimitivetype,
18 | "subtype": subtype,
19 | "fieldname": fieldname,
20 | "keyname": keyname,
21 | }).Parse(string(tmplsrc())))
22 | }
23 |
24 | // types retrieves all a list of all available struct type specs in a file.
25 | func types(f *ast.File) []*ast.TypeSpec {
26 | s := make([]*ast.TypeSpec, 0)
27 | for _, decl := range f.Decls {
28 | if decl, ok := decl.(*ast.GenDecl); ok {
29 | for _, spec := range decl.Specs {
30 | if spec, ok := spec.(*ast.TypeSpec); ok {
31 | if _, ok := spec.Type.(*ast.StructType); ok {
32 | s = append(s, spec)
33 | }
34 | }
35 | }
36 | }
37 | }
38 | return s
39 | }
40 |
41 | // fields retrieves all fields from a struct type spec.
42 | func fields(spec *ast.TypeSpec) []*ast.Field {
43 | s := make([]*ast.Field, 0)
44 | if structType, ok := spec.Type.(*ast.StructType); ok {
45 | for _, field := range structType.Fields.List {
46 | if keyname(field) != "" {
47 | s = append(s, field)
48 | }
49 | }
50 | }
51 | return s
52 | }
53 |
54 | // getType returns the name of the type of the field.
55 | func getType(field *ast.Field) string {
56 | if ident, ok := field.Type.(*ast.Ident); ok {
57 | return ident.Name
58 | } else if _, ok := field.Type.(*ast.StarExpr); ok {
59 | return "*"
60 | } else if _, ok := field.Type.(*ast.ArrayType); ok {
61 | return "[]"
62 | }
63 | return ""
64 | }
65 |
66 | // istype returns true if the field is a given type.
67 | func istype(field *ast.Field, typ string) bool {
68 | return getType(field) == typ
69 | }
70 |
71 | // isprimitivetype returns true if the field is a primitive type.
72 | func isprimitivetype(field *ast.Field) bool {
73 | switch getType(field) {
74 | case "string", "int", "int64", "uint", "uint64", "float32", "float64", "bool":
75 | return true
76 | }
77 | return false
78 | }
79 |
80 | // subtype returns the subtype of a pointer or array.
81 | func subtype(field *ast.Field) string {
82 | if typ, ok := field.Type.(*ast.StarExpr); ok {
83 | if ident, ok := typ.X.(*ast.Ident); ok {
84 | return ident.Name
85 | }
86 | } else if typ, ok := field.Type.(*ast.ArrayType); ok {
87 | if typ, ok := typ.Elt.(*ast.StarExpr); ok {
88 | if ident, ok := typ.X.(*ast.Ident); ok {
89 | return ident.Name
90 | }
91 | }
92 | if ident, ok := typ.Elt.(*ast.Ident); ok {
93 | return ident.Name
94 | }
95 | }
96 | return ""
97 | }
98 |
99 | // fieldname returns the first name in a field.
100 | func fieldname(field *ast.Field) string {
101 | return field.Names[0].Name
102 | }
103 |
104 | // keyname returns the JSON key to be used for a field.
105 | func keyname(field *ast.Field) string {
106 | tags := tags(field)
107 |
108 | if len(tags) > 0 {
109 | if len(tags[0]) == 0 {
110 | return fieldname(field)
111 | } else if tags[0] == "-" {
112 | return ""
113 | } else {
114 | return tags[0]
115 | }
116 | } else {
117 | return fieldname(field)
118 | }
119 | }
120 |
121 | // tags returns the JSON tags on a field.
122 | func tags(field *ast.Field) []string {
123 | var tag string
124 | if field.Tag != nil {
125 | tag = field.Tag.Value[1 : len(field.Tag.Value)-1]
126 | tag = reflect.StructTag(tag).Get("json")
127 | }
128 | return strings.Split(tag, ",")
129 | }
130 |
--------------------------------------------------------------------------------
/generator/generator.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "go/ast"
7 | "go/parser"
8 | "go/token"
9 | "io/ioutil"
10 | "os"
11 | "path/filepath"
12 | "regexp"
13 |
14 | "github.com/benbjohnson/megajson/generator/decoder"
15 | "github.com/benbjohnson/megajson/generator/encoder"
16 | )
17 |
18 | var extregexp = regexp.MustCompile(`\.go$`)
19 |
20 | // Generator generates encoders and decoders for Go files matching a given path.
21 | type Generator interface {
22 | Generate(path string) error
23 | }
24 |
25 | type generator struct {
26 | decoder decoder.Generator
27 | encoder encoder.Generator
28 | }
29 |
30 | func New() Generator {
31 | return &generator{decoder:decoder.NewGenerator(), encoder:encoder.NewGenerator()}
32 | }
33 |
34 | // Generate recursively iterates over a path and generates encoders and decoders.
35 | func (g *generator) Generate(path string) error {
36 | return filepath.Walk(path, g.walk)
37 | }
38 |
39 | // walk iterates is the callback used by Generate() for iterating over files and directories.
40 | func (g *generator) walk(path string, info os.FileInfo, err error) error {
41 | // Only go file are used for generation.
42 | if info == nil {
43 | return fmt.Errorf("file not found: %s", path)
44 | } else if info.IsDir() || filepath.Ext(path) != ".go" {
45 | return nil
46 | }
47 |
48 | // Parse Go file.
49 | file, err := parser.ParseFile(token.NewFileSet(), path, nil, 0)
50 | if err != nil {
51 | return err
52 | }
53 |
54 | if err := g.encode(file, extregexp.ReplaceAllString(path, "_encoder.go"), info.Mode()); err != nil {
55 | return err
56 | }
57 | if err := g.decode(file, extregexp.ReplaceAllString(path, "_decoder.go"), info.Mode()); err != nil {
58 | return err
59 | }
60 |
61 | return nil
62 | }
63 |
64 | // decode generates a decoder file from a given Go file.
65 | func (g *generator) decode(file *ast.File, path string, mode os.FileMode) error {
66 | var b bytes.Buffer
67 | if err := g.decoder.Generate(&b, file); err != nil {
68 | return err
69 | }
70 | if b.Len() > 0 {
71 | if err := ioutil.WriteFile(path, b.Bytes(), mode); err != nil {
72 | return err
73 | }
74 | }
75 | return nil
76 | }
77 |
78 | // encode generates an encoder file from a given Go file.
79 | func (g *generator) encode(file *ast.File, path string, mode os.FileMode) error {
80 | var b bytes.Buffer
81 | if err := g.encoder.Generate(&b, file); err != nil {
82 | return err
83 | }
84 | if b.Len() > 0 {
85 | if err := ioutil.WriteFile(path, b.Bytes(), mode); err != nil {
86 | return err
87 | }
88 | }
89 | return nil
90 | }
91 |
--------------------------------------------------------------------------------
/generator/test/.fixtures/nested/decode.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "strings"
7 | )
8 |
9 | const DATA = `{"StringX":"foo","BX":{"Name":"John","Age":20},"BY":null,"Bn":[{"Name":"Jane","Age":60},{"Name":"Jack","Age":-13}]}`
10 |
11 | func main() {
12 | var obj *A
13 | d := NewAJSONDecoder(strings.NewReader(DATA))
14 | if err := d.Decode(&obj); err != nil {
15 | log.Fatalln("Decoding error: ", err.Error())
16 | }
17 |
18 | fmt.Print("|")
19 | fmt.Printf("%v|", obj.StringX)
20 | fmt.Printf("%v|", obj.BX.Name)
21 | fmt.Printf("%v|", obj.BX.Age)
22 | fmt.Printf("%v|", obj.BY)
23 | fmt.Printf("%v|", len(obj.Bn))
24 | fmt.Printf("%v|", obj.Bn[0].Name)
25 | fmt.Printf("%v|", obj.Bn[0].Age)
26 | fmt.Printf("%v|", obj.Bn[1].Name)
27 | fmt.Printf("%v|", obj.Bn[1].Age)
28 | }
29 |
--------------------------------------------------------------------------------
/generator/test/.fixtures/nested/encode.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 | )
7 |
8 | func main() {
9 | obj := &A{
10 | StringX: "foo",
11 | BX: &B{
12 | Name: "John",
13 | Age: 20,
14 | },
15 | BY: nil,
16 | Bn: []*B{
17 | &B{
18 | Name: "Jane",
19 | Age: 60,
20 | },
21 | },
22 | }
23 | e := NewAJSONEncoder(os.Stdout)
24 | if err := e.Encode(obj); err != nil {
25 | log.Fatalln("Encoding error: ", err.Error())
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/generator/test/.fixtures/nested/types.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | type A struct {
4 | StringX string
5 | BX *B
6 | BY *B
7 | Bn []*B
8 | Bn2 []*B
9 | }
10 |
11 | type B struct {
12 | Name string
13 | Age int
14 | }
15 |
--------------------------------------------------------------------------------
/generator/test/.fixtures/simple/decode.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "strings"
7 | )
8 |
9 | const DATA = `{"StringX":"foo","IntX":200,"Int64X":189273,"myuint":2392,"Uint64X":172389984,"Float32X":182.23,"Float64X":19380.1312,"BoolX":true}`
10 |
11 | func main() {
12 | s := &MyStruct{}
13 |
14 | d := NewMyStructJSONDecoder(strings.NewReader(DATA))
15 | if err := d.Decode(&s); err != nil {
16 | log.Fatalln("MyStruct decoding error: ", err.Error())
17 | }
18 |
19 | fmt.Print("|")
20 | fmt.Printf("%v|", s.StringX)
21 | fmt.Printf("%v|", s.IntX)
22 | fmt.Printf("%v|", s.Int64X)
23 | fmt.Printf("%v|", s.UintX)
24 | fmt.Printf("%v|", s.Uint64X)
25 | fmt.Printf("%v|", s.Float32X)
26 | fmt.Printf("%v|", s.Float64X)
27 | fmt.Printf("%v|", s.BoolX)
28 | }
29 |
--------------------------------------------------------------------------------
/generator/test/.fixtures/simple/encode.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "os"
6 | )
7 |
8 | func main() {
9 | s := &MyStruct{
10 | StringX: "foo",
11 | IntX: 200,
12 | Int64X: 189273,
13 | UintX: 2392,
14 | Uint64X: 172389984,
15 | Float32X: 182.23,
16 | Float64X: 19380.1312,
17 | BoolX: true,
18 | }
19 | e := NewMyStructJSONEncoder(os.Stdout)
20 | if err := e.Encode(s); err != nil {
21 | log.Fatalln("MyStruct encoding error: ", err.Error())
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/generator/test/.fixtures/simple/types.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | type MyStruct struct {
4 | StringX string
5 | IntX int
6 | Int64X int64
7 | UintX uint `json:"myuint"`
8 | Uint64X uint64
9 | Float32X float32
10 | Float64X float64
11 | BoolX bool
12 | IgnoreString string `json:"-"`
13 | }
14 |
--------------------------------------------------------------------------------
/generator/test/test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "os"
7 | "os/exec"
8 | "path/filepath"
9 | "runtime"
10 | )
11 |
12 | // Sets up a Go project using a given fixture directory.
13 | func Test(name string, fn func(string)) {
14 | path, _ := ioutil.TempDir("", "")
15 | os.RemoveAll(path)
16 | defer os.RemoveAll(path)
17 |
18 | _, base, _, _ := runtime.Caller(0)
19 | run("cp", "-r", filepath.Join(filepath.Dir(base), ".fixtures", name), path)
20 | fn(path)
21 | }
22 |
23 | // Executes a command that is expected run successfully.
24 | // On failure it dumps output and panics.
25 | func run(name string, args ...string) {
26 | c := exec.Command(name, args...)
27 | if err := c.Run(); err != nil {
28 | fmt.Println(c.CombinedOutput())
29 | panic("fixture error: " + err.Error())
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "log"
6 |
7 | "github.com/benbjohnson/megajson/generator"
8 | )
9 |
10 | func init() {
11 | log.SetFlags(0)
12 | }
13 |
14 | func main() {
15 | flag.Parse()
16 | if flag.NArg() == 0 {
17 | usage()
18 | }
19 |
20 | path := flag.Arg(0)
21 | g := generator.New()
22 | if err := g.Generate(path); err != nil {
23 | log.Fatalln(err)
24 | }
25 | }
26 |
27 | func usage() {
28 | log.Fatal("usage: megajson OPTIONS FILE")
29 | }
30 |
--------------------------------------------------------------------------------
/scanner/.gitignore:
--------------------------------------------------------------------------------
1 | *.test
2 | *.out
3 |
--------------------------------------------------------------------------------
/scanner/scanner.go:
--------------------------------------------------------------------------------
1 | package scanner
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "strconv"
7 | "unicode/utf8"
8 | )
9 |
10 | const (
11 | // The size, in bytes, that is read from the reader at a time.
12 | bufSize = 4096
13 | )
14 |
15 | // Scanner is a tokenizer for JSON input from an io.Reader.
16 | type Scanner interface {
17 | Pos() int
18 | Scan() (int, []byte, error)
19 | Unscan(tok int, b []byte)
20 | ReadString(target *string) error
21 | ReadInt(target *int) error
22 | ReadInt64(target *int64) error
23 | ReadUint(target *uint) error
24 | ReadUint64(target *uint64) error
25 | ReadFloat32(target *float32) error
26 | ReadFloat64(target *float64) error
27 | ReadBool(target *bool) error
28 | ReadMap(target *map[string]interface{}) error
29 | ReadArray(target *[]interface{}) error
30 | }
31 |
32 | type scanner struct {
33 | r io.Reader
34 | c rune
35 | scratch [bufSize]byte
36 | buf [bufSize]byte
37 | buflen int
38 | idx int
39 | pos int
40 | tmpc rune
41 | tmp struct {
42 | tok int
43 | b []byte
44 | err error
45 | }
46 | }
47 |
48 | // NewScanner initializes a new scanner with a given reader.
49 | func NewScanner(r io.Reader) Scanner {
50 | s := &scanner{r: r, buflen: -1}
51 | return s
52 | }
53 |
54 | // Pos returns the current rune position of the scanner.
55 | func (s *scanner) Pos() int {
56 | return s.pos
57 | }
58 |
59 | // read retrieves the next rune from the reader.
60 | func (s *scanner) read() error {
61 | if s.tmpc > 0 {
62 | s.c = s.tmpc
63 | s.tmpc = 0
64 | return nil
65 | }
66 |
67 | // Read from the reader if the buffer is empty.
68 | if s.idx >= s.buflen {
69 | var err error
70 | if s.buflen, err = s.r.Read(s.buf[0:]); err != nil {
71 | return err
72 | }
73 | s.idx = 0
74 | }
75 |
76 | // Read a single byte and then determine if utf8 decoding is needed.
77 | b := s.buf[s.idx]
78 | if b < utf8.RuneSelf {
79 | s.c = rune(b)
80 | s.idx++
81 | } else {
82 | // Read a new buffer if we don't have at least the max size of a UTF8 character.
83 | if s.idx+utf8.UTFMax >= s.buflen {
84 | s.buf[0] = b
85 | var err error
86 | if s.buflen, err = s.r.Read(s.buf[1:]); err != nil {
87 | return err
88 | }
89 | s.buflen += 1
90 | }
91 |
92 | var size int
93 | s.c, size = utf8.DecodeRune(s.buf[s.idx:])
94 | s.idx += size
95 | }
96 |
97 | s.pos++
98 | return nil
99 | }
100 |
101 | // unread places the current rune back on the reader.
102 | func (s *scanner) unread() {
103 | s.tmpc = s.c
104 | }
105 |
106 | // expect reads the next rune and checks that it matches.
107 | func (s *scanner) expect(c rune) error {
108 | if err := s.read(); err != nil {
109 | return err
110 | } else if s.c != c {
111 | return fmt.Errorf("Unexpected char: %q", s.c)
112 | }
113 | return nil
114 | }
115 |
116 | // Scan returns the next JSON token from the reader.
117 | func (s *scanner) Scan() (int, []byte, error) {
118 | if s.tmp.tok != 0 {
119 | tok, b := s.tmp.tok, s.tmp.b
120 | s.tmp.tok, s.tmp.b = 0, nil
121 | return tok, b, nil
122 | }
123 |
124 | for {
125 | if err := s.read(); err != nil {
126 | return 0, nil, err
127 | }
128 |
129 | switch s.c {
130 | case '{':
131 | return TLBRACE, []byte{'{'}, nil
132 | case '}':
133 | return TRBRACE, []byte{'}'}, nil
134 | case '[':
135 | return TLBRACKET, []byte{'['}, nil
136 | case ']':
137 | return TRBRACKET, []byte{']'}, nil
138 | case ':':
139 | return TCOLON, []byte{':'}, nil
140 | case ',':
141 | return TCOMMA, []byte{','}, nil
142 | case '"':
143 | return s.scanString()
144 | case 't':
145 | return s.scanTrue()
146 | case 'f':
147 | return s.scanFalse()
148 | case 'n':
149 | return s.scanNull()
150 | }
151 |
152 | if (s.c >= '0' && s.c <= '9') || s.c == '-' {
153 | return s.scanNumber()
154 | }
155 | }
156 | }
157 |
158 | // Unscan adds a token and byte array back onto the buffer to be read
159 | // on the next call to Scan().
160 | func (s *scanner) Unscan(tok int, b []byte) {
161 | s.tmp.tok = tok
162 | s.tmp.b = b
163 | s.pos--
164 | }
165 |
166 | // scanNumber reads a JSON number from the reader.
167 | func (s *scanner) scanNumber() (int, []byte, error) {
168 | var n int
169 |
170 | if s.c == '-' {
171 | s.scratch[n] = '-'
172 | n++
173 | if err := s.read(); err != nil {
174 | return 0, nil, err
175 | }
176 | }
177 |
178 | // Read whole number.
179 | if err := s.scanDigits(&n); err == io.EOF {
180 | return TNUMBER, s.scratch[0:n], nil
181 | } else if err != nil {
182 | return 0, nil, err
183 | }
184 | n++
185 |
186 | // Read period.
187 | if err := s.read(); err == io.EOF {
188 | return TNUMBER, s.scratch[0:n], nil
189 | } else if err != nil {
190 | return 0, nil, err
191 | } else if s.c != '.' {
192 | s.unread()
193 | // We can't just return. The number could be 1e-1
194 | return s.scanScientific(n)
195 | }
196 | s.scratch[n] = '.'
197 | n++
198 |
199 | if err := s.read(); err != nil {
200 | return 0, nil, err
201 | }
202 |
203 | // Read fraction.
204 | if err := s.scanDigits(&n); err == io.EOF {
205 | return TNUMBER, s.scratch[0:n], nil
206 | } else if err != nil {
207 | return 0, nil, err
208 | }
209 | n++
210 |
211 | return s.scanScientific(n)
212 | }
213 |
214 | func (s *scanner) scanScientific(n int) (int, []byte, error) {
215 | // Read scientific
216 | if err := s.read(); err == io.EOF {
217 | return TNUMBER, s.scratch[0:n], nil
218 | } else if err != nil {
219 | return 0, nil, err
220 | } else if s.c != 'e' && s.c != 'E' {
221 | s.unread()
222 | return TNUMBER, s.scratch[0:n], nil
223 | }
224 | s.scratch[n] = 'e'
225 | n++
226 |
227 | // Read sign
228 | if err := s.read(); err == io.EOF {
229 | return TNUMBER, s.scratch[0:n], nil
230 | } else if err != nil {
231 | return 0, nil, err
232 | } else if s.c != '-' && s.c != '+' {
233 | s.unread()
234 | } else if s.c == '-' { // don't bother adding the +
235 | s.scratch[n] = '-'
236 | n++
237 | }
238 |
239 | if err := s.read(); err != nil {
240 | return 0, nil, err
241 | }
242 |
243 | // Read whole number.
244 | if err := s.scanDigits(&n); err == io.EOF {
245 | return TNUMBER, s.scratch[0:n], nil
246 | } else if err != nil {
247 | fmt.Println("Bad?")
248 | return 0, nil, err
249 | }
250 | n++
251 |
252 | return TNUMBER, s.scratch[0:n], nil
253 | }
254 |
255 | // scanDigits reads a series of digits from the reader.
256 | func (s *scanner) scanDigits(n *int) error {
257 | for {
258 | if s.c >= '0' && s.c <= '9' {
259 | s.scratch[*n] = byte(s.c)
260 | (*n)++
261 | if err := s.read(); err != nil {
262 | return err
263 | }
264 | } else {
265 | s.unread()
266 | (*n)--
267 | return nil
268 | }
269 | }
270 | }
271 |
272 | // scanString reads a quoted JSON string from the reader.
273 | func (s *scanner) scanString() (int, []byte, error) {
274 | var overflow []byte
275 |
276 | var n int
277 | for {
278 | if err := s.read(); err != nil {
279 | return 0, nil, err
280 | }
281 | switch s.c {
282 | case '\\':
283 | if err := s.read(); err != nil {
284 | return 0, nil, err
285 | }
286 | switch s.c {
287 | case '"':
288 | s.scratch[n] = '"'
289 | n++
290 | case '\\':
291 | s.scratch[n] = '\\'
292 | n++
293 | case '/':
294 | s.scratch[n] = '/'
295 | n++
296 | case 'b':
297 | s.scratch[n] = '\b'
298 | n++
299 | case 'f':
300 | s.scratch[n] = '\f'
301 | n++
302 | case 'n':
303 | s.scratch[n] = '\n'
304 | n++
305 | case 'r':
306 | s.scratch[n] = '\r'
307 | n++
308 | case 't':
309 | s.scratch[n] = '\t'
310 | n++
311 | case 'u':
312 | numeric := make([]byte, 4)
313 | numericCount := 0
314 | unicode_loop:
315 | for {
316 | if err := s.read(); err != nil {
317 | return 0, nil, err
318 | }
319 | switch {
320 | case s.c >= '0' && s.c <= '9' || s.c >= 'a' && s.c <= 'f' || s.c >= 'A' && s.c <= 'F':
321 | numeric[numericCount] = byte(s.c)
322 | numericCount++
323 | if numericCount == 4 {
324 | var i int64
325 | var err error
326 | if i, err = strconv.ParseInt(string(numeric), 16, 32); err != nil {
327 | return 0, nil, err
328 | }
329 | if i < utf8.RuneSelf {
330 | s.scratch[n] = byte(i)
331 | n++
332 | } else {
333 | encoded := utf8.EncodeRune(s.scratch[n:], rune(i))
334 | n += encoded
335 | }
336 | break unicode_loop
337 | }
338 | default:
339 | s.unread()
340 | return 0, nil, fmt.Errorf("Unexpected symbol in unicode escape: %c", s.c)
341 | }
342 | }
343 | default:
344 | return 0, nil, fmt.Errorf("Invalid escape character: \\%c", s.c)
345 | }
346 |
347 | case '"':
348 | if len(overflow) == 0 {
349 | return TSTRING, s.scratch[0:n], nil
350 | }
351 | overflow = append(overflow, s.scratch[0:n]...)
352 | return TSTRING, overflow, nil
353 |
354 | default:
355 | if s.c < utf8.RuneSelf {
356 | if n == bufSize {
357 | overflow = append(overflow, s.scratch[0:n]...)
358 | n = 0
359 | }
360 | s.scratch[n] = byte(s.c)
361 | n++
362 | } else {
363 | n += utf8.EncodeRune(s.scratch[n:], s.c)
364 | }
365 | }
366 | }
367 | }
368 |
369 | // scanTrue reads the "true" token.
370 | func (s *scanner) scanTrue() (int, []byte, error) {
371 | if err := s.expect('r'); err != nil {
372 | return 0, nil, err
373 | }
374 | if err := s.expect('u'); err != nil {
375 | return 0, nil, err
376 | }
377 | if err := s.expect('e'); err != nil {
378 | return 0, nil, err
379 | }
380 | return TTRUE, nil, nil
381 | }
382 |
383 | // scanFalse reads the "false" token.
384 | func (s *scanner) scanFalse() (int, []byte, error) {
385 | if err := s.expect('a'); err != nil {
386 | return 0, nil, err
387 | }
388 | if err := s.expect('l'); err != nil {
389 | return 0, nil, err
390 | }
391 | if err := s.expect('s'); err != nil {
392 | return 0, nil, err
393 | }
394 | if err := s.expect('e'); err != nil {
395 | return 0, nil, err
396 | }
397 | return TFALSE, nil, nil
398 | }
399 |
400 | // scanNull reads the "null" token.
401 | func (s *scanner) scanNull() (int, []byte, error) {
402 | if err := s.expect('u'); err != nil {
403 | return 0, nil, err
404 | }
405 | if err := s.expect('l'); err != nil {
406 | return 0, nil, err
407 | }
408 | if err := s.expect('l'); err != nil {
409 | return 0, nil, err
410 | }
411 | return TNULL, nil, nil
412 | }
413 |
414 | // ReadString reads a token into a string variable.
415 | func (s *scanner) ReadString(target *string) error {
416 | tok, b, err := s.Scan()
417 | if err != nil {
418 | return err
419 | }
420 | switch tok {
421 | case TSTRING:
422 | *target = string(b)
423 | case TNUMBER, TTRUE, TFALSE, TNULL:
424 | *target = ""
425 | default:
426 | return fmt.Errorf("Unexpected %s at %d: %s; expected string", TokenName(tok), s.pos, string(b))
427 | }
428 | return nil
429 | }
430 |
431 | // ReadInt reads a token into an int variable.
432 | func (s *scanner) ReadInt(target *int) error {
433 | tok, b, err := s.Scan()
434 | if err != nil {
435 | return err
436 | }
437 | switch tok {
438 | case TNUMBER:
439 | n, _ := strconv.ParseInt(string(b), 10, 64)
440 | *target = int(n)
441 | case TSTRING, TTRUE, TFALSE, TNULL:
442 | *target = 0
443 | default:
444 | return fmt.Errorf("Unexpected %s at %d: %s; expected number", TokenName(tok), s.pos, string(b))
445 | }
446 | return nil
447 | }
448 |
449 | // ReadInt64 reads a token into an int64 variable.
450 | func (s *scanner) ReadInt64(target *int64) error {
451 | tok, b, err := s.Scan()
452 | if err != nil {
453 | return err
454 | }
455 | switch tok {
456 | case TNUMBER:
457 | n, _ := strconv.ParseInt(string(b), 10, 64)
458 | *target = n
459 | case TSTRING, TTRUE, TFALSE, TNULL:
460 | *target = 0
461 | default:
462 | return fmt.Errorf("Unexpected %s at %d: %s; expected number", TokenName(tok), s.pos, string(b))
463 | }
464 | return nil
465 | }
466 |
467 | // ReadUint reads a token into an uint variable.
468 | func (s *scanner) ReadUint(target *uint) error {
469 | tok, b, err := s.Scan()
470 | if err != nil {
471 | return err
472 | }
473 | switch tok {
474 | case TNUMBER:
475 | n, _ := strconv.ParseUint(string(b), 10, 64)
476 | *target = uint(n)
477 | case TSTRING, TTRUE, TFALSE, TNULL:
478 | *target = 0
479 | default:
480 | return fmt.Errorf("Unexpected %s at %d: %s; expected number", TokenName(tok), s.pos, string(b))
481 | }
482 | return nil
483 | }
484 |
485 | // ReadUint64 reads a token into an uint64 variable.
486 | func (s *scanner) ReadUint64(target *uint64) error {
487 | tok, b, err := s.Scan()
488 | if err != nil {
489 | return err
490 | }
491 | switch tok {
492 | case TNUMBER:
493 | n, _ := strconv.ParseUint(string(b), 10, 64)
494 | *target = n
495 | case TSTRING, TTRUE, TFALSE, TNULL:
496 | *target = 0
497 | default:
498 | return fmt.Errorf("Unexpected %s at %d: %s; expected number", TokenName(tok), s.pos, string(b))
499 | }
500 | return nil
501 | }
502 |
503 | // ReadFloat32 reads a token into a float32 variable.
504 | func (s *scanner) ReadFloat32(target *float32) error {
505 | tok, b, err := s.Scan()
506 | if err != nil {
507 | return err
508 | }
509 | switch tok {
510 | case TNUMBER:
511 | n, _ := strconv.ParseFloat(string(b), 32)
512 | *target = float32(n)
513 | case TSTRING, TTRUE, TFALSE, TNULL:
514 | *target = 0
515 | default:
516 | return fmt.Errorf("Unexpected %s at %d: %s; expected number", TokenName(tok), s.pos, string(b))
517 | }
518 | return nil
519 | }
520 |
521 | // ReadFloat64 reads a token into a float64 variable.
522 | func (s *scanner) ReadFloat64(target *float64) error {
523 | tok, b, err := s.Scan()
524 | if err != nil {
525 | return err
526 | }
527 | switch tok {
528 | case TNUMBER:
529 | n, _ := strconv.ParseFloat(string(b), 64)
530 | *target = n
531 | case TSTRING, TTRUE, TFALSE, TNULL:
532 | *target = 0
533 | default:
534 | return fmt.Errorf("Unexpected %s at %d: %s; expected number", TokenName(tok), s.pos, string(b))
535 | }
536 | return nil
537 | }
538 |
539 | // ReadBool reads a token into a boolean variable.
540 | func (s *scanner) ReadBool(target *bool) error {
541 | tok, b, err := s.Scan()
542 | if err != nil {
543 | return err
544 | }
545 | switch tok {
546 | case TTRUE:
547 | *target = true
548 | case TFALSE, TSTRING, TNUMBER, TNULL:
549 | *target = false
550 | default:
551 | return fmt.Errorf("Unexpected %s at %d: %s; expected number", TokenName(tok), s.pos, string(b))
552 | }
553 | return nil
554 | }
555 |
556 | // ReadMap reads the next value into a map variable.
557 | func (s *scanner) ReadMap(target *map[string]interface{}) error {
558 | if tok, b, err := s.Scan(); err != nil {
559 | return err
560 | } else if tok == TNULL {
561 | *target = nil
562 | return nil
563 | } else if tok != TLBRACE {
564 | return fmt.Errorf("Unexpected %s at %d: %s; expected '{'", TokenName(tok), s.Pos(), string(b))
565 | }
566 |
567 | // Create a new map.
568 | *target = make(map[string]interface{})
569 | v := *target
570 |
571 | // Loop over key/value pairs.
572 | index := 0
573 | for {
574 | // Read in key.
575 | var key string
576 | tok, b, err := s.Scan()
577 | if err != nil {
578 | return err
579 | } else if tok == TRBRACE {
580 | return nil
581 | } else if tok == TCOMMA {
582 | if index == 0 {
583 | return fmt.Errorf("Unexpected comma at %d", s.Pos())
584 | }
585 | if tok, b, err = s.Scan(); err != nil {
586 | return err
587 | }
588 | }
589 |
590 | if tok != TSTRING {
591 | return fmt.Errorf("Unexpected %s at %d: %s; expected '{' or string", TokenName(tok), s.Pos(), string(b))
592 | } else {
593 | key = string(b)
594 | }
595 |
596 | // Read in the colon.
597 | if tok, b, err := s.Scan(); err != nil {
598 | return err
599 | } else if tok != TCOLON {
600 | return fmt.Errorf("Unexpected %s at %d: %s; expected colon", TokenName(tok), s.Pos(), string(b))
601 | }
602 |
603 | // Read the next token.
604 | tok, b, err = s.Scan()
605 | if err != nil {
606 | return err
607 | }
608 | switch tok {
609 | case TSTRING:
610 | v[key] = string(b)
611 | case TNUMBER:
612 | v[key], _ = strconv.ParseFloat(string(b), 64)
613 | case TTRUE:
614 | v[key] = true
615 | case TFALSE:
616 | v[key] = false
617 | case TNULL:
618 | v[key] = nil
619 | case TLBRACE:
620 | s.Unscan(tok, b)
621 | m := make(map[string]interface{})
622 | if err := s.ReadMap(&m); err != nil {
623 | return err
624 | }
625 | v[key] = m
626 | case TLBRACKET:
627 | s.Unscan(tok, b)
628 | var arr []interface{}
629 | if err := s.ReadArray(&arr); err != nil {
630 | return err
631 | }
632 | v[key] = arr
633 | default:
634 | return fmt.Errorf("Unexpected %s at %d: %s", TokenName(tok), s.Pos(), string(b))
635 | }
636 |
637 | index++
638 | }
639 |
640 | return nil
641 | }
642 |
643 | func (s *scanner) ReadArray(target *[]interface{}) error {
644 | if tok, b, err := s.Scan(); err != nil {
645 | return err
646 | } else if tok != TLBRACKET {
647 | return fmt.Errorf("Unexpected %s at %d: %s; expected '['", TokenName(tok), s.Pos(), string(b))
648 | }
649 |
650 | index := 0
651 | for {
652 | tok, b, err := s.Scan()
653 | if err != nil {
654 | return err
655 | } else if tok == TRBRACKET {
656 | return nil
657 | } else if tok == TCOMMA {
658 | if index == 0 {
659 | return fmt.Errorf("Unexpected comma in array at %d", s.Pos())
660 | }
661 | if tok, b, err = s.Scan(); err != nil {
662 | return err
663 | }
664 | }
665 |
666 | var v interface{}
667 | switch tok {
668 | case TSTRING:
669 | v = string(b)
670 | case TNUMBER:
671 | v, _ = strconv.ParseFloat(string(b), 64)
672 | case TTRUE:
673 | v = true
674 | case TFALSE:
675 | v = false
676 | case TNULL:
677 | v = nil
678 | case TLBRACE:
679 | s.Unscan(tok, b)
680 | m := make(map[string]interface{})
681 | if err := s.ReadMap(&m); err != nil {
682 | return err
683 | }
684 | v = m
685 | case TLBRACKET:
686 | s.Unscan(tok, b)
687 | var arr []interface{}
688 | if err := s.ReadArray(&arr); err != nil {
689 | return err
690 | }
691 | v = arr
692 | default:
693 | return fmt.Errorf("Unexpected %s at %d: %s", TokenName(tok), s.Pos(), string(b))
694 | }
695 | *target = append(*target, v)
696 |
697 | index++
698 | }
699 | return nil
700 | }
701 |
--------------------------------------------------------------------------------
/scanner/scanner_test.go:
--------------------------------------------------------------------------------
1 | package scanner
2 |
3 | import (
4 | "bytes"
5 | "io"
6 | "strings"
7 | "testing"
8 |
9 | "github.com/stretchr/testify/assert"
10 | "strconv"
11 | )
12 |
13 | // Ensures that a positive number can be scanned.
14 | func TestScanPositiveNumber(t *testing.T) {
15 | tok, b, err := NewScanner(strings.NewReader("100")).Scan()
16 | assert.NoError(t, err)
17 | assert.Equal(t, tok, TNUMBER)
18 | assert.Equal(t, string(b), "100")
19 | }
20 |
21 | // Ensures that a negative number can be scanned.
22 | func TestScanNegativeNumber(t *testing.T) {
23 | tok, b, err := NewScanner(strings.NewReader("-1")).Scan()
24 | assert.NoError(t, err)
25 | assert.Equal(t, tok, TNUMBER)
26 | assert.Equal(t, string(b), "-1")
27 | }
28 |
29 | // Ensures that a fractional number can be scanned.
30 | func TestScanFloat(t *testing.T) {
31 | tok, b, err := NewScanner(strings.NewReader("120.12931")).Scan()
32 | assert.NoError(t, err)
33 | assert.Equal(t, tok, TNUMBER)
34 | assert.Equal(t, string(b), "120.12931")
35 | }
36 |
37 | // Ensures that a fractional number in scientific notation can be scanned.
38 | func TestScanFloatScientific(t *testing.T) {
39 | tok, b, err := NewScanner(strings.NewReader("10.1e01")).Scan()
40 | assert.NoError(t, err)
41 | assert.Equal(t, tok, TNUMBER)
42 | assert.Equal(t, string(b), "10.1e01")
43 |
44 | tok, b, err = NewScanner(strings.NewReader("10.1e-01")).Scan()
45 | assert.NoError(t, err)
46 | assert.Equal(t, tok, TNUMBER)
47 | assert.Equal(t, string(b), "10.1e-01")
48 |
49 | tok, b, err = NewScanner(strings.NewReader("10.1e+01")).Scan()
50 | assert.NoError(t, err)
51 | assert.Equal(t, tok, TNUMBER)
52 | assert.Equal(t, string(b), "10.1e01")
53 | f, _ := strconv.ParseFloat(string(b), 64)
54 | assert.Equal(t, 10.1e+01, f)
55 |
56 | tok, b, err = NewScanner(strings.NewReader("-1e1")).Scan()
57 | assert.NoError(t, err)
58 | assert.Equal(t, tok, TNUMBER)
59 | assert.Equal(t, string(b), "-1e1")
60 | }
61 |
62 | // Ensures that a quoted string can be scanned.
63 | func TestScanString(t *testing.T) {
64 | tok, b, err := NewScanner(strings.NewReader(`"hello world"`)).Scan()
65 | assert.NoError(t, err)
66 | assert.Equal(t, tok, TSTRING)
67 | assert.Equal(t, string(b), "hello world")
68 | }
69 |
70 | // Ensures that a quoted string with escaped characters can be scanned.
71 | func TestScanEscapedString(t *testing.T) {
72 | tok, b, err := NewScanner(strings.NewReader(`"\"\\\/\b\f\n\r\t"`)).Scan()
73 | assert.NoError(t, err)
74 | assert.Equal(t, tok, TSTRING)
75 | assert.Equal(t, string(b), "\"\\/\b\f\n\r\t")
76 | }
77 |
78 | // Ensures that escaped unicode sequences can be decoded.
79 | func TestScanEscapedUnicode(t *testing.T) {
80 | tok, b, err := NewScanner(strings.NewReader(`"\u0026 \u0424 \u03B4 \u03b4"`)).Scan()
81 | assert.NoError(t, err)
82 | assert.Equal(t, tok, TSTRING)
83 | assert.Equal(t, string(b), "& Ф δ δ")
84 | }
85 |
86 | // Ensures that a true value can be scanned.
87 | func TestScanTrue(t *testing.T) {
88 | tok, _, err := NewScanner(strings.NewReader(`true`)).Scan()
89 | assert.NoError(t, err)
90 | assert.Equal(t, tok, TTRUE)
91 | }
92 |
93 | // Ensures that a false value can be scanned.
94 | func TestScanFalse(t *testing.T) {
95 | tok, _, err := NewScanner(strings.NewReader(`false`)).Scan()
96 | assert.NoError(t, err)
97 | assert.Equal(t, tok, TFALSE)
98 | }
99 |
100 | // Ensures that a null value can be scanned.
101 | func TestScanNull(t *testing.T) {
102 | tok, _, err := NewScanner(strings.NewReader(`null`)).Scan()
103 | assert.NoError(t, err)
104 | assert.Equal(t, tok, TNULL)
105 | }
106 |
107 | // Ensures that an EOF gets returned.
108 | func TestScanEOF(t *testing.T) {
109 | _, _, err := NewScanner(strings.NewReader(``)).Scan()
110 | assert.Equal(t, err, io.EOF)
111 | }
112 |
113 | // Ensures that a string can be read into a field.
114 | func TestReadString(t *testing.T) {
115 | var v string
116 | err := NewScanner(strings.NewReader(`"foo"`)).ReadString(&v)
117 | assert.NoError(t, err)
118 | assert.Equal(t, v, "foo")
119 | }
120 |
121 | // Ensures that strings largers than allocated buffer can be read.
122 | func TestReadHugeString(t *testing.T) {
123 | var v string
124 | huge := strings.Repeat("s", bufSize*3)
125 | err := NewScanner(strings.NewReader(`"` + huge + `"`)).ReadString(&v)
126 | assert.NoError(t, err)
127 | assert.Equal(t, v, huge)
128 | }
129 |
130 | // Ensures that a non-string value is read into a string field as blank.
131 | func TestReadNonStringAsString(t *testing.T) {
132 | var v string
133 | err := NewScanner(strings.NewReader(`12`)).ReadString(&v)
134 | assert.NoError(t, err)
135 | assert.Equal(t, v, "")
136 | }
137 |
138 | // Ensures that a non-value returns a read error.
139 | func TestReadNonValueAsString(t *testing.T) {
140 | var v string
141 | err := NewScanner(strings.NewReader(`{`)).ReadString(&v)
142 | assert.Error(t, err)
143 | }
144 |
145 | // Ensures that an int can be read into a field.
146 | func TestReadInt(t *testing.T) {
147 | var v int
148 | err := NewScanner(strings.NewReader(`100`)).ReadInt(&v)
149 | assert.NoError(t, err)
150 | assert.Equal(t, v, 100)
151 | }
152 |
153 | // Ensures that a non-number value is read into an int field as zero.
154 | func TestReadNonNumberAsInt(t *testing.T) {
155 | var v int
156 | err := NewScanner(strings.NewReader(`"foo"`)).ReadInt(&v)
157 | assert.NoError(t, err)
158 | assert.Equal(t, v, 0)
159 | }
160 |
161 | // Ensures that an int64 can be read into a field.
162 | func TestReadInt64(t *testing.T) {
163 | var v int64
164 | err := NewScanner(strings.NewReader(`-100`)).ReadInt64(&v)
165 | assert.NoError(t, err)
166 | assert.Equal(t, v, -100)
167 | }
168 |
169 | // Ensures that a uint can be read into a field.
170 | func TestReadUint(t *testing.T) {
171 | var v uint
172 | err := NewScanner(strings.NewReader(`100`)).ReadUint(&v)
173 | assert.NoError(t, err)
174 | assert.Equal(t, v, uint(100))
175 | }
176 |
177 | // Ensures that an uint64 can be read into a field.
178 | func TestReadUint64(t *testing.T) {
179 | var v uint64
180 | err := NewScanner(strings.NewReader(`1024`)).ReadUint64(&v)
181 | assert.NoError(t, err)
182 | assert.Equal(t, v, uint(1024))
183 | }
184 |
185 | // Ensures that a float32 can be read into a field.
186 | func TestReadFloat32(t *testing.T) {
187 | var v float32
188 | err := NewScanner(strings.NewReader(`1293.123`)).ReadFloat32(&v)
189 | assert.NoError(t, err)
190 | assert.Equal(t, v, float32(1293.123))
191 | }
192 |
193 | // Ensures that a float64 can be read into a field.
194 | func TestReadFloat64(t *testing.T) {
195 | var v float64
196 | err := NewScanner(strings.NewReader(`9871293.414123`)).ReadFloat64(&v)
197 | assert.NoError(t, err)
198 | assert.Equal(t, v, 9871293.414123)
199 | }
200 |
201 | // Ensures that a boolean can be read into a field.
202 | func TestReadBoolTrue(t *testing.T) {
203 | var v bool
204 | err := NewScanner(strings.NewReader(`true`)).ReadBool(&v)
205 | assert.NoError(t, err)
206 | assert.Equal(t, v, true)
207 | }
208 |
209 | // Ensures whitespace between tokens are ignored.
210 | func TestScanIgnoreWhitespace(t *testing.T) {
211 | s := NewScanner(strings.NewReader(" 100 true false "))
212 |
213 | tok, _, err := s.Scan()
214 | assert.NoError(t, err)
215 | assert.Equal(t, tok, TNUMBER)
216 |
217 | tok, _, err = s.Scan()
218 | assert.NoError(t, err)
219 | assert.Equal(t, tok, TTRUE)
220 |
221 | tok, _, err = s.Scan()
222 | assert.NoError(t, err)
223 | assert.Equal(t, tok, TFALSE)
224 |
225 | tok, _, err = s.Scan()
226 | assert.Equal(t, err, io.EOF)
227 | assert.Equal(t, tok, 0)
228 | }
229 |
230 | // Ensures that a map can be read into a field.
231 | func TestReadMap(t *testing.T) {
232 | var v map[string]interface{}
233 | err := NewScanner(strings.NewReader(`{"foo":"bar", "bat":1293,"truex":true,"falsex":false,"nullx":null,"nested":{"xxx":"yyy"}}`)).ReadMap(&v)
234 | assert.NoError(t, err)
235 | assert.Equal(t, v["foo"], "bar")
236 | assert.Equal(t, v["bat"], float64(1293))
237 | assert.Equal(t, v["truex"], true)
238 | assert.Equal(t, v["falsex"], false)
239 | _, exists := v["nullx"]
240 | assert.Equal(t, v["nullx"], nil)
241 | assert.True(t, exists)
242 | assert.NotNil(t, v["nested"])
243 | nested := v["nested"].(map[string]interface{})
244 | assert.Equal(t, nested["xxx"], "yyy")
245 | }
246 |
247 | // Ensures that a map with arrays can be read into a field.
248 | func TestReadMapWithArray(t *testing.T) {
249 | var v map[string]interface{}
250 | err := NewScanner(strings.NewReader(`{"foo":["bar", 42]}`)).ReadMap(&v)
251 | assert.NoError(t, err)
252 | arr := v["foo"].([]interface{})
253 | t.Logf("got=%#v", v)
254 | assert.Equal(t, "bar", arr[0].(string))
255 | assert.Equal(t, 42.0, arr[1].(float64))
256 | }
257 |
258 | func BenchmarkScanNumber(b *testing.B) {
259 | withBuffer(b, "100", func(buf []byte) {
260 | s := NewScanner(bytes.NewBuffer(buf))
261 | for i := 0; i < b.N; i++ {
262 | if _, _, err := s.Scan(); err == io.EOF {
263 | s = NewScanner(bytes.NewBuffer(buf))
264 | } else if err != nil {
265 | b.Fatal("scan error:", err)
266 | }
267 | }
268 | })
269 | }
270 |
271 | func BenchmarkScanString(b *testing.B) {
272 | withBuffer(b, `"01234567"`, func(buf []byte) {
273 | s := NewScanner(bytes.NewBuffer(buf))
274 | for i := 0; i < b.N; i++ {
275 | if _, _, err := s.Scan(); err == io.EOF {
276 | s = NewScanner(bytes.NewBuffer(buf))
277 | } else if err != nil {
278 | b.Fatal("scan error:", err)
279 | }
280 | }
281 | })
282 | }
283 |
284 | func BenchmarkScanLongString(b *testing.B) {
285 | withBuffer(b, `"foo foo foo foo foo foo foo foo foo foo foo foo foo foo"`, func(buf []byte) {
286 | s := NewScanner(bytes.NewBuffer(buf))
287 | for i := 0; i < b.N; i++ {
288 | if _, _, err := s.Scan(); err == io.EOF {
289 | s = NewScanner(bytes.NewBuffer(buf))
290 | } else if err != nil {
291 | b.Fatal("scan error:", err)
292 | }
293 | }
294 | })
295 | }
296 |
297 | func BenchmarkScanEscapedString(b *testing.B) {
298 | withBuffer(b, `"\"\\\/\b\f\n\r\t"`, func(buf []byte) {
299 | s := NewScanner(bytes.NewBuffer(buf))
300 | for i := 0; i < b.N; i++ {
301 | if _, _, err := s.Scan(); err == io.EOF {
302 | s = NewScanner(bytes.NewBuffer(buf))
303 | } else if err != nil {
304 | b.Fatal("scan error:", err)
305 | }
306 | }
307 | })
308 | }
309 |
310 | func BenchmarkReadString(b *testing.B) {
311 | withBuffer(b, `"01234567"`, func(buf []byte) {
312 | var v string
313 | s := NewScanner(bytes.NewBuffer(buf))
314 | for i := 0; i < b.N; i++ {
315 | if err := s.ReadString(&v); err == io.EOF {
316 | s = NewScanner(bytes.NewBuffer(buf))
317 | } else if err != nil {
318 | b.Fatal("scan error:", err)
319 | }
320 | }
321 | })
322 | }
323 |
324 | func BenchmarkReadLongString(b *testing.B) {
325 | withBuffer(b, `"foo foo foo foo foo foo foo foo foo foo foo foo foo foo"`, func(buf []byte) {
326 | var v string
327 | s := NewScanner(bytes.NewBuffer(buf))
328 | for i := 0; i < b.N; i++ {
329 | if err := s.ReadString(&v); err == io.EOF {
330 | s = NewScanner(bytes.NewBuffer(buf))
331 | } else if err != nil {
332 | b.Fatal("scan error:", err)
333 | }
334 | }
335 | })
336 | }
337 |
338 | func BenchmarkReadInt(b *testing.B) {
339 | withBuffer(b, `"100"`, func(buf []byte) {
340 | var v int
341 | s := NewScanner(bytes.NewBuffer(buf))
342 | for i := 0; i < b.N; i++ {
343 | if err := s.ReadInt(&v); err == io.EOF {
344 | s = NewScanner(bytes.NewBuffer(buf))
345 | } else if err != nil {
346 | b.Fatal("scan error:", err)
347 | }
348 | }
349 | })
350 | }
351 |
352 | func BenchmarkReadFloat64(b *testing.B) {
353 | withBuffer(b, `"9871293.414123"`, func(buf []byte) {
354 | var v float64
355 | s := NewScanner(bytes.NewBuffer(buf))
356 | for i := 0; i < b.N; i++ {
357 | if err := s.ReadFloat64(&v); err == io.EOF {
358 | s = NewScanner(bytes.NewBuffer(buf))
359 | } else if err != nil {
360 | b.Fatal("scan error:", err)
361 | }
362 | }
363 | })
364 | }
365 |
366 | func BenchmarkReadBool(b *testing.B) {
367 | withBuffer(b, `true`, func(buf []byte) {
368 | var v bool
369 | s := NewScanner(bytes.NewBuffer(buf))
370 | for i := 0; i < b.N; i++ {
371 | if err := s.ReadBool(&v); err == io.EOF {
372 | s = NewScanner(bytes.NewBuffer(buf))
373 | } else if err != nil {
374 | b.Fatal("scan error:", err)
375 | }
376 | }
377 | })
378 | }
379 |
380 | func withBuffer(b *testing.B, value string, fn func([]byte)) {
381 | b.StopTimer()
382 | var str string
383 | for i := 0; i < 1000; i++ {
384 | str += value + " "
385 | }
386 | b.StartTimer()
387 |
388 | fn([]byte(str))
389 |
390 | b.SetBytes(int64(len(value)))
391 | }
392 |
--------------------------------------------------------------------------------
/scanner/token.go:
--------------------------------------------------------------------------------
1 | package scanner
2 |
3 | const (
4 | TSTRING = iota + 1
5 | TNUMBER
6 | TTRUE
7 | TFALSE
8 | TNULL
9 | TLBRACE
10 | TRBRACE
11 | TLBRACKET
12 | TRBRACKET
13 | TCOLON
14 | TCOMMA
15 | )
16 |
17 | var names = map[int]string{
18 | TSTRING: "string",
19 | TNUMBER: "number",
20 | TTRUE: "true",
21 | TFALSE: "false",
22 | TNULL: "null",
23 | TLBRACE: "left brace",
24 | TRBRACE: "right brace",
25 | TLBRACKET: "left bracket",
26 | TRBRACKET: "right bracket",
27 | TCOLON: "colon",
28 | TCOMMA: "comma",
29 | }
30 |
31 | // TokenName returns a human readable version of the token.
32 | func TokenName(tok int) string {
33 | return names[tok]
34 | }
35 |
--------------------------------------------------------------------------------
/writer/writer.go:
--------------------------------------------------------------------------------
1 | package writer
2 |
3 | import (
4 | "encoding/json"
5 | "io"
6 | "strconv"
7 | "unicode/utf8"
8 | )
9 |
10 | const (
11 | // The maximum size that a byte can be encoded as.
12 | maxByteEncodeSize = 6
13 |
14 | // The size, in bytes, that can be encoded at a time.
15 | bufSize = 16384
16 |
17 | // The max size, in bytes, that an encoded value can be.
18 | actualBufSize = (bufSize * maxByteEncodeSize)
19 | )
20 |
21 | var hex = "0123456789abcdef"
22 |
23 | type Writer struct {
24 | w io.Writer
25 | buf [actualBufSize + 64]byte
26 | pos int
27 | }
28 |
29 | // NewWriter creates a new JSON writer.
30 | func NewWriter(w io.Writer) *Writer {
31 | return &Writer{w: w}
32 | }
33 |
34 | // Flush writes all data in the buffer to the writer.
35 | func (w *Writer) Flush() error {
36 | if w.pos > 0 {
37 | if _, err := w.w.Write(w.buf[0:w.pos]); err != nil {
38 | return err
39 | }
40 | w.pos = 0
41 | }
42 | return nil
43 | }
44 |
45 | // check verifies there is space in the buffer.
46 | func (w *Writer) check() error {
47 | if w.pos > actualBufSize {
48 | return w.Flush()
49 | }
50 | return nil
51 | }
52 |
53 | // writeByte writes a single byte to the buffer and increments the position.
54 | func (w *Writer) writeByte(c byte) {
55 | w.buf[w.pos] = c
56 | w.pos++
57 | }
58 |
59 | // writeString writes a string to the buffer and increments the position.
60 | func (w *Writer) writeString(s string) {
61 | copy(w.buf[w.pos:], s)
62 | w.pos += len(s)
63 | }
64 |
65 | // WriteByte writes a single byte.
66 | func (w *Writer) WriteByte(c byte) error {
67 | if err := w.check(); err != nil {
68 | return err
69 | }
70 | w.buf[w.pos] = c
71 | w.pos++
72 | return nil
73 | }
74 |
75 | // WriteString writes a JSON string to the writer. Parts of this function are
76 | // borrowed from the encoding/json package.
77 | func (w *Writer) WriteString(v string) error {
78 | bufsz := (actualBufSize - w.pos) / maxByteEncodeSize
79 |
80 | w.writeByte('"')
81 | for i := 0; i < len(v); i += bufsz {
82 | if i > 0 {
83 | bufsz = bufSize
84 | if err := w.Flush(); err != nil {
85 | return err
86 | }
87 | }
88 |
89 | // Extract substring.
90 | end := i + bufsz
91 | if end > len(v) {
92 | end = len(v)
93 | }
94 | bufend := end + utf8.UTFMax
95 | if bufend > len(v) {
96 | bufend = len(v)
97 | }
98 | sub := v[i:bufend]
99 | sublen := end - i
100 |
101 | prev := 0
102 | for j := 0; j < sublen; {
103 | if b := sub[j]; b < utf8.RuneSelf {
104 | if 0x20 <= b && b != '\\' && b != '"' && b != '<' && b != '>' {
105 | j++
106 | continue
107 | }
108 | if prev < j {
109 | w.writeString(sub[prev:j])
110 | }
111 | switch b {
112 | case '\\':
113 | w.writeByte('\\')
114 | w.writeByte('\\')
115 | case '"':
116 | w.writeByte('\\')
117 | w.writeByte('"')
118 | case '\n':
119 | w.writeByte('\\')
120 | w.writeByte('n')
121 | case '\r':
122 | w.writeByte('\\')
123 | w.writeByte('r')
124 | default:
125 | // This encodes bytes < 0x20 except for \n and \r,
126 | // as well as < and >. The latter are escaped because they
127 | // can lead to security holes when user-controlled strings
128 | // are rendered into JSON and served to some browsers.
129 | w.writeByte('\\')
130 | w.writeByte('u')
131 | w.writeByte('0')
132 | w.writeByte('0')
133 | w.writeByte(hex[b>>4])
134 | w.writeByte(hex[b&0xF])
135 | }
136 | j++
137 | prev = j
138 | continue
139 | }
140 | c, size := utf8.DecodeRuneInString(sub[j:])
141 | if c == utf8.RuneError && size == 1 {
142 | return &json.InvalidUTF8Error{S: v}
143 | }
144 | j += size
145 |
146 | // If we cross the buffer end then adjust the outer loop
147 | if j > bufsz {
148 | i += j - bufsz
149 | sublen += j - bufsz
150 | }
151 | }
152 | if prev < sublen {
153 | w.writeString(sub[prev:sublen])
154 | }
155 | }
156 | w.writeByte('"')
157 | return nil
158 | }
159 |
160 | // WriteInt encodes and writes an integer.
161 | func (w *Writer) WriteInt(v int) error {
162 | return w.WriteInt64(int64(v))
163 | }
164 |
165 | // WriteInt64 encodes and writes a 64-bit integer.
166 | func (w *Writer) WriteInt64(v int64) error {
167 | if err := w.check(); err != nil {
168 | return err
169 | }
170 |
171 | buf := strconv.AppendInt(w.buf[w.pos:w.pos], v, 10)
172 | w.pos += len(buf)
173 | return nil
174 | }
175 |
176 | // WriteUint encodes and writes an unsigned integer.
177 | func (w *Writer) WriteUint(v uint) error {
178 | return w.WriteUint64(uint64(v))
179 | }
180 |
181 | // WriteUint encodes and writes an unsigned integer.
182 | func (w *Writer) WriteUint64(v uint64) error {
183 | if err := w.check(); err != nil {
184 | return err
185 | }
186 |
187 | buf := strconv.AppendUint(w.buf[w.pos:w.pos], v, 10)
188 | w.pos += len(buf)
189 | return nil
190 | }
191 |
192 | // WriteFloat32 encodes and writes a 32-bit float.
193 | func (w *Writer) WriteFloat32(v float32) error {
194 | if err := w.check(); err != nil {
195 | return err
196 | }
197 | buf := strconv.AppendFloat(w.buf[w.pos:w.pos], float64(v), 'g', -1, 32)
198 | w.pos += len(buf)
199 | return nil
200 | }
201 |
202 | // WriteFloat64 encodes and writes a 64-bit float.
203 | func (w *Writer) WriteFloat64(v float64) error {
204 | if err := w.check(); err != nil {
205 | return err
206 | }
207 | buf := strconv.AppendFloat(w.buf[w.pos:w.pos], v, 'g', -1, 64)
208 | w.pos += len(buf)
209 | return nil
210 | }
211 |
212 | // WriteBool writes a boolean.
213 | func (w *Writer) WriteBool(v bool) error {
214 | if err := w.check(); err != nil {
215 | return err
216 | }
217 | if v {
218 | w.buf[w.pos+0] = 't'
219 | w.buf[w.pos+1] = 'r'
220 | w.buf[w.pos+2] = 'u'
221 | w.buf[w.pos+3] = 'e'
222 | w.pos += 4
223 | } else {
224 | w.buf[w.pos+0] = 'f'
225 | w.buf[w.pos+1] = 'a'
226 | w.buf[w.pos+2] = 'l'
227 | w.buf[w.pos+3] = 's'
228 | w.buf[w.pos+4] = 'e'
229 | w.pos += 5
230 | }
231 | return nil
232 | }
233 |
234 | // WriteNull writes "null".
235 | func (w *Writer) WriteNull() error {
236 | if err := w.check(); err != nil {
237 | return err
238 | }
239 | w.buf[w.pos+0] = 'n'
240 | w.buf[w.pos+1] = 'u'
241 | w.buf[w.pos+2] = 'l'
242 | w.buf[w.pos+3] = 'l'
243 | w.pos += 4
244 | return nil
245 | }
246 |
247 | // WriteMap writes a map.
248 | func (w *Writer) WriteMap(v map[string]interface{}) error {
249 | if err := w.check(); err != nil {
250 | return err
251 | }
252 |
253 | w.buf[w.pos] = '{'
254 | w.pos++
255 |
256 | var index int
257 | for key, value := range v {
258 | if index > 0 {
259 | w.buf[w.pos] = ','
260 | w.pos++
261 | }
262 |
263 | // Write key and colon.
264 | if err := w.WriteString(key); err != nil {
265 | return err
266 | }
267 | w.buf[w.pos] = ':'
268 | w.pos++
269 |
270 | // Write value.
271 | if value == nil {
272 | if err := w.WriteNull(); err != nil {
273 | return err
274 | }
275 |
276 | } else {
277 | switch value := value.(type) {
278 | case string:
279 | if err := w.WriteString(value); err != nil {
280 | return err
281 | }
282 | case int:
283 | if err := w.WriteInt(value); err != nil {
284 | return err
285 | }
286 | case int64:
287 | if err := w.WriteInt64(value); err != nil {
288 | return err
289 | }
290 | case uint:
291 | if err := w.WriteUint(value); err != nil {
292 | return err
293 | }
294 | case uint64:
295 | if err := w.WriteUint64(value); err != nil {
296 | return err
297 | }
298 | case float32:
299 | if err := w.WriteFloat32(value); err != nil {
300 | return err
301 | }
302 | case float64:
303 | if err := w.WriteFloat64(value); err != nil {
304 | return err
305 | }
306 | case bool:
307 | if err := w.WriteBool(value); err != nil {
308 | return err
309 | }
310 | case map[string]interface{}:
311 | if err := w.WriteMap(value); err != nil {
312 | return err
313 | }
314 | }
315 | }
316 |
317 | index++
318 | }
319 |
320 | w.buf[w.pos] = '}'
321 | w.pos++
322 |
323 | return nil
324 | }
325 |
--------------------------------------------------------------------------------
/writer/writer_test.go:
--------------------------------------------------------------------------------
1 | package writer
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | // Ensures that a string can be escaped and encoded.
11 | func TestWriteString(t *testing.T) {
12 | var b bytes.Buffer
13 | w := NewWriter(&b)
14 | w.WriteString("foo\t\n\r\"大")
15 | assert.NoError(t, w.Flush())
16 | assert.Equal(t, `"foo\u0009\n\r\"大"`, b.String())
17 | }
18 |
19 | // Ensures that a large string can be escaped and encoded.
20 | func TestWriteStringLarge(t *testing.T) {
21 | var input, expected string
22 | for i := 0; i < 10000; i++ {
23 | input += "\t"
24 | expected += `\u0009`
25 | }
26 | input += "X"
27 | expected = "\"" + expected + "X\""
28 |
29 | var b bytes.Buffer
30 | w := NewWriter(&b)
31 | err := w.WriteString(input)
32 | assert.NoError(t, w.Flush())
33 | assert.NoError(t, err)
34 | assert.Equal(t, len(expected), len(b.String()))
35 | if err == nil && len(expected) == len(b.String()) {
36 | assert.Equal(t, expected, b.String())
37 | }
38 | }
39 |
40 | // Ensures that a large unicode string can be escaped and encoded.
41 | func TestWriteStringLargeUnicode(t *testing.T) {
42 | var input, expected string
43 | for i := 0; i < 10000; i++ {
44 | input += "大"
45 | expected += "大"
46 | }
47 | expected = "\"" + expected + "\""
48 |
49 | var b bytes.Buffer
50 | w := NewWriter(&b)
51 | err := w.WriteString(input)
52 | assert.NoError(t, w.Flush())
53 | assert.NoError(t, err)
54 | assert.Equal(t, len(expected), len(b.String()))
55 | if err == nil && len(expected) == len(b.String()) {
56 | assert.Equal(t, expected, b.String())
57 | }
58 | }
59 |
60 | // Ensures that a multiple strings can be encoded sequentially and share the same buffer.
61 | func TestWriteMultipleStrings(t *testing.T) {
62 | var b bytes.Buffer
63 | var expected string
64 | w := NewWriter(&b)
65 |
66 | for i := 0; i < 10000; i++ {
67 | err := w.WriteString("foo\t\n\r\"大\t")
68 | assert.NoError(t, err)
69 | expected += `"foo\u0009\n\r\"大\u0009"`
70 | }
71 | assert.NoError(t, w.Flush())
72 | assert.Equal(t, len(expected), len(b.String()))
73 | if len(expected) == len(b.String()) {
74 | assert.Equal(t, expected, b.String())
75 | }
76 | }
77 |
78 | // Ensures that a blank string can be encoded.
79 | func TestWriteBlankString(t *testing.T) {
80 | var b bytes.Buffer
81 | w := NewWriter(&b)
82 | w.WriteString("")
83 | assert.NoError(t, w.Flush())
84 | assert.Equal(t, b.String(), `""`)
85 | }
86 |
87 | func BenchmarkWriteRawBytes(b *testing.B) {
88 | s := "hello, world"
89 | var w bytes.Buffer
90 | for i := 0; i < b.N; i++ {
91 | if _, err := w.Write([]byte(s)); err != nil {
92 | b.Fatal("WriteRawBytes:", err)
93 | }
94 | }
95 | b.SetBytes(int64(len(s)))
96 | }
97 |
98 | func BenchmarkWriteString(b *testing.B) {
99 | var buf bytes.Buffer
100 | w := NewWriter(&buf)
101 | s := "hello, world"
102 | for i := 0; i < b.N; i++ {
103 | if err := w.WriteString(s); err != nil {
104 | b.Fatal("WriteString:", err)
105 | }
106 | }
107 | w.Flush()
108 |
109 | b.SetBytes(int64(len(s)))
110 | }
111 |
112 | // Ensures that an int can be written.
113 | func TestWriteInt(t *testing.T) {
114 | var b bytes.Buffer
115 | w := NewWriter(&b)
116 | assert.NoError(t, w.WriteInt(-100))
117 | assert.NoError(t, w.Flush())
118 | assert.Equal(t, b.String(), `-100`)
119 | }
120 |
121 | // Ensures that a uint can be written.
122 | func TestWriteUint(t *testing.T) {
123 | var b bytes.Buffer
124 | w := NewWriter(&b)
125 | assert.NoError(t, w.WriteUint(uint(1230928137)))
126 | assert.NoError(t, w.Flush())
127 | assert.Equal(t, b.String(), `1230928137`)
128 | }
129 |
130 | func BenchmarkWriteInt(b *testing.B) {
131 | var buf bytes.Buffer
132 | w := NewWriter(&buf)
133 | v := -3
134 | for i := 0; i < b.N; i++ {
135 | if err := w.WriteInt(v); err != nil {
136 | b.Fatal("WriteInt:", err)
137 | }
138 | }
139 | w.Flush()
140 | b.SetBytes(int64(len("-3")))
141 | }
142 |
143 | func BenchmarkWriteUint(b *testing.B) {
144 | var buf bytes.Buffer
145 | w := NewWriter(&buf)
146 | v := uint(30)
147 | for i := 0; i < b.N; i++ {
148 | if err := w.WriteUint(v); err != nil {
149 | b.Fatal("WriteUint:", err)
150 | }
151 | }
152 | b.SetBytes(int64(len("30")))
153 | }
154 |
155 | // Ensures that a float32 can be written.
156 | func TestWriteFloat32(t *testing.T) {
157 | var b bytes.Buffer
158 | w := NewWriter(&b)
159 | assert.NoError(t, w.WriteFloat32(float32(2319.1921)))
160 | assert.NoError(t, w.Flush())
161 | assert.Equal(t, b.String(), `2319.1921`)
162 | }
163 |
164 | // Ensures that a float64 can be written.
165 | func TestWriteFloat64(t *testing.T) {
166 | var b bytes.Buffer
167 | w := NewWriter(&b)
168 | assert.NoError(t, w.WriteFloat64(2319123.1921918273))
169 | assert.NoError(t, w.Flush())
170 | assert.Equal(t, b.String(), `2.319123192191827e+06`)
171 | }
172 |
173 | // Ensures that a simple map can be written.
174 | func TestWriteSimpleMap(t *testing.T) {
175 | var b bytes.Buffer
176 | w := NewWriter(&b)
177 | m := map[string]interface{}{
178 | "foo": "bar",
179 | "bat": "baz",
180 | }
181 | assert.NoError(t, w.WriteMap(m))
182 | assert.NoError(t, w.Flush())
183 | if b.String() != `{"foo":"bar","bat":"baz"}` && b.String() != `{"foo":"bar","bat":"baz"}` {
184 | t.Fatal("Invalid map encoding:", b.String())
185 | }
186 | }
187 |
188 | // Ensures that a more complex map can be written.
189 | func TestWriteMap(t *testing.T) {
190 | var b bytes.Buffer
191 | w := NewWriter(&b)
192 | m := map[string]interface{}{
193 | "stringx": "foo",
194 | "intx": 100,
195 | "int64x": int64(1023),
196 | "uintx": uint(100),
197 | "uint64x": uint64(1023),
198 | "float32x": float32(312.311),
199 | "float64x": float64(812731.19812),
200 | "truex": true,
201 | "falsex": false,
202 | "nullx": nil,
203 | }
204 | assert.NoError(t, w.WriteMap(m))
205 | assert.NoError(t, w.Flush())
206 | assert.Contains(t, b.String(), `"intx":100`)
207 | assert.Contains(t, b.String(), `"int64x":1023`)
208 | assert.Contains(t, b.String(), `"uint64x":1023`)
209 | assert.Contains(t, b.String(), `"float32x":312.311`)
210 | assert.Contains(t, b.String(), `"float64x":812731.19812`)
211 | assert.Contains(t, b.String(), `"falsex":false`)
212 | assert.Contains(t, b.String(), `"nullx":null`)
213 | assert.Contains(t, b.String(), `"falsex":false`)
214 | assert.Contains(t, b.String(), `"stringx":"foo"`)
215 | assert.Contains(t, b.String(), `"uintx":100`)
216 | assert.Contains(t, b.String(), `"truex":true`)
217 | }
218 |
219 | // Ensures that a nested map can be written.
220 | func TestWriteNestedMap(t *testing.T) {
221 | var b bytes.Buffer
222 | w := NewWriter(&b)
223 | m := map[string]interface{}{
224 | "foo": map[string]interface{}{"bar": "bat"},
225 | }
226 | assert.NoError(t, w.WriteMap(m))
227 | assert.NoError(t, w.Flush())
228 | assert.Equal(t, b.String(), `{"foo":{"bar":"bat"}}`)
229 | }
230 |
231 | func BenchmarkWriteFloat32(b *testing.B) {
232 | var buf bytes.Buffer
233 | w := NewWriter(&buf)
234 | v := float32(2319.1921)
235 | for i := 0; i < b.N; i++ {
236 | if err := w.WriteFloat32(v); err != nil {
237 | b.Fatal("WriteFloat32:", err)
238 | }
239 | }
240 | w.Flush()
241 | b.SetBytes(int64(len("2319.1921")))
242 | }
243 |
244 | func BenchmarkWriteFloat64(b *testing.B) {
245 | var buf bytes.Buffer
246 | w := NewWriter(&buf)
247 | v := 2319123.1921918273
248 | for i := 0; i < b.N; i++ {
249 | if err := w.WriteFloat64(v); err != nil {
250 | b.Fatal("WriteFloat64:", err)
251 | }
252 | }
253 | w.Flush()
254 | b.SetBytes(int64(len(`2.319123192191827e+06`)))
255 | }
256 |
257 | // Ensures that a single byte can be written to the writer.
258 | func TestWriteByte(t *testing.T) {
259 | var b bytes.Buffer
260 | w := NewWriter(&b)
261 | assert.NoError(t, w.WriteByte(':'))
262 | assert.NoError(t, w.Flush())
263 | assert.Equal(t, b.String(), `:`)
264 | }
265 |
266 | // Ensures that a true boolean value can be written.
267 | func TestWriteTrue(t *testing.T) {
268 | var b bytes.Buffer
269 | w := NewWriter(&b)
270 | assert.NoError(t, w.WriteBool(true))
271 | assert.NoError(t, w.Flush())
272 | assert.Equal(t, b.String(), `true`)
273 | }
274 |
275 | // Ensures that a false boolean value can be written.
276 | func TestWriteFalse(t *testing.T) {
277 | var b bytes.Buffer
278 | w := NewWriter(&b)
279 | assert.NoError(t, w.WriteBool(false))
280 | assert.NoError(t, w.Flush())
281 | assert.Equal(t, b.String(), `false`)
282 | }
283 |
284 | func BenchmarkWriteBool(b *testing.B) {
285 | var buf bytes.Buffer
286 | w := NewWriter(&buf)
287 | for i := 0; i < b.N; i++ {
288 | if err := w.WriteBool(true); err != nil {
289 | b.Fatal("WriteBool:", err)
290 | }
291 | }
292 | w.Flush()
293 | b.SetBytes(int64(len(`true`)))
294 | }
295 |
296 | // Ensures that a null value can be written.
297 | func TestWriteNull(t *testing.T) {
298 | var b bytes.Buffer
299 | w := NewWriter(&b)
300 | assert.NoError(t, w.WriteNull())
301 | assert.NoError(t, w.Flush())
302 | assert.Equal(t, b.String(), `null`)
303 | }
304 |
305 | func BenchmarkWriteNull(b *testing.B) {
306 | var buf bytes.Buffer
307 | w := NewWriter(&buf)
308 | for i := 0; i < b.N; i++ {
309 | if err := w.WriteNull(); err != nil {
310 | b.Fatal("WriteNull:", err)
311 | }
312 | }
313 | w.Flush()
314 | b.SetBytes(int64(len(`true`)))
315 | }
316 |
--------------------------------------------------------------------------------