├── .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 ![status](https://img.shields.io/badge/status-unmaintained-red.svg) 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 | --------------------------------------------------------------------------------