├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── arena.go ├── arena_test.go ├── arena_timing_test.go ├── doc.go ├── fastfloat ├── parse.go ├── parse_test.go └── parse_timing_test.go ├── fuzz.go ├── go.mod ├── handy.go ├── handy_example_test.go ├── handy_test.go ├── parser.go ├── parser_example_test.go ├── parser_test.go ├── parser_timing_test.go ├── pool.go ├── scanner.go ├── scanner_example_test.go ├── scanner_test.go ├── testdata ├── canada.json ├── citm_catalog.json ├── large.json ├── medium.json ├── small.json └── twitter.json ├── update.go ├── update_example_test.go ├── update_test.go ├── util.go ├── util_test.go ├── validate.go ├── validate_test.go └── validate_timing_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | tags 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.10.x 5 | 6 | script: 7 | # build test for supported platforms 8 | - GOOS=linux go build 9 | - GOOS=darwin go build 10 | - GOOS=freebsd go build 11 | - GOOS=windows go build 12 | 13 | # run tests on a standard platform 14 | - go test -v ./... -coverprofile=coverage.txt -covermode=atomic 15 | - go test -v ./... -race 16 | 17 | after_success: 18 | # Upload coverage results to codecov.io 19 | - bash <(curl -s https://codecov.io/bash) 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Aliaksandr Valialkin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/valyala/fastjson.svg)](https://travis-ci.org/valyala/fastjson) 2 | [![GoDoc](https://godoc.org/github.com/valyala/fastjson?status.svg)](http://godoc.org/github.com/valyala/fastjson) 3 | [![Go Report](https://goreportcard.com/badge/github.com/valyala/fastjson)](https://goreportcard.com/report/github.com/valyala/fastjson) 4 | [![codecov](https://codecov.io/gh/valyala/fastjson/branch/master/graph/badge.svg)](https://codecov.io/gh/valyala/fastjson) 5 | 6 | # fastjson - fast JSON parser and validator for Go 7 | 8 | 9 | ## Features 10 | 11 | * Fast. As usual, up to 15x faster than the standard [encoding/json](https://golang.org/pkg/encoding/json/). 12 | See [benchmarks](#benchmarks). 13 | * Parses arbitrary JSON without schema, reflection, struct magic and code generation 14 | contrary to [easyjson](https://github.com/mailru/easyjson). 15 | * Provides simple [API](http://godoc.org/github.com/valyala/fastjson). 16 | * Outperforms [jsonparser](https://github.com/buger/jsonparser) and [gjson](https://github.com/tidwall/gjson) 17 | when accessing multiple unrelated fields, since `fastjson` parses the input JSON only once. 18 | * Validates the parsed JSON unlike [jsonparser](https://github.com/buger/jsonparser) 19 | and [gjson](https://github.com/tidwall/gjson). 20 | * May quickly extract a part of the original JSON with `Value.Get(...).MarshalTo` and modify it 21 | with [Del](https://godoc.org/github.com/valyala/fastjson#Value.Del) 22 | and [Set](https://godoc.org/github.com/valyala/fastjson#Value.Set) functions. 23 | * May parse array containing values with distinct types (aka non-homogenous types). 24 | For instance, `fastjson` easily parses the following JSON array `[123, "foo", [456], {"k": "v"}, null]`. 25 | * `fastjson` preserves the original order of object items when calling 26 | [Object.Visit](https://godoc.org/github.com/valyala/fastjson#Object.Visit). 27 | 28 | 29 | ## Known limitations 30 | 31 | * Requies extra care to work with - references to certain objects recursively 32 | returned by [Parser](https://godoc.org/github.com/valyala/fastjson#Parser) 33 | must be released before the next call to [Parse](https://godoc.org/github.com/valyala/fastjson#Parser.Parse). 34 | Otherwise the program may work improperly. The same applies to objects returned by [Arena](https://godoc.org/github.com/valyala/fastjson#Arena). 35 | Adhere recommendations from [docs](https://godoc.org/github.com/valyala/fastjson). 36 | * Cannot parse JSON from `io.Reader`. There is [Scanner](https://godoc.org/github.com/valyala/fastjson#Scanner) 37 | for parsing stream of JSON values from a string. 38 | 39 | 40 | ## Usage 41 | 42 | One-liner accessing a single field: 43 | ```go 44 | s := []byte(`{"foo": [123, "bar"]}`) 45 | fmt.Printf("foo.0=%d\n", fastjson.GetInt(s, "foo", "0")) 46 | 47 | // Output: 48 | // foo.0=123 49 | ``` 50 | 51 | Accessing multiple fields with error handling: 52 | ```go 53 | var p fastjson.Parser 54 | v, err := p.Parse(`{ 55 | "str": "bar", 56 | "int": 123, 57 | "float": 1.23, 58 | "bool": true, 59 | "arr": [1, "foo", {}] 60 | }`) 61 | if err != nil { 62 | log.Fatal(err) 63 | } 64 | fmt.Printf("foo=%s\n", v.GetStringBytes("str")) 65 | fmt.Printf("int=%d\n", v.GetInt("int")) 66 | fmt.Printf("float=%f\n", v.GetFloat64("float")) 67 | fmt.Printf("bool=%v\n", v.GetBool("bool")) 68 | fmt.Printf("arr.1=%s\n", v.GetStringBytes("arr", "1")) 69 | 70 | // Output: 71 | // foo=bar 72 | // int=123 73 | // float=1.230000 74 | // bool=true 75 | // arr.1=foo 76 | ``` 77 | 78 | See also [examples](https://godoc.org/github.com/valyala/fastjson#pkg-examples). 79 | 80 | 81 | ## Security 82 | 83 | * `fastjson` shouldn't crash or panic when parsing input strings specially crafted 84 | by an attacker. It must return error on invalid input JSON. 85 | * `fastjson` requires up to `sizeof(Value) * len(inputJSON)` bytes of memory 86 | for parsing `inputJSON` string. Limit the maximum size of the `inputJSON` 87 | before parsing it in order to limit the maximum memory usage. 88 | 89 | 90 | ## Performance optimization tips 91 | 92 | * Re-use [Parser](https://godoc.org/github.com/valyala/fastjson#Parser) and [Scanner](https://godoc.org/github.com/valyala/fastjson#Scanner) 93 | for parsing many JSONs. This reduces memory allocations overhead. 94 | [ParserPool](https://godoc.org/github.com/valyala/fastjson#ParserPool) may be useful in this case. 95 | * Prefer calling `Value.Get*` on the value returned from [Parser](https://godoc.org/github.com/valyala/fastjson#Parser) 96 | instead of calling `Get*` one-liners when multiple fields 97 | must be obtained from JSON, since each `Get*` one-liner re-parses 98 | the input JSON again. 99 | * Prefer calling once [Value.Get](https://godoc.org/github.com/valyala/fastjson#Value.Get) 100 | for common prefix paths and then calling `Value.Get*` on the returned value 101 | for distinct suffix paths. 102 | * Prefer iterating over array returned from [Value.GetArray](https://godoc.org/github.com/valyala/fastjson#Object.Visit) 103 | with a range loop instead of calling `Value.Get*` for each array item. 104 | 105 | ## Fuzzing 106 | Install [go-fuzz](https://github.com/dvyukov/go-fuzz) & optionally the go-fuzz-corpus. 107 | 108 | ```bash 109 | go get -u github.com/dvyukov/go-fuzz/go-fuzz github.com/dvyukov/go-fuzz/go-fuzz-build 110 | ``` 111 | 112 | Build using `go-fuzz-build` and run `go-fuzz` with an optional corpus. 113 | 114 | ```bash 115 | mkdir -p workdir/corpus 116 | cp $GOPATH/src/github.com/dvyukov/go-fuzz-corpus/json/corpus/* workdir/corpus 117 | go-fuzz-build github.com/valyala/fastjson 118 | go-fuzz -bin=fastjson-fuzz.zip -workdir=workdir 119 | ``` 120 | 121 | ## Benchmarks 122 | 123 | Go 1.12 has been used for benchmarking. 124 | 125 | Legend: 126 | 127 | * `small` - parse [small.json](testdata/small.json) (190 bytes). 128 | * `medium` - parse [medium.json](testdata/medium.json) (2.3KB). 129 | * `large` - parse [large.json](testdata/large.json) (28KB). 130 | * `canada` - parse [canada.json](testdata/canada.json) (2.2MB). 131 | * `citm` - parse [citm_catalog.json](testdata/citm_catalog.json) (1.7MB). 132 | * `twitter` - parse [twitter.json](testdata/twitter.json) (617KB). 133 | 134 | * `stdjson-map` - parse into a `map[string]interface{}` using `encoding/json`. 135 | * `stdjson-struct` - parse into a struct containing 136 | a subset of fields of the parsed JSON, using `encoding/json`. 137 | * `stdjson-empty-struct` - parse into an empty struct using `encoding/json`. 138 | This is the fastest possible solution for `encoding/json`, may be used 139 | for json validation. See also benchmark results for json validation. 140 | * `fastjson` - parse using `fastjson` without fields access. 141 | * `fastjson-get` - parse using `fastjson` with fields access similar to `stdjson-struct`. 142 | 143 | ``` 144 | $ GOMAXPROCS=1 go test github.com/valyala/fastjson -bench='Parse$' 145 | goos: linux 146 | goarch: amd64 147 | pkg: github.com/valyala/fastjson 148 | BenchmarkParse/small/stdjson-map 200000 7305 ns/op 26.01 MB/s 960 B/op 51 allocs/op 149 | BenchmarkParse/small/stdjson-struct 500000 3431 ns/op 55.37 MB/s 224 B/op 4 allocs/op 150 | BenchmarkParse/small/stdjson-empty-struct 500000 2273 ns/op 83.58 MB/s 168 B/op 2 allocs/op 151 | BenchmarkParse/small/fastjson 5000000 347 ns/op 547.53 MB/s 0 B/op 0 allocs/op 152 | BenchmarkParse/small/fastjson-get 2000000 620 ns/op 306.39 MB/s 0 B/op 0 allocs/op 153 | BenchmarkParse/medium/stdjson-map 30000 40672 ns/op 57.26 MB/s 10196 B/op 208 allocs/op 154 | BenchmarkParse/medium/stdjson-struct 30000 47792 ns/op 48.73 MB/s 9174 B/op 258 allocs/op 155 | BenchmarkParse/medium/stdjson-empty-struct 100000 22096 ns/op 105.40 MB/s 280 B/op 5 allocs/op 156 | BenchmarkParse/medium/fastjson 500000 3025 ns/op 769.90 MB/s 0 B/op 0 allocs/op 157 | BenchmarkParse/medium/fastjson-get 500000 3211 ns/op 725.20 MB/s 0 B/op 0 allocs/op 158 | BenchmarkParse/large/stdjson-map 2000 614079 ns/op 45.79 MB/s 210734 B/op 2785 allocs/op 159 | BenchmarkParse/large/stdjson-struct 5000 298554 ns/op 94.18 MB/s 15616 B/op 353 allocs/op 160 | BenchmarkParse/large/stdjson-empty-struct 5000 268577 ns/op 104.69 MB/s 280 B/op 5 allocs/op 161 | BenchmarkParse/large/fastjson 50000 35210 ns/op 798.56 MB/s 5 B/op 0 allocs/op 162 | BenchmarkParse/large/fastjson-get 50000 35171 ns/op 799.46 MB/s 5 B/op 0 allocs/op 163 | BenchmarkParse/canada/stdjson-map 20 68147307 ns/op 33.03 MB/s 12260502 B/op 392539 allocs/op 164 | BenchmarkParse/canada/stdjson-struct 20 68044518 ns/op 33.08 MB/s 12260123 B/op 392534 allocs/op 165 | BenchmarkParse/canada/stdjson-empty-struct 100 17709250 ns/op 127.11 MB/s 280 B/op 5 allocs/op 166 | BenchmarkParse/canada/fastjson 300 4182404 ns/op 538.22 MB/s 254902 B/op 381 allocs/op 167 | BenchmarkParse/canada/fastjson-get 300 4274744 ns/op 526.60 MB/s 254902 B/op 381 allocs/op 168 | BenchmarkParse/citm/stdjson-map 50 27772612 ns/op 62.19 MB/s 5214163 B/op 95402 allocs/op 169 | BenchmarkParse/citm/stdjson-struct 100 14936191 ns/op 115.64 MB/s 1989 B/op 75 allocs/op 170 | BenchmarkParse/citm/stdjson-empty-struct 100 14946034 ns/op 115.56 MB/s 280 B/op 5 allocs/op 171 | BenchmarkParse/citm/fastjson 1000 1879714 ns/op 918.87 MB/s 17628 B/op 30 allocs/op 172 | BenchmarkParse/citm/fastjson-get 1000 1881598 ns/op 917.94 MB/s 17628 B/op 30 allocs/op 173 | BenchmarkParse/twitter/stdjson-map 100 11289146 ns/op 55.94 MB/s 2187878 B/op 31266 allocs/op 174 | BenchmarkParse/twitter/stdjson-struct 300 5779442 ns/op 109.27 MB/s 408 B/op 6 allocs/op 175 | BenchmarkParse/twitter/stdjson-empty-struct 300 5738504 ns/op 110.05 MB/s 408 B/op 6 allocs/op 176 | BenchmarkParse/twitter/fastjson 2000 774042 ns/op 815.86 MB/s 2541 B/op 2 allocs/op 177 | BenchmarkParse/twitter/fastjson-get 2000 777833 ns/op 811.89 MB/s 2541 B/op 2 allocs/op 178 | ``` 179 | 180 | Benchmark results for json validation: 181 | 182 | ``` 183 | $ GOMAXPROCS=1 go test github.com/valyala/fastjson -bench='Validate$' 184 | goos: linux 185 | goarch: amd64 186 | pkg: github.com/valyala/fastjson 187 | BenchmarkValidate/small/stdjson 2000000 955 ns/op 198.83 MB/s 72 B/op 2 allocs/op 188 | BenchmarkValidate/small/fastjson 5000000 384 ns/op 493.60 MB/s 0 B/op 0 allocs/op 189 | BenchmarkValidate/medium/stdjson 200000 10799 ns/op 215.66 MB/s 184 B/op 5 allocs/op 190 | BenchmarkValidate/medium/fastjson 300000 3809 ns/op 611.30 MB/s 0 B/op 0 allocs/op 191 | BenchmarkValidate/large/stdjson 10000 133064 ns/op 211.31 MB/s 184 B/op 5 allocs/op 192 | BenchmarkValidate/large/fastjson 30000 45268 ns/op 621.14 MB/s 0 B/op 0 allocs/op 193 | BenchmarkValidate/canada/stdjson 200 8470904 ns/op 265.74 MB/s 184 B/op 5 allocs/op 194 | BenchmarkValidate/canada/fastjson 500 2973377 ns/op 757.07 MB/s 0 B/op 0 allocs/op 195 | BenchmarkValidate/citm/stdjson 200 7273172 ns/op 237.48 MB/s 184 B/op 5 allocs/op 196 | BenchmarkValidate/citm/fastjson 1000 1684430 ns/op 1025.39 MB/s 0 B/op 0 allocs/op 197 | BenchmarkValidate/twitter/stdjson 500 2849439 ns/op 221.63 MB/s 312 B/op 6 allocs/op 198 | BenchmarkValidate/twitter/fastjson 2000 1036796 ns/op 609.10 MB/s 0 B/op 0 allocs/op 199 | ``` 200 | 201 | ## FAQ 202 | 203 | * Q: _There are a ton of other high-perf packages for JSON parsing in Go. Why creating yet another package?_ 204 | A: Because other packages require either rigid JSON schema via struct magic 205 | and code generation or perform poorly when multiple unrelated fields 206 | must be obtained from the parsed JSON. 207 | Additionally, `fastjson` provides nicer [API](http://godoc.org/github.com/valyala/fastjson). 208 | 209 | * Q: _What is the main purpose for `fastjson`?_ 210 | A: High-perf JSON parsing for [RTB](https://www.iab.com/wp-content/uploads/2015/05/OpenRTB_API_Specification_Version_2_3_1.pdf) 211 | and other [JSON-RPC](https://en.wikipedia.org/wiki/JSON-RPC) services. 212 | 213 | * Q: _Why fastjson doesn't provide fast marshaling (serialization)?_ 214 | A: Actually it provides some sort of marshaling - see [Value.MarshalTo](https://godoc.org/github.com/valyala/fastjson#Value.MarshalTo). 215 | But I'd recommend using [quicktemplate](https://github.com/valyala/quicktemplate#use-cases) 216 | for high-performance JSON marshaling :) 217 | 218 | * Q: _`fastjson` crashes my program!_ 219 | A: There is high probability of improper use. 220 | * Make sure you don't hold references to objects recursively returned by `Parser` / `Scanner` 221 | beyond the next `Parser.Parse` / `Scanner.Next` call 222 | if such restriction is mentioned in [docs](https://github.com/valyala/fastjson/issues/new). 223 | * Make sure you don't access `fastjson` objects from concurrently running goroutines 224 | if such restriction is mentioned in [docs](https://github.com/valyala/fastjson/issues/new). 225 | * Build and run your program with [-race](https://golang.org/doc/articles/race_detector.html) flag. 226 | Make sure the race detector detects zero races. 227 | * If your program continue crashing after fixing issues mentioned above, [file a bug](https://github.com/valyala/fastjson/issues/new). 228 | -------------------------------------------------------------------------------- /arena.go: -------------------------------------------------------------------------------- 1 | package fastjson 2 | 3 | import ( 4 | "strconv" 5 | ) 6 | 7 | // Arena may be used for fast creation and re-use of Values. 8 | // 9 | // Typical Arena lifecycle: 10 | // 11 | // 1) Construct Values via the Arena and Value.Set* calls. 12 | // 2) Marshal the constructed Values with Value.MarshalTo call. 13 | // 3) Reset all the constructed Values at once by Arena.Reset call. 14 | // 4) Go to 1 and re-use the Arena. 15 | // 16 | // It is unsafe calling Arena methods from concurrent goroutines. 17 | // Use per-goroutine Arenas or ArenaPool instead. 18 | type Arena struct { 19 | b []byte 20 | c cache 21 | } 22 | 23 | // Reset resets all the Values allocated by a. 24 | // 25 | // Values previously allocated by a cannot be used after the Reset call. 26 | func (a *Arena) Reset() { 27 | a.b = a.b[:0] 28 | a.c.reset() 29 | } 30 | 31 | // NewObject returns new empty object value. 32 | // 33 | // New entries may be added to the returned object via Set call. 34 | // 35 | // The returned object is valid until Reset is called on a. 36 | func (a *Arena) NewObject() *Value { 37 | v := a.c.getValue() 38 | v.t = TypeObject 39 | v.o.reset() 40 | return v 41 | } 42 | 43 | // NewArray returns new empty array value. 44 | // 45 | // New entries may be added to the returned array via Set* calls. 46 | // 47 | // The returned array is valid until Reset is called on a. 48 | func (a *Arena) NewArray() *Value { 49 | v := a.c.getValue() 50 | v.t = TypeArray 51 | v.a = v.a[:0] 52 | return v 53 | } 54 | 55 | // NewString returns new string value containing s. 56 | // 57 | // The returned string is valid until Reset is called on a. 58 | func (a *Arena) NewString(s string) *Value { 59 | v := a.c.getValue() 60 | v.t = typeRawString 61 | bLen := len(a.b) 62 | a.b = escapeString(a.b, s) 63 | v.s = b2s(a.b[bLen+1 : len(a.b)-1]) 64 | return v 65 | } 66 | 67 | // NewStringBytes returns new string value containing b. 68 | // 69 | // The returned string is valid until Reset is called on a. 70 | func (a *Arena) NewStringBytes(b []byte) *Value { 71 | v := a.c.getValue() 72 | v.t = typeRawString 73 | bLen := len(a.b) 74 | a.b = escapeString(a.b, b2s(b)) 75 | v.s = b2s(a.b[bLen+1 : len(a.b)-1]) 76 | return v 77 | } 78 | 79 | // NewNumberFloat64 returns new number value containing f. 80 | // 81 | // The returned number is valid until Reset is called on a. 82 | func (a *Arena) NewNumberFloat64(f float64) *Value { 83 | v := a.c.getValue() 84 | v.t = TypeNumber 85 | bLen := len(a.b) 86 | a.b = strconv.AppendFloat(a.b, f, 'g', -1, 64) 87 | v.s = b2s(a.b[bLen:]) 88 | return v 89 | } 90 | 91 | // NewNumberInt returns new number value containing n. 92 | // 93 | // The returned number is valid until Reset is called on a. 94 | func (a *Arena) NewNumberInt(n int) *Value { 95 | v := a.c.getValue() 96 | v.t = TypeNumber 97 | bLen := len(a.b) 98 | a.b = strconv.AppendInt(a.b, int64(n), 10) 99 | v.s = b2s(a.b[bLen:]) 100 | return v 101 | } 102 | 103 | // NewNumberString returns new number value containing s. 104 | // 105 | // The returned number is valid until Reset is called on a. 106 | func (a *Arena) NewNumberString(s string) *Value { 107 | v := a.c.getValue() 108 | v.t = TypeNumber 109 | v.s = s 110 | return v 111 | } 112 | 113 | // NewNull returns null value. 114 | func (a *Arena) NewNull() *Value { 115 | return valueNull 116 | } 117 | 118 | // NewTrue returns true value. 119 | func (a *Arena) NewTrue() *Value { 120 | return valueTrue 121 | } 122 | 123 | // NewFalse return false value. 124 | func (a *Arena) NewFalse() *Value { 125 | return valueFalse 126 | } 127 | -------------------------------------------------------------------------------- /arena_test.go: -------------------------------------------------------------------------------- 1 | package fastjson 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestArena(t *testing.T) { 10 | t.Run("serial", func(t *testing.T) { 11 | var a Arena 12 | for i := 0; i < 10; i++ { 13 | if err := testArena(&a); err != nil { 14 | t.Fatal(err) 15 | } 16 | a.Reset() 17 | } 18 | }) 19 | t.Run("concurrent", func(t *testing.T) { 20 | var ap ArenaPool 21 | workers := 4 22 | ch := make(chan error, workers) 23 | for i := 0; i < workers; i++ { 24 | go func() { 25 | a := ap.Get() 26 | defer ap.Put(a) 27 | var err error 28 | for i := 0; i < 10; i++ { 29 | if err = testArena(a); err != nil { 30 | break 31 | } 32 | } 33 | ch <- err 34 | }() 35 | } 36 | for i := 0; i < workers; i++ { 37 | select { 38 | case err := <-ch: 39 | if err != nil { 40 | t.Fatal(err) 41 | } 42 | case <-time.After(time.Second): 43 | t.Fatalf("timeout") 44 | } 45 | } 46 | }) 47 | } 48 | 49 | func testArena(a *Arena) error { 50 | o := a.NewObject() 51 | o.Set("nil1", a.NewNull()) 52 | o.Set("nil2", nil) 53 | o.Set("false", a.NewFalse()) 54 | o.Set("true", a.NewTrue()) 55 | ni := a.NewNumberInt(123) 56 | o.Set("ni", ni) 57 | o.Set("nf", a.NewNumberFloat64(1.23)) 58 | o.Set("ns", a.NewNumberString("34.43")) 59 | s := a.NewString("foo") 60 | o.Set("str1", s) 61 | o.Set("str2", a.NewStringBytes([]byte("xx"))) 62 | 63 | aa := a.NewArray() 64 | aa.SetArrayItem(0, s) 65 | aa.Set("1", ni) 66 | o.Set("a", aa) 67 | obj := a.NewObject() 68 | obj.Set("s", s) 69 | o.Set("obj", obj) 70 | 71 | str := o.String() 72 | strExpected := `{"nil1":null,"nil2":null,"false":false,"true":true,"ni":123,"nf":1.23,"ns":34.43,"str1":"foo","str2":"xx","a":["foo",123],"obj":{"s":"foo"}}` 73 | if str != strExpected { 74 | return fmt.Errorf("unexpected json\ngot\n%s\nwant\n%s", str, strExpected) 75 | } 76 | return nil 77 | } 78 | -------------------------------------------------------------------------------- /arena_timing_test.go: -------------------------------------------------------------------------------- 1 | package fastjson 2 | 3 | import ( 4 | "sync/atomic" 5 | "testing" 6 | ) 7 | 8 | func BenchmarkArenaTypicalUse(b *testing.B) { 9 | // Determine the length of created object 10 | var aa Arena 11 | obj := benchCreateArenaObject(&aa) 12 | objLen := len(obj.String()) 13 | b.SetBytes(int64(objLen)) 14 | b.ReportAllocs() 15 | b.RunParallel(func(pb *testing.PB) { 16 | var buf []byte 17 | var a Arena 18 | var sink int 19 | for pb.Next() { 20 | obj := benchCreateArenaObject(&a) 21 | buf = obj.MarshalTo(buf[:0]) 22 | a.Reset() 23 | sink += len(buf) 24 | } 25 | atomic.AddUint64(&Sink, uint64(sink)) 26 | }) 27 | } 28 | 29 | func benchCreateArenaObject(a *Arena) *Value { 30 | o := a.NewObject() 31 | o.Set("key1", a.NewNumberInt(123)) 32 | o.Set("key2", a.NewNumberFloat64(-1.23)) 33 | 34 | // Create a string only once and use multuple times as a performance optimization. 35 | s := a.NewString("foobar") 36 | aa := a.NewArray() 37 | for i := 0; i < 10; i++ { 38 | aa.SetArrayItem(i, s) 39 | } 40 | o.Set("key3", aa) 41 | return o 42 | } 43 | 44 | var Sink uint64 45 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package fastjson provides fast JSON parsing. 3 | 4 | Arbitrary JSON may be parsed by fastjson without the need for creating structs 5 | or for generating go code. Just parse JSON and get the required fields with 6 | Get* functions. 7 | 8 | */ 9 | package fastjson 10 | -------------------------------------------------------------------------------- /fastfloat/parse.go: -------------------------------------------------------------------------------- 1 | package fastfloat 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | // ParseUint64BestEffort parses uint64 number s. 11 | // 12 | // It is equivalent to strconv.ParseUint(s, 10, 64), but is faster. 13 | // 14 | // 0 is returned if the number cannot be parsed. 15 | // See also ParseUint64, which returns parse error if the number cannot be parsed. 16 | func ParseUint64BestEffort(s string) uint64 { 17 | if len(s) == 0 { 18 | return 0 19 | } 20 | i := uint(0) 21 | d := uint64(0) 22 | j := i 23 | for i < uint(len(s)) { 24 | if s[i] >= '0' && s[i] <= '9' { 25 | d = d*10 + uint64(s[i]-'0') 26 | i++ 27 | if i > 18 { 28 | // The integer part may be out of range for uint64. 29 | // Fall back to slow parsing. 30 | dd, err := strconv.ParseUint(s, 10, 64) 31 | if err != nil { 32 | return 0 33 | } 34 | return dd 35 | } 36 | continue 37 | } 38 | break 39 | } 40 | if i <= j { 41 | return 0 42 | } 43 | if i < uint(len(s)) { 44 | // Unparsed tail left. 45 | return 0 46 | } 47 | return d 48 | } 49 | 50 | // ParseUint64 parses uint64 from s. 51 | // 52 | // It is equivalent to strconv.ParseUint(s, 10, 64), but is faster. 53 | // 54 | // See also ParseUint64BestEffort. 55 | func ParseUint64(s string) (uint64, error) { 56 | if len(s) == 0 { 57 | return 0, fmt.Errorf("cannot parse uint64 from empty string") 58 | } 59 | i := uint(0) 60 | d := uint64(0) 61 | j := i 62 | for i < uint(len(s)) { 63 | if s[i] >= '0' && s[i] <= '9' { 64 | d = d*10 + uint64(s[i]-'0') 65 | i++ 66 | if i > 18 { 67 | // The integer part may be out of range for uint64. 68 | // Fall back to slow parsing. 69 | dd, err := strconv.ParseUint(s, 10, 64) 70 | if err != nil { 71 | return 0, err 72 | } 73 | return dd, nil 74 | } 75 | continue 76 | } 77 | break 78 | } 79 | if i <= j { 80 | return 0, fmt.Errorf("cannot parse uint64 from %q", s) 81 | } 82 | if i < uint(len(s)) { 83 | // Unparsed tail left. 84 | return 0, fmt.Errorf("unparsed tail left after parsing uint64 from %q: %q", s, s[i:]) 85 | } 86 | return d, nil 87 | } 88 | 89 | // ParseInt64BestEffort parses int64 number s. 90 | // 91 | // It is equivalent to strconv.ParseInt(s, 10, 64), but is faster. 92 | // 93 | // 0 is returned if the number cannot be parsed. 94 | // See also ParseInt64, which returns parse error if the number cannot be parsed. 95 | func ParseInt64BestEffort(s string) int64 { 96 | if len(s) == 0 { 97 | return 0 98 | } 99 | i := uint(0) 100 | minus := s[0] == '-' 101 | if minus { 102 | i++ 103 | if i >= uint(len(s)) { 104 | return 0 105 | } 106 | } 107 | 108 | d := int64(0) 109 | j := i 110 | for i < uint(len(s)) { 111 | if s[i] >= '0' && s[i] <= '9' { 112 | d = d*10 + int64(s[i]-'0') 113 | i++ 114 | if i > 18 { 115 | // The integer part may be out of range for int64. 116 | // Fall back to slow parsing. 117 | dd, err := strconv.ParseInt(s, 10, 64) 118 | if err != nil { 119 | return 0 120 | } 121 | return dd 122 | } 123 | continue 124 | } 125 | break 126 | } 127 | if i <= j { 128 | return 0 129 | } 130 | if i < uint(len(s)) { 131 | // Unparsed tail left. 132 | return 0 133 | } 134 | if minus { 135 | d = -d 136 | } 137 | return d 138 | } 139 | 140 | // ParseInt64 parses int64 number s. 141 | // 142 | // It is equivalent to strconv.ParseInt(s, 10, 64), but is faster. 143 | // 144 | // See also ParseInt64BestEffort. 145 | func ParseInt64(s string) (int64, error) { 146 | if len(s) == 0 { 147 | return 0, fmt.Errorf("cannot parse int64 from empty string") 148 | } 149 | i := uint(0) 150 | minus := s[0] == '-' 151 | if minus { 152 | i++ 153 | if i >= uint(len(s)) { 154 | return 0, fmt.Errorf("cannot parse int64 from %q", s) 155 | } 156 | } 157 | 158 | d := int64(0) 159 | j := i 160 | for i < uint(len(s)) { 161 | if s[i] >= '0' && s[i] <= '9' { 162 | d = d*10 + int64(s[i]-'0') 163 | i++ 164 | if i > 18 { 165 | // The integer part may be out of range for int64. 166 | // Fall back to slow parsing. 167 | dd, err := strconv.ParseInt(s, 10, 64) 168 | if err != nil { 169 | return 0, err 170 | } 171 | return dd, nil 172 | } 173 | continue 174 | } 175 | break 176 | } 177 | if i <= j { 178 | return 0, fmt.Errorf("cannot parse int64 from %q", s) 179 | } 180 | if i < uint(len(s)) { 181 | // Unparsed tail left. 182 | return 0, fmt.Errorf("unparsed tail left after parsing int64 form %q: %q", s, s[i:]) 183 | } 184 | if minus { 185 | d = -d 186 | } 187 | return d, nil 188 | } 189 | 190 | // Exact powers of 10. 191 | // 192 | // This works faster than math.Pow10, since it avoids additional multiplication. 193 | var float64pow10 = [...]float64{ 194 | 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 195 | } 196 | 197 | // ParseBestEffort parses floating-point number s. 198 | // 199 | // It is equivalent to strconv.ParseFloat(s, 64), but is faster. 200 | // 201 | // 0 is returned if the number cannot be parsed. 202 | // See also Parse, which returns parse error if the number cannot be parsed. 203 | func ParseBestEffort(s string) float64 { 204 | if len(s) == 0 { 205 | return 0 206 | } 207 | i := uint(0) 208 | minus := s[0] == '-' 209 | if minus { 210 | i++ 211 | if i >= uint(len(s)) { 212 | return 0 213 | } 214 | } 215 | 216 | // the integer part might be elided to remain compliant 217 | // with https://go.dev/ref/spec#Floating-point_literals 218 | if s[i] == '.' && (i+1 >= uint(len(s)) || s[i+1] < '0' || s[i+1] > '9') { 219 | return 0 220 | } 221 | 222 | d := uint64(0) 223 | j := i 224 | for i < uint(len(s)) { 225 | if s[i] >= '0' && s[i] <= '9' { 226 | d = d*10 + uint64(s[i]-'0') 227 | i++ 228 | if i > 18 { 229 | // The integer part may be out of range for uint64. 230 | // Fall back to slow parsing. 231 | f, err := strconv.ParseFloat(s, 64) 232 | if err != nil && !math.IsInf(f, 0) { 233 | return 0 234 | } 235 | return f 236 | } 237 | continue 238 | } 239 | break 240 | } 241 | if i <= j && s[i] != '.' { 242 | s = s[i:] 243 | if strings.HasPrefix(s, "+") { 244 | s = s[1:] 245 | } 246 | // "infinity" is needed for OpenMetrics support. 247 | // See https://github.com/OpenObservability/OpenMetrics/blob/master/OpenMetrics.md 248 | if strings.EqualFold(s, "inf") || strings.EqualFold(s, "infinity") { 249 | if minus { 250 | return -inf 251 | } 252 | return inf 253 | } 254 | if strings.EqualFold(s, "nan") { 255 | return nan 256 | } 257 | return 0 258 | } 259 | f := float64(d) 260 | if i >= uint(len(s)) { 261 | // Fast path - just integer. 262 | if minus { 263 | f = -f 264 | } 265 | return f 266 | } 267 | 268 | if s[i] == '.' { 269 | // Parse fractional part. 270 | i++ 271 | if i >= uint(len(s)) { 272 | // the fractional part may be elided to remain compliant 273 | // with https://go.dev/ref/spec#Floating-point_literals 274 | return f 275 | } 276 | k := i 277 | for i < uint(len(s)) { 278 | if s[i] >= '0' && s[i] <= '9' { 279 | d = d*10 + uint64(s[i]-'0') 280 | i++ 281 | if i-j >= uint(len(float64pow10)) { 282 | // The mantissa is out of range. Fall back to standard parsing. 283 | f, err := strconv.ParseFloat(s, 64) 284 | if err != nil && !math.IsInf(f, 0) { 285 | return 0 286 | } 287 | return f 288 | } 289 | continue 290 | } 291 | break 292 | } 293 | if i < k { 294 | return 0 295 | } 296 | // Convert the entire mantissa to a float at once to avoid rounding errors. 297 | f = float64(d) / float64pow10[i-k] 298 | if i >= uint(len(s)) { 299 | // Fast path - parsed fractional number. 300 | if minus { 301 | f = -f 302 | } 303 | return f 304 | } 305 | } 306 | if s[i] == 'e' || s[i] == 'E' { 307 | // Parse exponent part. 308 | i++ 309 | if i >= uint(len(s)) { 310 | return 0 311 | } 312 | expMinus := false 313 | if s[i] == '+' || s[i] == '-' { 314 | expMinus = s[i] == '-' 315 | i++ 316 | if i >= uint(len(s)) { 317 | return 0 318 | } 319 | } 320 | exp := int16(0) 321 | j := i 322 | for i < uint(len(s)) { 323 | if s[i] >= '0' && s[i] <= '9' { 324 | exp = exp*10 + int16(s[i]-'0') 325 | i++ 326 | if exp > 300 { 327 | // The exponent may be too big for float64. 328 | // Fall back to standard parsing. 329 | f, err := strconv.ParseFloat(s, 64) 330 | if err != nil && !math.IsInf(f, 0) { 331 | return 0 332 | } 333 | return f 334 | } 335 | continue 336 | } 337 | break 338 | } 339 | if i <= j { 340 | return 0 341 | } 342 | if expMinus { 343 | exp = -exp 344 | } 345 | f *= math.Pow10(int(exp)) 346 | if i >= uint(len(s)) { 347 | if minus { 348 | f = -f 349 | } 350 | return f 351 | } 352 | } 353 | return 0 354 | } 355 | 356 | // Parse parses floating-point number s. 357 | // 358 | // It is equivalent to strconv.ParseFloat(s, 64), but is faster. 359 | // 360 | // See also ParseBestEffort. 361 | func Parse(s string) (float64, error) { 362 | if len(s) == 0 { 363 | return 0, fmt.Errorf("cannot parse float64 from empty string") 364 | } 365 | i := uint(0) 366 | minus := s[0] == '-' 367 | if minus { 368 | i++ 369 | if i >= uint(len(s)) { 370 | return 0, fmt.Errorf("cannot parse float64 from %q", s) 371 | } 372 | } 373 | 374 | // the integer part might be elided to remain compliant 375 | // with https://go.dev/ref/spec#Floating-point_literals 376 | if s[i] == '.' && (i+1 >= uint(len(s)) || s[i+1] < '0' || s[i+1] > '9') { 377 | return 0, fmt.Errorf("missing integer and fractional part in %q", s) 378 | } 379 | 380 | d := uint64(0) 381 | j := i 382 | for i < uint(len(s)) { 383 | if s[i] >= '0' && s[i] <= '9' { 384 | d = d*10 + uint64(s[i]-'0') 385 | i++ 386 | if i > 18 { 387 | // The integer part may be out of range for uint64. 388 | // Fall back to slow parsing. 389 | f, err := strconv.ParseFloat(s, 64) 390 | if err != nil && !math.IsInf(f, 0) { 391 | return 0, err 392 | } 393 | return f, nil 394 | } 395 | continue 396 | } 397 | break 398 | } 399 | if i <= j && s[i] != '.' { 400 | ss := s[i:] 401 | if strings.HasPrefix(ss, "+") { 402 | ss = ss[1:] 403 | } 404 | // "infinity" is needed for OpenMetrics support. 405 | // See https://github.com/OpenObservability/OpenMetrics/blob/master/OpenMetrics.md 406 | if strings.EqualFold(ss, "inf") || strings.EqualFold(ss, "infinity") { 407 | if minus { 408 | return -inf, nil 409 | } 410 | return inf, nil 411 | } 412 | if strings.EqualFold(ss, "nan") { 413 | return nan, nil 414 | } 415 | return 0, fmt.Errorf("unparsed tail left after parsing float64 from %q: %q", s, ss) 416 | } 417 | f := float64(d) 418 | if i >= uint(len(s)) { 419 | // Fast path - just integer. 420 | if minus { 421 | f = -f 422 | } 423 | return f, nil 424 | } 425 | 426 | if s[i] == '.' { 427 | // Parse fractional part. 428 | i++ 429 | if i >= uint(len(s)) { 430 | // the fractional part might be elided to remain compliant 431 | // with https://go.dev/ref/spec#Floating-point_literals 432 | return f, nil 433 | } 434 | k := i 435 | for i < uint(len(s)) { 436 | if s[i] >= '0' && s[i] <= '9' { 437 | d = d*10 + uint64(s[i]-'0') 438 | i++ 439 | if i-j >= uint(len(float64pow10)) { 440 | // The mantissa is out of range. Fall back to standard parsing. 441 | f, err := strconv.ParseFloat(s, 64) 442 | if err != nil && !math.IsInf(f, 0) { 443 | return 0, fmt.Errorf("cannot parse mantissa in %q: %s", s, err) 444 | } 445 | return f, nil 446 | } 447 | continue 448 | } 449 | break 450 | } 451 | if i < k { 452 | return 0, fmt.Errorf("cannot find mantissa in %q", s) 453 | } 454 | // Convert the entire mantissa to a float at once to avoid rounding errors. 455 | f = float64(d) / float64pow10[i-k] 456 | if i >= uint(len(s)) { 457 | // Fast path - parsed fractional number. 458 | if minus { 459 | f = -f 460 | } 461 | return f, nil 462 | } 463 | } 464 | if s[i] == 'e' || s[i] == 'E' { 465 | // Parse exponent part. 466 | i++ 467 | if i >= uint(len(s)) { 468 | return 0, fmt.Errorf("cannot parse exponent in %q", s) 469 | } 470 | expMinus := false 471 | if s[i] == '+' || s[i] == '-' { 472 | expMinus = s[i] == '-' 473 | i++ 474 | if i >= uint(len(s)) { 475 | return 0, fmt.Errorf("cannot parse exponent in %q", s) 476 | } 477 | } 478 | exp := int16(0) 479 | j := i 480 | for i < uint(len(s)) { 481 | if s[i] >= '0' && s[i] <= '9' { 482 | exp = exp*10 + int16(s[i]-'0') 483 | i++ 484 | if exp > 300 { 485 | // The exponent may be too big for float64. 486 | // Fall back to standard parsing. 487 | f, err := strconv.ParseFloat(s, 64) 488 | if err != nil && !math.IsInf(f, 0) { 489 | return 0, fmt.Errorf("cannot parse exponent in %q: %s", s, err) 490 | } 491 | return f, nil 492 | } 493 | continue 494 | } 495 | break 496 | } 497 | if i <= j { 498 | return 0, fmt.Errorf("cannot parse exponent in %q", s) 499 | } 500 | if expMinus { 501 | exp = -exp 502 | } 503 | f *= math.Pow10(int(exp)) 504 | if i >= uint(len(s)) { 505 | if minus { 506 | f = -f 507 | } 508 | return f, nil 509 | } 510 | } 511 | return 0, fmt.Errorf("cannot parse float64 from %q", s) 512 | } 513 | 514 | var inf = math.Inf(1) 515 | var nan = math.NaN() 516 | -------------------------------------------------------------------------------- /fastfloat/parse_test.go: -------------------------------------------------------------------------------- 1 | package fastfloat 2 | 3 | import ( 4 | "math" 5 | "math/rand" 6 | "strconv" 7 | "testing" 8 | ) 9 | 10 | func TestParseUint64BestEffort(t *testing.T) { 11 | f := func(s string, expectedNum uint64) { 12 | t.Helper() 13 | 14 | num := ParseUint64BestEffort(s) 15 | if num != expectedNum { 16 | t.Fatalf("unexpected number parsed from %q; got %v; want %v", s, num, expectedNum) 17 | } 18 | } 19 | 20 | // Invalid first char 21 | f("", 0) 22 | f(" ", 0) 23 | f("foo", 0) 24 | f("-", 0) 25 | f("-foo", 0) 26 | f("-123", 0) 27 | 28 | // Invalid suffix 29 | f("1foo", 0) 30 | f("13223 ", 0) 31 | f("1-2", 0) 32 | 33 | // Int 34 | f("1", 1) 35 | f("123", 123) 36 | f("1234567890", 1234567890) 37 | f("9223372036854775807", 9223372036854775807) 38 | f("18446744073709551615", 18446744073709551615) 39 | 40 | // Too big int 41 | f("18446744073709551616", 0) 42 | } 43 | 44 | func TestParseUint64Failure(t *testing.T) { 45 | f := func(s string) { 46 | t.Helper() 47 | 48 | num, err := ParseUint64(s) 49 | if err == nil { 50 | t.Fatalf("expecting non-nil error") 51 | } 52 | if num != 0 { 53 | t.Fatalf("unexpected number returned from ParseUint64(%q); got %v; want %v", s, num, 0) 54 | } 55 | } 56 | 57 | // Invalid first char 58 | f("") 59 | f(" ") 60 | f("foo") 61 | f("-") 62 | f("-foo") 63 | f("-123") 64 | 65 | // Invalid suffix 66 | f("1foo") 67 | f("13223 ") 68 | f("1-2") 69 | 70 | // Too big int 71 | f("18446744073709551616") 72 | } 73 | 74 | func TestParseUint64Success(t *testing.T) { 75 | f := func(s string, expectedNum uint64) { 76 | t.Helper() 77 | 78 | num, err := ParseUint64(s) 79 | if err != nil { 80 | t.Fatalf("unexpected error in ParseUint64(%q): %s", s, err) 81 | } 82 | if num != expectedNum { 83 | t.Fatalf("unexpected number parsed from %q; got %v; want %v", s, num, expectedNum) 84 | } 85 | } 86 | 87 | f("0", 0) 88 | f("1", 1) 89 | f("123", 123) 90 | f("1234567890", 1234567890) 91 | f("9223372036854775807", 9223372036854775807) 92 | f("18446744073709551615", 18446744073709551615) 93 | } 94 | 95 | func TestParseInt64BestEffort(t *testing.T) { 96 | f := func(s string, expectedNum int64) { 97 | t.Helper() 98 | 99 | num := ParseInt64BestEffort(s) 100 | if num != expectedNum { 101 | t.Fatalf("unexpected number parsed from %q; got %v; want %v", s, num, expectedNum) 102 | } 103 | } 104 | 105 | // Invalid first char 106 | f("", 0) 107 | f(" ", 0) 108 | f("foo", 0) 109 | f("-", 0) 110 | f("-foo", 0) 111 | 112 | // Invalid suffix 113 | f("1foo", 0) 114 | f("13223 ", 0) 115 | f("1-2", 0) 116 | 117 | // Int 118 | f("0", 0) 119 | f("-0", 0) 120 | f("1", 1) 121 | f("123", 123) 122 | f("-123", -123) 123 | f("1234567890", 1234567890) 124 | f("9223372036854775807", 9223372036854775807) 125 | f("-9223372036854775807", -9223372036854775807) 126 | 127 | // Too big int 128 | f("9223372036854775808", 0) 129 | f("18446744073709551615", 0) 130 | } 131 | 132 | func TestParseInt64Failure(t *testing.T) { 133 | f := func(s string) { 134 | t.Helper() 135 | 136 | num, err := ParseInt64(s) 137 | if err == nil { 138 | t.Fatalf("expecting non-nil error") 139 | } 140 | if num != 0 { 141 | t.Fatalf("unexpected number returned from ParseInt64(%q); got %v; want %v", s, num, 0) 142 | } 143 | } 144 | 145 | // Invalid first char 146 | f("") 147 | f(" ") 148 | f("foo") 149 | f("-") 150 | f("-foo") 151 | 152 | // Invalid suffix 153 | f("1foo") 154 | f("-13223 ") 155 | f("1-2") 156 | 157 | // Too big int 158 | f("9223372036854775808") 159 | f("18446744073709551615") 160 | f("-18446744073709551615") 161 | } 162 | 163 | func TestParseInt64Success(t *testing.T) { 164 | f := func(s string, expectedNum int64) { 165 | t.Helper() 166 | 167 | num, err := ParseInt64(s) 168 | if err != nil { 169 | t.Fatalf("unexpected error returned from ParseInt64(%q): %s", s, err) 170 | } 171 | if num != expectedNum { 172 | t.Fatalf("unexpected number parsed from %q; got %v; want %v", s, num, expectedNum) 173 | } 174 | } 175 | 176 | // Int 177 | f("0", 0) 178 | f("-0", 0) 179 | f("1", 1) 180 | f("123", 123) 181 | f("-123", -123) 182 | f("1234567890", 1234567890) 183 | f("9223372036854775807", 9223372036854775807) 184 | f("-9223372036854775807", -9223372036854775807) 185 | } 186 | 187 | func TestParseBestEffort(t *testing.T) { 188 | f := func(s string, expectedNum float64) { 189 | t.Helper() 190 | 191 | num := ParseBestEffort(s) 192 | if math.IsNaN(expectedNum) { 193 | if !math.IsNaN(num) { 194 | t.Fatalf("unexpected number parsed from %q; got %v; want %v", s, num, expectedNum) 195 | } 196 | } else if num != expectedNum { 197 | t.Fatalf("unexpected number parsed from %q; got %v; want %v", s, num, expectedNum) 198 | } 199 | } 200 | 201 | // Invalid first char 202 | f("", 0) 203 | f(" ", 0) 204 | f("foo", 0) 205 | f(" bar ", 0) 206 | f("-", 0) 207 | f("--", 0) 208 | f("-.", 0) 209 | f(".", 0) 210 | f("-.e", 0) 211 | f("+112", 0) 212 | f("++", 0) 213 | f("e123", 0) 214 | f("E123", 0) 215 | f("-e12", 0) 216 | f(".", 0) 217 | f("..34", 0) 218 | f("-.e3", 0) 219 | f(".e+3", 0) 220 | 221 | // Invalid suffix 222 | f("1foo", 0) 223 | f("1 foo", 0) 224 | f("12.34.56", 0) 225 | f("13e34.56", 0) 226 | f("12.34e56e4", 0) 227 | f("123..45", 0) 228 | f("123ee34", 0) 229 | f("123e", 0) 230 | f("123e+", 0) 231 | f("123E-", 0) 232 | f("123E+.", 0) 233 | f("-123e-23foo", 0) 234 | f("12345678901234567890foobar", 0) 235 | f("0.12345678901234567890foobar", 0) 236 | f("-0.12345678901234567890foobar", 0) 237 | f("1e-400.5", 0) 238 | 239 | // Integer 240 | f("0", 0) 241 | f("-0", 0) 242 | f("0123", 123) 243 | f("-00123", -123) 244 | f("1", 1) 245 | f("-1", -1) 246 | f("1234567890123456", 1234567890123456) 247 | f("12345678901234567", 12345678901234567) 248 | f("123456789012345678", 123456789012345678) 249 | f("1234567890123456789", 1234567890123456789) 250 | f("12345678901234567890", 12345678901234567890) 251 | f("-12345678901234567890", -12345678901234567890) 252 | f("9223372036854775807", 9223372036854775807) 253 | f("18446744073709551615", 18446744073709551615) 254 | f("-18446744073709551615", -18446744073709551615) 255 | 256 | // Fractional part 257 | f("0.0", 0) 258 | f("0.000", 0) 259 | f("0.1", 0.1) 260 | f("0.3", 0.3) 261 | f("-0.1", -0.1) 262 | f("-0.123", -0.123) 263 | f("1.66", 1.66) 264 | f("12.", 12) 265 | f(".12", 0.12) 266 | f("-.12", -0.12) 267 | f("12345.12345678901", 12345.12345678901) 268 | f("12345.123456789012", 12345.123456789012) 269 | f("12345.1234567890123", 12345.1234567890123) 270 | f("12345.12345678901234", 12345.12345678901234) 271 | f("12345.123456789012345", 12345.123456789012345) 272 | f("12345.1234567890123456", 12345.1234567890123456) 273 | f("12345.12345678901234567", 12345.12345678901234567) 274 | f("12345.123456789012345678", 12345.123456789012345678) 275 | f("12345.1234567890123456789", 12345.1234567890123456789) 276 | f("12345.12345678901234567890", 12345.12345678901234567890) 277 | f("-12345.12345678901234567890", -12345.12345678901234567890) 278 | f("18446744073709551615.18446744073709551615", 18446744073709551615.18446744073709551615) 279 | f("-18446744073709551615.18446744073709551615", -18446744073709551615.18446744073709551615) 280 | 281 | // Exponent part 282 | f("0e0", 0) 283 | f("123e+001", 123e1) 284 | f("0e12", 0) 285 | f("-0E123", 0) 286 | f("-0E-123", 0) 287 | f("-0E+123", 0) 288 | f("123e12", 123e12) 289 | f("-123E-12", -123e-12) 290 | f("-123e-400", 0) 291 | f("123e456", math.Inf(1)) // too big exponent 292 | f("-123e456", math.Inf(-1)) // too big exponent 293 | f("1e4", 1e4) 294 | f("-1E-10", -1e-10) 295 | 296 | // Fractional + exponent part 297 | f("0.123e4", 0.123e4) 298 | f("-123.456E-10", -123.456e-10) 299 | f("1.e4", 1.e4) 300 | f("-1.E-10", -1.e-10) 301 | 302 | // inf and nan 303 | f("12345678909123456789012e45678", math.Inf(1)) 304 | f("-12345678909123456789012e45678", math.Inf(-1)) 305 | f("0.12345678909123456789012e45678", math.Inf(1)) 306 | f("-0.12345678909123456789012e45678", math.Inf(-1)) 307 | f("1.1e2345678909123456789012", math.Inf(1)) 308 | f("-1.1e2345678909123456789012", math.Inf(-1)) 309 | f("inf", math.Inf(1)) 310 | f("-Inf", math.Inf(-1)) 311 | f("+iNf", math.Inf(1)) 312 | f("INF", math.Inf(1)) 313 | f("infinity", math.Inf(1)) 314 | f("-Infinity", math.Inf(-1)) 315 | f("+iNfINIty", math.Inf(1)) 316 | f("INFINITY", math.Inf(1)) 317 | f("nan", math.NaN()) 318 | f("-nan", math.NaN()) 319 | f("naN", math.NaN()) 320 | f("NaN", math.NaN()) 321 | } 322 | 323 | func TestParseFailure(t *testing.T) { 324 | f := func(s string) { 325 | t.Helper() 326 | 327 | num, err := Parse(s) 328 | if err == nil { 329 | t.Fatalf("expecting non-nil error") 330 | } 331 | if num != 0 { 332 | t.Fatalf("unexpected number returned from ParseInt64(%q); got %v; want %v", s, num, 0) 333 | } 334 | } 335 | 336 | // Invalid first char 337 | f("") 338 | f(" ") 339 | f("foo") 340 | f(" bar ") 341 | f("-") 342 | f("--") 343 | f(".") 344 | f("-.") 345 | f("-.e") 346 | f("+112") 347 | f("++") 348 | f("e123") 349 | f("E123") 350 | f("-e12") 351 | f(".") 352 | f("..34") 353 | f("-.e3") 354 | f(".e+3") 355 | 356 | // Invalid suffix 357 | f("1foo") 358 | f("1 foo") 359 | f("12.34.56") 360 | f("13e34.56") 361 | f("12.34e56e4") 362 | f("123..45") 363 | f("123ee34") 364 | f("123e") 365 | f("123e+") 366 | f("123E-") 367 | f("123E+.") 368 | f("-123e-23foo") 369 | f("12345678901234567890foobar") 370 | f("0.12345678901234567890foobar") 371 | f("-0.12345678901234567890foobar") 372 | f("1e-400.5") 373 | } 374 | 375 | func TestParseSuccess(t *testing.T) { 376 | f := func(s string, expectedNum float64) { 377 | t.Helper() 378 | 379 | num, err := Parse(s) 380 | if err != nil { 381 | t.Fatalf("unexpected error in Parse(%q): %s", s, err) 382 | } 383 | if math.IsNaN(expectedNum) { 384 | if !math.IsNaN(num) { 385 | t.Fatalf("unexpected number parsed from %q; got %v; want %v", s, num, expectedNum) 386 | } 387 | } else if num != expectedNum { 388 | t.Fatalf("unexpected number parsed from %q; got %v; want %v", s, num, expectedNum) 389 | } 390 | } 391 | 392 | // Integer 393 | f("0", 0) 394 | f("-0", 0) 395 | f("0123", 123) 396 | f("-00123", -123) 397 | f("1", 1) 398 | f("-1", -1) 399 | f("1234567890123456", 1234567890123456) 400 | f("12345678901234567", 12345678901234567) 401 | f("123456789012345678", 123456789012345678) 402 | f("1234567890123456789", 1234567890123456789) 403 | f("12345678901234567890", 12345678901234567890) 404 | f("-12345678901234567890", -12345678901234567890) 405 | f("9223372036854775807", 9223372036854775807) 406 | f("18446744073709551615", 18446744073709551615) 407 | f("-18446744073709551615", -18446744073709551615) 408 | 409 | // Fractional part 410 | f("0.0", 0) 411 | f("0.000", 0) 412 | f("0.1", 0.1) 413 | f("0.3", 0.3) 414 | f("-0.1", -0.1) 415 | f("-0.123", -0.123) 416 | f("1.66", 1.66) 417 | f("12.", 12) 418 | f(".12", 0.12) 419 | f("-.12", -0.12) 420 | f("12345.12345678901", 12345.12345678901) 421 | f("12345.123456789012", 12345.123456789012) 422 | f("12345.1234567890123", 12345.1234567890123) 423 | f("12345.12345678901234", 12345.12345678901234) 424 | f("12345.123456789012345", 12345.123456789012345) 425 | f("12345.1234567890123456", 12345.1234567890123456) 426 | f("12345.12345678901234567", 12345.12345678901234567) 427 | f("12345.123456789012345678", 12345.123456789012345678) 428 | f("12345.1234567890123456789", 12345.1234567890123456789) 429 | f("12345.12345678901234567890", 12345.12345678901234567890) 430 | f("-12345.12345678901234567890", -12345.12345678901234567890) 431 | f("18446744073709551615.18446744073709551615", 18446744073709551615.18446744073709551615) 432 | f("-18446744073709551615.18446744073709551615", -18446744073709551615.18446744073709551615) 433 | 434 | // Exponent part 435 | f("0e0", 0) 436 | f("123e+001", 123e1) 437 | f("0e12", 0) 438 | f("-0E123", 0) 439 | f("-0E-123", 0) 440 | f("-0E+123", 0) 441 | f("123e12", 123e12) 442 | f("-123E-12", -123e-12) 443 | f("-123e-400", 0) 444 | f("123e456", math.Inf(1)) // too big exponent 445 | f("-123e456", math.Inf(-1)) // too big exponent 446 | f("1e4", 1e4) 447 | f("-1E-10", -1e-10) 448 | 449 | // Fractional + exponent part 450 | f("0.123e4", 0.123e4) 451 | f("-123.456E-10", -123.456e-10) 452 | f("1.e4", 1.e4) 453 | f("-1.E-10", -1.e-10) 454 | f(".1e3", 100) 455 | f("-.12e3", -120) 456 | 457 | // inf and nan 458 | f("12345678909123456789012e45678", math.Inf(1)) 459 | f("-12345678909123456789012e45678", math.Inf(-1)) 460 | f("0.12345678909123456789012e45678", math.Inf(1)) 461 | f("-0.12345678909123456789012e45678", math.Inf(-1)) 462 | f("inf", math.Inf(1)) 463 | f("-Inf", math.Inf(-1)) 464 | f("+iNf", math.Inf(1)) 465 | f("INF", math.Inf(1)) 466 | f("infinity", math.Inf(1)) 467 | f("-Infinity", math.Inf(-1)) 468 | f("+iNfINIty", math.Inf(1)) 469 | f("INFINITY", math.Inf(1)) 470 | f("nan", math.NaN()) 471 | f("-nan", math.NaN()) 472 | f("naN", math.NaN()) 473 | f("NaN", math.NaN()) 474 | } 475 | 476 | func TestParseBestEffortFuzz(t *testing.T) { 477 | r := rand.New(rand.NewSource(0)) 478 | for i := 0; i < 100000; i++ { 479 | f := r.Float64() 480 | s := strconv.FormatFloat(f, 'g', -1, 64) 481 | numExpected, err := strconv.ParseFloat(s, 64) 482 | if err != nil { 483 | t.Fatalf("unexpected error when parsing %q: %s", s, err) 484 | } 485 | num := ParseBestEffort(s) 486 | if num != numExpected { 487 | t.Fatalf("unexpected number parsed from %q; got %g; want %g", s, num, numExpected) 488 | } 489 | } 490 | } 491 | 492 | func TestParseFuzz(t *testing.T) { 493 | r := rand.New(rand.NewSource(0)) 494 | for i := 0; i < 100000; i++ { 495 | f := r.Float64() 496 | s := strconv.FormatFloat(f, 'g', -1, 64) 497 | numExpected, err := strconv.ParseFloat(s, 64) 498 | if err != nil { 499 | t.Fatalf("unexpected error when parsing %q: %s", s, err) 500 | } 501 | num, err := Parse(s) 502 | if err != nil { 503 | t.Fatalf("unexpected error in Parse(%q): %s", s, err) 504 | } 505 | if num != numExpected { 506 | t.Fatalf("unexpected number parsed from %q; got %g; want %g", s, num, numExpected) 507 | } 508 | } 509 | } 510 | -------------------------------------------------------------------------------- /fastfloat/parse_timing_test.go: -------------------------------------------------------------------------------- 1 | package fastfloat 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "sync/atomic" 7 | "testing" 8 | ) 9 | 10 | func BenchmarkParseUint64(b *testing.B) { 11 | for _, s := range []string{"0", "12", "12345", "1234567890", "9223372036854775807"} { 12 | b.Run(s, func(b *testing.B) { 13 | benchmarkParseUint64(b, s) 14 | }) 15 | } 16 | } 17 | 18 | func BenchmarkParseUint64BestEffort(b *testing.B) { 19 | for _, s := range []string{"0", "12", "12345", "1234567890", "9223372036854775807"} { 20 | b.Run(s, func(b *testing.B) { 21 | benchmarkParseUint64BestEffort(b, s) 22 | }) 23 | } 24 | } 25 | 26 | func BenchmarkParseInt64(b *testing.B) { 27 | for _, s := range []string{"0", "12", "12345", "1234567890", "9223372036854775807"} { 28 | b.Run(s, func(b *testing.B) { 29 | benchmarkParseInt64(b, s) 30 | }) 31 | } 32 | } 33 | 34 | func BenchmarkParseInt64BestEffort(b *testing.B) { 35 | for _, s := range []string{"0", "12", "12345", "1234567890", "9223372036854775807"} { 36 | b.Run(s, func(b *testing.B) { 37 | benchmarkParseInt64BestEffort(b, s) 38 | }) 39 | } 40 | } 41 | 42 | func BenchmarkParseBestEffort(b *testing.B) { 43 | for _, s := range []string{"0", "12", "12345", "1234567890", "1234.45678", "1234e45", "12.34e-34", "12345.1234567890", "12345.12345678901"} { 44 | b.Run(s, func(b *testing.B) { 45 | benchmarkParseBestEffort(b, s) 46 | }) 47 | } 48 | } 49 | 50 | func BenchmarkParse(b *testing.B) { 51 | for _, s := range []string{"0", "12", "12345", "1234567890", "1234.45678", "1234e45", "12.34e-34", "12345.1234567890", "12345.12345678901"} { 52 | b.Run(s, func(b *testing.B) { 53 | benchmarkParse(b, s) 54 | }) 55 | } 56 | } 57 | 58 | func benchmarkParseUint64(b *testing.B, s string) { 59 | b.Run("std", func(b *testing.B) { 60 | b.ReportAllocs() 61 | b.SetBytes(int64(len(s))) 62 | b.RunParallel(func(pb *testing.PB) { 63 | var d uint64 64 | for pb.Next() { 65 | dd, err := strconv.ParseUint(s, 10, 64) 66 | if err != nil { 67 | panic(fmt.Errorf("unexpected error: %s", err)) 68 | } 69 | d += dd 70 | } 71 | atomic.AddUint64(&Sink, d) 72 | }) 73 | }) 74 | b.Run("custom", func(b *testing.B) { 75 | b.ReportAllocs() 76 | b.SetBytes(int64(len(s))) 77 | b.RunParallel(func(pb *testing.PB) { 78 | var d uint64 79 | for pb.Next() { 80 | dd, err := ParseUint64(s) 81 | if err != nil { 82 | panic(fmt.Errorf("unexpected error: %s", err)) 83 | } 84 | d += dd 85 | } 86 | atomic.AddUint64(&Sink, d) 87 | }) 88 | }) 89 | } 90 | 91 | func benchmarkParseInt64(b *testing.B, s string) { 92 | b.Run("std", func(b *testing.B) { 93 | b.ReportAllocs() 94 | b.SetBytes(int64(len(s))) 95 | b.RunParallel(func(pb *testing.PB) { 96 | var d int64 97 | for pb.Next() { 98 | dd, err := strconv.ParseInt(s, 10, 64) 99 | if err != nil { 100 | panic(fmt.Errorf("unexpected error: %s", err)) 101 | } 102 | d += dd 103 | } 104 | atomic.AddUint64(&Sink, uint64(d)) 105 | }) 106 | }) 107 | b.Run("custom", func(b *testing.B) { 108 | b.ReportAllocs() 109 | b.SetBytes(int64(len(s))) 110 | b.RunParallel(func(pb *testing.PB) { 111 | var d int64 112 | for pb.Next() { 113 | dd, err := ParseInt64(s) 114 | if err != nil { 115 | panic(fmt.Errorf("unexpected error: %s", err)) 116 | } 117 | d += dd 118 | } 119 | atomic.AddUint64(&Sink, uint64(d)) 120 | }) 121 | }) 122 | } 123 | 124 | func benchmarkParseUint64BestEffort(b *testing.B, s string) { 125 | b.Run("std", func(b *testing.B) { 126 | b.ReportAllocs() 127 | b.SetBytes(int64(len(s))) 128 | b.RunParallel(func(pb *testing.PB) { 129 | var d uint64 130 | for pb.Next() { 131 | dd, err := strconv.ParseUint(s, 10, 64) 132 | if err != nil { 133 | panic(fmt.Errorf("unexpected error: %s", err)) 134 | } 135 | d += dd 136 | } 137 | atomic.AddUint64(&Sink, d) 138 | }) 139 | }) 140 | b.Run("custom", func(b *testing.B) { 141 | b.ReportAllocs() 142 | b.SetBytes(int64(len(s))) 143 | b.RunParallel(func(pb *testing.PB) { 144 | var d uint64 145 | for pb.Next() { 146 | d += ParseUint64BestEffort(s) 147 | } 148 | atomic.AddUint64(&Sink, d) 149 | }) 150 | }) 151 | } 152 | 153 | func benchmarkParseInt64BestEffort(b *testing.B, s string) { 154 | b.Run("std", func(b *testing.B) { 155 | b.ReportAllocs() 156 | b.SetBytes(int64(len(s))) 157 | b.RunParallel(func(pb *testing.PB) { 158 | var d int64 159 | for pb.Next() { 160 | dd, err := strconv.ParseInt(s, 10, 64) 161 | if err != nil { 162 | panic(fmt.Errorf("unexpected error: %s", err)) 163 | } 164 | d += dd 165 | } 166 | atomic.AddUint64(&Sink, uint64(d)) 167 | }) 168 | }) 169 | b.Run("custom", func(b *testing.B) { 170 | b.ReportAllocs() 171 | b.SetBytes(int64(len(s))) 172 | b.RunParallel(func(pb *testing.PB) { 173 | var d int64 174 | for pb.Next() { 175 | d += ParseInt64BestEffort(s) 176 | } 177 | atomic.AddUint64(&Sink, uint64(d)) 178 | }) 179 | }) 180 | } 181 | 182 | func benchmarkParse(b *testing.B, s string) { 183 | b.Run("std", func(b *testing.B) { 184 | b.ReportAllocs() 185 | b.SetBytes(int64(len(s))) 186 | b.RunParallel(func(pb *testing.PB) { 187 | var f float64 188 | for pb.Next() { 189 | ff, err := strconv.ParseFloat(s, 64) 190 | if err != nil { 191 | panic(fmt.Errorf("unexpected error: %s", err)) 192 | } 193 | f += ff 194 | } 195 | atomic.AddUint64(&Sink, uint64(f)) 196 | }) 197 | }) 198 | b.Run("custom", func(b *testing.B) { 199 | b.ReportAllocs() 200 | b.SetBytes(int64(len(s))) 201 | b.RunParallel(func(pb *testing.PB) { 202 | var f float64 203 | for pb.Next() { 204 | ff, err := Parse(s) 205 | if err != nil { 206 | panic(fmt.Errorf("unexpected error in Parse(%q): %s", s, err)) 207 | } 208 | f += ff 209 | } 210 | atomic.AddUint64(&Sink, uint64(f)) 211 | }) 212 | }) 213 | } 214 | 215 | func benchmarkParseBestEffort(b *testing.B, s string) { 216 | b.Run("std", func(b *testing.B) { 217 | b.ReportAllocs() 218 | b.SetBytes(int64(len(s))) 219 | b.RunParallel(func(pb *testing.PB) { 220 | var f float64 221 | for pb.Next() { 222 | ff, err := strconv.ParseFloat(s, 64) 223 | if err != nil { 224 | panic(fmt.Errorf("unexpected error: %s", err)) 225 | } 226 | f += ff 227 | } 228 | atomic.AddUint64(&Sink, uint64(f)) 229 | }) 230 | }) 231 | b.Run("custom", func(b *testing.B) { 232 | b.ReportAllocs() 233 | b.SetBytes(int64(len(s))) 234 | b.RunParallel(func(pb *testing.PB) { 235 | var f float64 236 | for pb.Next() { 237 | f += ParseBestEffort(s) 238 | } 239 | atomic.AddUint64(&Sink, uint64(f)) 240 | }) 241 | }) 242 | } 243 | 244 | var Sink uint64 245 | -------------------------------------------------------------------------------- /fuzz.go: -------------------------------------------------------------------------------- 1 | // +build gofuzz 2 | 3 | package fastjson 4 | 5 | func Fuzz(data []byte) int { 6 | err := ValidateBytes(data) 7 | if err != nil { 8 | return 0 9 | } 10 | 11 | v := MustParseBytes(data) 12 | 13 | dst := make([]byte, 0) 14 | dst = v.MarshalTo(dst) 15 | 16 | err = ValidateBytes(dst) 17 | if err != nil { 18 | panic(err) 19 | } 20 | 21 | return 1 22 | } 23 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/valyala/fastjson 2 | 3 | go 1.12 4 | -------------------------------------------------------------------------------- /handy.go: -------------------------------------------------------------------------------- 1 | package fastjson 2 | 3 | var handyPool ParserPool 4 | 5 | // GetString returns string value for the field identified by keys path 6 | // in JSON data. 7 | // 8 | // Array indexes may be represented as decimal numbers in keys. 9 | // 10 | // An empty string is returned on error. Use Parser for proper error handling. 11 | // 12 | // Parser is faster for obtaining multiple fields from JSON. 13 | func GetString(data []byte, keys ...string) string { 14 | p := handyPool.Get() 15 | v, err := p.ParseBytes(data) 16 | if err != nil { 17 | handyPool.Put(p) 18 | return "" 19 | } 20 | sb := v.GetStringBytes(keys...) 21 | str := string(sb) 22 | handyPool.Put(p) 23 | return str 24 | } 25 | 26 | // GetBytes returns string value for the field identified by keys path 27 | // in JSON data. 28 | // 29 | // Array indexes may be represented as decimal numbers in keys. 30 | // 31 | // nil is returned on error. Use Parser for proper error handling. 32 | // 33 | // Parser is faster for obtaining multiple fields from JSON. 34 | func GetBytes(data []byte, keys ...string) []byte { 35 | p := handyPool.Get() 36 | v, err := p.ParseBytes(data) 37 | if err != nil { 38 | handyPool.Put(p) 39 | return nil 40 | } 41 | sb := v.GetStringBytes(keys...) 42 | 43 | // Make a copy of sb, since sb belongs to p. 44 | var b []byte 45 | if sb != nil { 46 | b = append(b, sb...) 47 | } 48 | 49 | handyPool.Put(p) 50 | return b 51 | } 52 | 53 | // GetInt returns int value for the field identified by keys path 54 | // in JSON data. 55 | // 56 | // Array indexes may be represented as decimal numbers in keys. 57 | // 58 | // 0 is returned on error. Use Parser for proper error handling. 59 | // 60 | // Parser is faster for obtaining multiple fields from JSON. 61 | func GetInt(data []byte, keys ...string) int { 62 | p := handyPool.Get() 63 | v, err := p.ParseBytes(data) 64 | if err != nil { 65 | handyPool.Put(p) 66 | return 0 67 | } 68 | n := v.GetInt(keys...) 69 | handyPool.Put(p) 70 | return n 71 | } 72 | 73 | // GetFloat64 returns float64 value for the field identified by keys path 74 | // in JSON data. 75 | // 76 | // Array indexes may be represented as decimal numbers in keys. 77 | // 78 | // 0 is returned on error. Use Parser for proper error handling. 79 | // 80 | // Parser is faster for obtaining multiple fields from JSON. 81 | func GetFloat64(data []byte, keys ...string) float64 { 82 | p := handyPool.Get() 83 | v, err := p.ParseBytes(data) 84 | if err != nil { 85 | handyPool.Put(p) 86 | return 0 87 | } 88 | f := v.GetFloat64(keys...) 89 | handyPool.Put(p) 90 | return f 91 | } 92 | 93 | // GetBool returns boolean value for the field identified by keys path 94 | // in JSON data. 95 | // 96 | // Array indexes may be represented as decimal numbers in keys. 97 | // 98 | // False is returned on error. Use Parser for proper error handling. 99 | // 100 | // Parser is faster for obtaining multiple fields from JSON. 101 | func GetBool(data []byte, keys ...string) bool { 102 | p := handyPool.Get() 103 | v, err := p.ParseBytes(data) 104 | if err != nil { 105 | handyPool.Put(p) 106 | return false 107 | } 108 | b := v.GetBool(keys...) 109 | handyPool.Put(p) 110 | return b 111 | } 112 | 113 | // Exists returns true if the field identified by keys path exists in JSON data. 114 | // 115 | // Array indexes may be represented as decimal numbers in keys. 116 | // 117 | // False is returned on error. Use Parser for proper error handling. 118 | // 119 | // Parser is faster when multiple fields must be checked in the JSON. 120 | func Exists(data []byte, keys ...string) bool { 121 | p := handyPool.Get() 122 | v, err := p.ParseBytes(data) 123 | if err != nil { 124 | handyPool.Put(p) 125 | return false 126 | } 127 | ok := v.Exists(keys...) 128 | handyPool.Put(p) 129 | return ok 130 | } 131 | 132 | // Parse parses json string s. 133 | // 134 | // The function is slower than the Parser.Parse for re-used Parser. 135 | func Parse(s string) (*Value, error) { 136 | var p Parser 137 | return p.Parse(s) 138 | } 139 | 140 | // MustParse parses json string s. 141 | // 142 | // The function panics if s cannot be parsed. 143 | // The function is slower than the Parser.Parse for re-used Parser. 144 | func MustParse(s string) *Value { 145 | v, err := Parse(s) 146 | if err != nil { 147 | panic(err) 148 | } 149 | return v 150 | } 151 | 152 | // ParseBytes parses b containing json. 153 | // 154 | // The function is slower than the Parser.ParseBytes for re-used Parser. 155 | func ParseBytes(b []byte) (*Value, error) { 156 | var p Parser 157 | return p.ParseBytes(b) 158 | } 159 | 160 | // MustParseBytes parses b containing json. 161 | // 162 | // The function panics if b cannot be parsed. 163 | // The function is slower than the Parser.ParseBytes for re-used Parser. 164 | func MustParseBytes(b []byte) *Value { 165 | v, err := ParseBytes(b) 166 | if err != nil { 167 | panic(err) 168 | } 169 | return v 170 | } 171 | -------------------------------------------------------------------------------- /handy_example_test.go: -------------------------------------------------------------------------------- 1 | package fastjson_test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/valyala/fastjson" 6 | ) 7 | 8 | func ExampleGetString() { 9 | data := []byte(`{"foo":{"bar":[123,"baz"]}}`) 10 | 11 | s := fastjson.GetString(data, "foo", "bar", "1") 12 | fmt.Printf("data.foo.bar[1] = %s", s) 13 | 14 | // Output: 15 | // data.foo.bar[1] = baz 16 | } 17 | 18 | func ExampleGetInt() { 19 | data := []byte(`{"foo": [233,true, {"bar": [2343]} ]}`) 20 | 21 | n1 := fastjson.GetInt(data, "foo", "0") 22 | fmt.Printf("data.foo[0] = %d\n", n1) 23 | 24 | n2 := fastjson.GetInt(data, "foo", "2", "bar", "0") 25 | fmt.Printf("data.foo[2].bar[0] = %d\n", n2) 26 | 27 | // Output: 28 | // data.foo[0] = 233 29 | // data.foo[2].bar[0] = 2343 30 | } 31 | 32 | func ExampleExists() { 33 | data := []byte(`{"foo": [1.23,{"bar":33,"baz":null}]}`) 34 | 35 | fmt.Printf("exists(data.foo) = %v\n", fastjson.Exists(data, "foo")) 36 | fmt.Printf("exists(data.foo[0]) = %v\n", fastjson.Exists(data, "foo", "0")) 37 | fmt.Printf("exists(data.foo[1].baz) = %v\n", fastjson.Exists(data, "foo", "1", "baz")) 38 | fmt.Printf("exists(data.foobar) = %v\n", fastjson.Exists(data, "foobar")) 39 | fmt.Printf("exists(data.foo.bar) = %v\n", fastjson.Exists(data, "foo", "bar")) 40 | 41 | // Output: 42 | // exists(data.foo) = true 43 | // exists(data.foo[0]) = true 44 | // exists(data.foo[1].baz) = true 45 | // exists(data.foobar) = false 46 | // exists(data.foo.bar) = false 47 | } 48 | -------------------------------------------------------------------------------- /handy_test.go: -------------------------------------------------------------------------------- 1 | package fastjson 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestGetStringConcurrent(t *testing.T) { 10 | const concurrency = 4 11 | data := []byte(largeFixture) 12 | 13 | ch := make(chan error, concurrency) 14 | 15 | for i := 0; i < concurrency; i++ { 16 | go func() { 17 | s := GetString(data, "non-existing-key") 18 | if s != "" { 19 | ch <- fmt.Errorf("unexpected non-empty string got: %q", s) 20 | } 21 | ch <- nil 22 | }() 23 | } 24 | 25 | for i := 0; i < concurrency; i++ { 26 | select { 27 | case <-time.After(time.Second * 5): 28 | t.Fatalf("timeout") 29 | case err := <-ch: 30 | if err != nil { 31 | t.Fatalf("unexpected error: %s", err) 32 | } 33 | } 34 | } 35 | } 36 | 37 | func TestGetBytesConcurrent(t *testing.T) { 38 | const concurrency = 4 39 | data := []byte(largeFixture) 40 | 41 | ch := make(chan error, concurrency) 42 | 43 | for i := 0; i < concurrency; i++ { 44 | go func() { 45 | b := GetBytes(data, "non-existing-key") 46 | if b != nil { 47 | ch <- fmt.Errorf("unexpected non-empty string got: %q", b) 48 | } 49 | ch <- nil 50 | }() 51 | } 52 | 53 | for i := 0; i < concurrency; i++ { 54 | select { 55 | case <-time.After(time.Second * 5): 56 | t.Fatalf("timeout") 57 | case err := <-ch: 58 | if err != nil { 59 | t.Fatalf("unexpected error: %s", err) 60 | } 61 | } 62 | } 63 | } 64 | 65 | func TestGetString(t *testing.T) { 66 | data := []byte(`{"foo":"bar", "baz": 1234}`) 67 | 68 | // normal path 69 | s := GetString(data, "foo") 70 | if s != "bar" { 71 | t.Fatalf("unexpected value obtained; got %q; want %q", s, "bar") 72 | } 73 | 74 | // non-existing path 75 | s = GetString(data, "foo", "zzz") 76 | if s != "" { 77 | t.Fatalf("unexpected non-empty value obtained: %q", s) 78 | } 79 | 80 | // invalid type 81 | s = GetString(data, "baz") 82 | if s != "" { 83 | t.Fatalf("unexpected non-empty value obtained: %q", s) 84 | } 85 | 86 | // invalid json 87 | s = GetString([]byte("invalid json"), "foobar", "baz") 88 | if s != "" { 89 | t.Fatalf("unexpected non-empty value obtained: %q", s) 90 | } 91 | } 92 | 93 | func TestGetBytes(t *testing.T) { 94 | data := []byte(`{"foo":"bar", "baz": 1234}`) 95 | 96 | // normal path 97 | b := GetBytes(data, "foo") 98 | if string(b) != "bar" { 99 | t.Fatalf("unexpected value obtained; got %q; want %q", b, "bar") 100 | } 101 | 102 | // non-existing path 103 | b = GetBytes(data, "foo", "zzz") 104 | if b != nil { 105 | t.Fatalf("unexpected non-empty value obtained: %q", b) 106 | } 107 | 108 | // invalid type 109 | b = GetBytes(data, "baz") 110 | if b != nil { 111 | t.Fatalf("unexpected non-empty value obtained: %q", b) 112 | } 113 | 114 | // invalid json 115 | b = GetBytes([]byte("invalid json"), "foobar", "baz") 116 | if b != nil { 117 | t.Fatalf("unexpected non-empty value obtained: %q", b) 118 | } 119 | } 120 | 121 | func TestGetInt(t *testing.T) { 122 | data := []byte(`{"foo":"bar", "baz": 1234}`) 123 | 124 | // normal path 125 | n := GetInt(data, "baz") 126 | if n != 1234 { 127 | t.Fatalf("unexpected value obtained; got %d; want %d", n, 1234) 128 | } 129 | 130 | // non-existing path 131 | n = GetInt(data, "foo", "zzz") 132 | if n != 0 { 133 | t.Fatalf("unexpected non-zero value obtained: %d", n) 134 | } 135 | 136 | // invalid type 137 | n = GetInt(data, "foo") 138 | if n != 0 { 139 | t.Fatalf("unexpected non-zero value obtained: %d", n) 140 | } 141 | 142 | // invalid json 143 | n = GetInt([]byte("invalid json"), "foobar", "baz") 144 | if n != 0 { 145 | t.Fatalf("unexpected non-empty value obtained: %d", n) 146 | } 147 | } 148 | 149 | func TestGetFloat64(t *testing.T) { 150 | data := []byte(`{"foo":"bar", "baz": 12.34}`) 151 | 152 | // normal path 153 | f := GetFloat64(data, "baz") 154 | if f != 12.34 { 155 | t.Fatalf("unexpected value obtained; got %f; want %f", f, 12.34) 156 | } 157 | 158 | // non-existing path 159 | f = GetFloat64(data, "foo", "zzz") 160 | if f != 0 { 161 | t.Fatalf("unexpected non-zero value obtained: %f", f) 162 | } 163 | 164 | // invalid type 165 | f = GetFloat64(data, "foo") 166 | if f != 0 { 167 | t.Fatalf("unexpected non-zero value obtained: %f", f) 168 | } 169 | 170 | // invalid json 171 | f = GetFloat64([]byte("invalid json"), "foobar", "baz") 172 | if f != 0 { 173 | t.Fatalf("unexpected non-empty value obtained: %f", f) 174 | } 175 | } 176 | 177 | func TestGetBool(t *testing.T) { 178 | data := []byte(`{"foo":"bar", "baz": true}`) 179 | 180 | // normal path 181 | b := GetBool(data, "baz") 182 | if !b { 183 | t.Fatalf("unexpected value obtained; got %v; want %v", b, true) 184 | } 185 | 186 | // non-existing path 187 | b = GetBool(data, "foo", "zzz") 188 | if b { 189 | t.Fatalf("unexpected true value obtained") 190 | } 191 | 192 | // invalid type 193 | b = GetBool(data, "foo") 194 | if b { 195 | t.Fatalf("unexpected true value obtained") 196 | } 197 | 198 | // invalid json 199 | b = GetBool([]byte("invalid json"), "foobar", "baz") 200 | if b { 201 | t.Fatalf("unexpected true value obtained") 202 | } 203 | } 204 | 205 | func TestExists(t *testing.T) { 206 | data := []byte(`{"foo": [{"bar": 1234, "baz": 0}]}`) 207 | 208 | if !Exists(data, "foo") { 209 | t.Fatalf("cannot find foo") 210 | } 211 | if !Exists(data, "foo", "0") { 212 | t.Fatalf("cannot find foo[0]") 213 | } 214 | if !Exists(data, "foo", "0", "baz") { 215 | t.Fatalf("cannot find foo[0].baz") 216 | } 217 | 218 | if Exists(data, "foobar") { 219 | t.Fatalf("found unexpected foobar") 220 | } 221 | if Exists(data, "foo", "1") { 222 | t.Fatalf("found unexpected foo[1]") 223 | } 224 | if Exists(data, "foo", "0", "234") { 225 | t.Fatalf("found unexpected foo[0][234]") 226 | } 227 | if Exists(data, "foo", "bar") { 228 | t.Fatalf("found unexpected foo.bar") 229 | } 230 | 231 | if Exists([]byte(`invalid JSON`), "foo", "bar") { 232 | t.Fatalf("Exists returned true on invalid json") 233 | } 234 | } 235 | 236 | func TestParse(t *testing.T) { 237 | v, err := Parse(`{"foo": "bar"}`) 238 | if err != nil { 239 | t.Fatalf("unexpected error: %s", err) 240 | } 241 | str := v.String() 242 | if str != `{"foo":"bar"}` { 243 | t.Fatalf("unexpected value parsed: %q; want %q", str, `{"foo":"bar"}`) 244 | } 245 | } 246 | 247 | func TestParseBytes(t *testing.T) { 248 | v, err := ParseBytes([]byte(`{"foo": "bar"}`)) 249 | if err != nil { 250 | t.Fatalf("unexpected error: %s", err) 251 | } 252 | str := v.String() 253 | if str != `{"foo":"bar"}` { 254 | t.Fatalf("unexpected value parsed: %q; want %q", str, `{"foo":"bar"}`) 255 | } 256 | } 257 | 258 | func TestMustParse(t *testing.T) { 259 | s := `{"foo":"bar"}` 260 | v := MustParse(s) 261 | str := v.String() 262 | if str != s { 263 | t.Fatalf("unexpected value parsed; %q; want %q", str, s) 264 | } 265 | 266 | v = MustParseBytes([]byte(s)) 267 | if str != s { 268 | t.Fatalf("unexpected value parsed; %q; want %q", str, s) 269 | } 270 | 271 | if !causesPanic(func() { v = MustParse(`[`) }) { 272 | t.Fatalf("expected MustParse to panic") 273 | } 274 | 275 | if !causesPanic(func() { v = MustParseBytes([]byte(`[`)) }) { 276 | t.Fatalf("expected MustParse to panic") 277 | } 278 | } 279 | 280 | func causesPanic(fn func()) (p bool) { 281 | defer func() { 282 | if r := recover(); r != nil { 283 | p = true 284 | } 285 | }() 286 | fn() 287 | return 288 | } 289 | -------------------------------------------------------------------------------- /parser.go: -------------------------------------------------------------------------------- 1 | package fastjson 2 | 3 | import ( 4 | "fmt" 5 | "github.com/valyala/fastjson/fastfloat" 6 | "strconv" 7 | "strings" 8 | "unicode/utf16" 9 | ) 10 | 11 | // Parser parses JSON. 12 | // 13 | // Parser may be re-used for subsequent parsing. 14 | // 15 | // Parser cannot be used from concurrent goroutines. 16 | // Use per-goroutine parsers or ParserPool instead. 17 | type Parser struct { 18 | // b contains working copy of the string to be parsed. 19 | b []byte 20 | 21 | // c is a cache for json values. 22 | c cache 23 | } 24 | 25 | // Parse parses s containing JSON. 26 | // 27 | // The returned value is valid until the next call to Parse*. 28 | // 29 | // Use Scanner if a stream of JSON values must be parsed. 30 | func (p *Parser) Parse(s string) (*Value, error) { 31 | s = skipWS(s) 32 | p.b = append(p.b[:0], s...) 33 | p.c.reset() 34 | 35 | v, tail, err := parseValue(b2s(p.b), &p.c, 0) 36 | if err != nil { 37 | return nil, fmt.Errorf("cannot parse JSON: %s; unparsed tail: %q", err, startEndString(tail)) 38 | } 39 | tail = skipWS(tail) 40 | if len(tail) > 0 { 41 | return nil, fmt.Errorf("unexpected tail: %q", startEndString(tail)) 42 | } 43 | return v, nil 44 | } 45 | 46 | // ParseBytes parses b containing JSON. 47 | // 48 | // The returned Value is valid until the next call to Parse*. 49 | // 50 | // Use Scanner if a stream of JSON values must be parsed. 51 | func (p *Parser) ParseBytes(b []byte) (*Value, error) { 52 | return p.Parse(b2s(b)) 53 | } 54 | 55 | type cache struct { 56 | vs []Value 57 | } 58 | 59 | func (c *cache) reset() { 60 | c.vs = c.vs[:0] 61 | } 62 | 63 | func (c *cache) getValue() *Value { 64 | if cap(c.vs) > len(c.vs) { 65 | c.vs = c.vs[:len(c.vs)+1] 66 | } else { 67 | c.vs = append(c.vs, Value{}) 68 | } 69 | // Do not reset the value, since the caller must properly init it. 70 | return &c.vs[len(c.vs)-1] 71 | } 72 | 73 | func skipWS(s string) string { 74 | if len(s) == 0 || s[0] > 0x20 { 75 | // Fast path. 76 | return s 77 | } 78 | return skipWSSlow(s) 79 | } 80 | 81 | func skipWSSlow(s string) string { 82 | if len(s) == 0 || s[0] != 0x20 && s[0] != 0x0A && s[0] != 0x09 && s[0] != 0x0D { 83 | return s 84 | } 85 | for i := 1; i < len(s); i++ { 86 | if s[i] != 0x20 && s[i] != 0x0A && s[i] != 0x09 && s[i] != 0x0D { 87 | return s[i:] 88 | } 89 | } 90 | return "" 91 | } 92 | 93 | type kv struct { 94 | k string 95 | v *Value 96 | } 97 | 98 | // MaxDepth is the maximum depth for nested JSON. 99 | const MaxDepth = 300 100 | 101 | func parseValue(s string, c *cache, depth int) (*Value, string, error) { 102 | if len(s) == 0 { 103 | return nil, s, fmt.Errorf("cannot parse empty string") 104 | } 105 | depth++ 106 | if depth > MaxDepth { 107 | return nil, s, fmt.Errorf("too big depth for the nested JSON; it exceeds %d", MaxDepth) 108 | } 109 | 110 | if s[0] == '{' { 111 | v, tail, err := parseObject(s[1:], c, depth) 112 | if err != nil { 113 | return nil, tail, fmt.Errorf("cannot parse object: %s", err) 114 | } 115 | return v, tail, nil 116 | } 117 | if s[0] == '[' { 118 | v, tail, err := parseArray(s[1:], c, depth) 119 | if err != nil { 120 | return nil, tail, fmt.Errorf("cannot parse array: %s", err) 121 | } 122 | return v, tail, nil 123 | } 124 | if s[0] == '"' { 125 | ss, tail, err := parseRawString(s[1:]) 126 | if err != nil { 127 | return nil, tail, fmt.Errorf("cannot parse string: %s", err) 128 | } 129 | v := c.getValue() 130 | v.t = typeRawString 131 | v.s = ss 132 | return v, tail, nil 133 | } 134 | if s[0] == 't' { 135 | if len(s) < len("true") || s[:len("true")] != "true" { 136 | return nil, s, fmt.Errorf("unexpected value found: %q", s) 137 | } 138 | return valueTrue, s[len("true"):], nil 139 | } 140 | if s[0] == 'f' { 141 | if len(s) < len("false") || s[:len("false")] != "false" { 142 | return nil, s, fmt.Errorf("unexpected value found: %q", s) 143 | } 144 | return valueFalse, s[len("false"):], nil 145 | } 146 | if s[0] == 'n' { 147 | if len(s) < len("null") || s[:len("null")] != "null" { 148 | // Try parsing NaN 149 | if len(s) >= 3 && strings.EqualFold(s[:3], "nan") { 150 | v := c.getValue() 151 | v.t = TypeNumber 152 | v.s = s[:3] 153 | return v, s[3:], nil 154 | } 155 | return nil, s, fmt.Errorf("unexpected value found: %q", s) 156 | } 157 | return valueNull, s[len("null"):], nil 158 | } 159 | 160 | ns, tail, err := parseRawNumber(s) 161 | if err != nil { 162 | return nil, tail, fmt.Errorf("cannot parse number: %s", err) 163 | } 164 | v := c.getValue() 165 | v.t = TypeNumber 166 | v.s = ns 167 | return v, tail, nil 168 | } 169 | 170 | func parseArray(s string, c *cache, depth int) (*Value, string, error) { 171 | s = skipWS(s) 172 | if len(s) == 0 { 173 | return nil, s, fmt.Errorf("missing ']'") 174 | } 175 | 176 | if s[0] == ']' { 177 | v := c.getValue() 178 | v.t = TypeArray 179 | v.a = v.a[:0] 180 | return v, s[1:], nil 181 | } 182 | 183 | a := c.getValue() 184 | a.t = TypeArray 185 | a.a = a.a[:0] 186 | for { 187 | var v *Value 188 | var err error 189 | 190 | s = skipWS(s) 191 | v, s, err = parseValue(s, c, depth) 192 | if err != nil { 193 | return nil, s, fmt.Errorf("cannot parse array value: %s", err) 194 | } 195 | a.a = append(a.a, v) 196 | 197 | s = skipWS(s) 198 | if len(s) == 0 { 199 | return nil, s, fmt.Errorf("unexpected end of array") 200 | } 201 | if s[0] == ',' { 202 | s = s[1:] 203 | continue 204 | } 205 | if s[0] == ']' { 206 | s = s[1:] 207 | return a, s, nil 208 | } 209 | return nil, s, fmt.Errorf("missing ',' after array value") 210 | } 211 | } 212 | 213 | func parseObject(s string, c *cache, depth int) (*Value, string, error) { 214 | s = skipWS(s) 215 | if len(s) == 0 { 216 | return nil, s, fmt.Errorf("missing '}'") 217 | } 218 | 219 | if s[0] == '}' { 220 | v := c.getValue() 221 | v.t = TypeObject 222 | v.o.reset() 223 | return v, s[1:], nil 224 | } 225 | 226 | o := c.getValue() 227 | o.t = TypeObject 228 | o.o.reset() 229 | for { 230 | var err error 231 | kv := o.o.getKV() 232 | 233 | // Parse key. 234 | s = skipWS(s) 235 | if len(s) == 0 || s[0] != '"' { 236 | return nil, s, fmt.Errorf(`cannot find opening '"" for object key`) 237 | } 238 | kv.k, s, err = parseRawKey(s[1:]) 239 | if err != nil { 240 | return nil, s, fmt.Errorf("cannot parse object key: %s", err) 241 | } 242 | s = skipWS(s) 243 | if len(s) == 0 || s[0] != ':' { 244 | return nil, s, fmt.Errorf("missing ':' after object key") 245 | } 246 | s = s[1:] 247 | 248 | // Parse value 249 | s = skipWS(s) 250 | kv.v, s, err = parseValue(s, c, depth) 251 | if err != nil { 252 | return nil, s, fmt.Errorf("cannot parse object value: %s", err) 253 | } 254 | s = skipWS(s) 255 | if len(s) == 0 { 256 | return nil, s, fmt.Errorf("unexpected end of object") 257 | } 258 | if s[0] == ',' { 259 | s = s[1:] 260 | continue 261 | } 262 | if s[0] == '}' { 263 | return o, s[1:], nil 264 | } 265 | return nil, s, fmt.Errorf("missing ',' after object value") 266 | } 267 | } 268 | 269 | func escapeString(dst []byte, s string) []byte { 270 | if !hasSpecialChars(s) { 271 | // Fast path - nothing to escape. 272 | dst = append(dst, '"') 273 | dst = append(dst, s...) 274 | dst = append(dst, '"') 275 | return dst 276 | } 277 | 278 | // Slow path. 279 | return strconv.AppendQuote(dst, s) 280 | } 281 | 282 | func hasSpecialChars(s string) bool { 283 | if strings.IndexByte(s, '"') >= 0 || strings.IndexByte(s, '\\') >= 0 { 284 | return true 285 | } 286 | for i := 0; i < len(s); i++ { 287 | if s[i] < 0x20 { 288 | return true 289 | } 290 | } 291 | return false 292 | } 293 | 294 | func unescapeStringBestEffort(s string) string { 295 | n := strings.IndexByte(s, '\\') 296 | if n < 0 { 297 | // Fast path - nothing to unescape. 298 | return s 299 | } 300 | 301 | // Slow path - unescape string. 302 | b := s2b(s) // It is safe to do, since s points to a byte slice in Parser.b. 303 | b = b[:n] 304 | s = s[n+1:] 305 | for len(s) > 0 { 306 | ch := s[0] 307 | s = s[1:] 308 | switch ch { 309 | case '"': 310 | b = append(b, '"') 311 | case '\\': 312 | b = append(b, '\\') 313 | case '/': 314 | b = append(b, '/') 315 | case 'b': 316 | b = append(b, '\b') 317 | case 'f': 318 | b = append(b, '\f') 319 | case 'n': 320 | b = append(b, '\n') 321 | case 'r': 322 | b = append(b, '\r') 323 | case 't': 324 | b = append(b, '\t') 325 | case 'u': 326 | if len(s) < 4 { 327 | // Too short escape sequence. Just store it unchanged. 328 | b = append(b, "\\u"...) 329 | break 330 | } 331 | xs := s[:4] 332 | x, err := strconv.ParseUint(xs, 16, 16) 333 | if err != nil { 334 | // Invalid escape sequence. Just store it unchanged. 335 | b = append(b, "\\u"...) 336 | break 337 | } 338 | s = s[4:] 339 | if !utf16.IsSurrogate(rune(x)) { 340 | b = append(b, string(rune(x))...) 341 | break 342 | } 343 | 344 | // Surrogate. 345 | // See https://en.wikipedia.org/wiki/Universal_Character_Set_characters#Surrogates 346 | if len(s) < 6 || s[0] != '\\' || s[1] != 'u' { 347 | b = append(b, "\\u"...) 348 | b = append(b, xs...) 349 | break 350 | } 351 | x1, err := strconv.ParseUint(s[2:6], 16, 16) 352 | if err != nil { 353 | b = append(b, "\\u"...) 354 | b = append(b, xs...) 355 | break 356 | } 357 | r := utf16.DecodeRune(rune(x), rune(x1)) 358 | b = append(b, string(r)...) 359 | s = s[6:] 360 | default: 361 | // Unknown escape sequence. Just store it unchanged. 362 | b = append(b, '\\', ch) 363 | } 364 | n = strings.IndexByte(s, '\\') 365 | if n < 0 { 366 | b = append(b, s...) 367 | break 368 | } 369 | b = append(b, s[:n]...) 370 | s = s[n+1:] 371 | } 372 | return b2s(b) 373 | } 374 | 375 | // parseRawKey is similar to parseRawString, but is optimized 376 | // for small-sized keys without escape sequences. 377 | func parseRawKey(s string) (string, string, error) { 378 | for i := 0; i < len(s); i++ { 379 | if s[i] == '"' { 380 | // Fast path. 381 | return s[:i], s[i+1:], nil 382 | } 383 | if s[i] == '\\' { 384 | // Slow path. 385 | return parseRawString(s) 386 | } 387 | } 388 | return s, "", fmt.Errorf(`missing closing '"'`) 389 | } 390 | 391 | func parseRawString(s string) (string, string, error) { 392 | n := strings.IndexByte(s, '"') 393 | if n < 0 { 394 | return s, "", fmt.Errorf(`missing closing '"'`) 395 | } 396 | if n == 0 || s[n-1] != '\\' { 397 | // Fast path. No escaped ". 398 | return s[:n], s[n+1:], nil 399 | } 400 | 401 | // Slow path - possible escaped " found. 402 | ss := s 403 | for { 404 | i := n - 1 405 | for i > 0 && s[i-1] == '\\' { 406 | i-- 407 | } 408 | if uint(n-i)%2 == 0 { 409 | return ss[:len(ss)-len(s)+n], s[n+1:], nil 410 | } 411 | s = s[n+1:] 412 | 413 | n = strings.IndexByte(s, '"') 414 | if n < 0 { 415 | return ss, "", fmt.Errorf(`missing closing '"'`) 416 | } 417 | if n == 0 || s[n-1] != '\\' { 418 | return ss[:len(ss)-len(s)+n], s[n+1:], nil 419 | } 420 | } 421 | } 422 | 423 | func parseRawNumber(s string) (string, string, error) { 424 | // The caller must ensure len(s) > 0 425 | 426 | // Find the end of the number. 427 | for i := 0; i < len(s); i++ { 428 | ch := s[i] 429 | if (ch >= '0' && ch <= '9') || ch == '.' || ch == '-' || ch == 'e' || ch == 'E' || ch == '+' { 430 | continue 431 | } 432 | if i == 0 || i == 1 && (s[0] == '-' || s[0] == '+') { 433 | if len(s[i:]) >= 3 { 434 | xs := s[i : i+3] 435 | if strings.EqualFold(xs, "inf") || strings.EqualFold(xs, "nan") { 436 | return s[:i+3], s[i+3:], nil 437 | } 438 | } 439 | return "", s, fmt.Errorf("unexpected char: %q", s[:1]) 440 | } 441 | ns := s[:i] 442 | s = s[i:] 443 | return ns, s, nil 444 | } 445 | return s, "", nil 446 | } 447 | 448 | // Object represents JSON object. 449 | // 450 | // Object cannot be used from concurrent goroutines. 451 | // Use per-goroutine parsers or ParserPool instead. 452 | type Object struct { 453 | kvs []kv 454 | keysUnescaped bool 455 | } 456 | 457 | func (o *Object) reset() { 458 | o.kvs = o.kvs[:0] 459 | o.keysUnescaped = false 460 | } 461 | 462 | // MarshalTo appends marshaled o to dst and returns the result. 463 | func (o *Object) MarshalTo(dst []byte) []byte { 464 | dst = append(dst, '{') 465 | for i, kv := range o.kvs { 466 | if o.keysUnescaped { 467 | dst = escapeString(dst, kv.k) 468 | } else { 469 | dst = append(dst, '"') 470 | dst = append(dst, kv.k...) 471 | dst = append(dst, '"') 472 | } 473 | dst = append(dst, ':') 474 | dst = kv.v.MarshalTo(dst) 475 | if i != len(o.kvs)-1 { 476 | dst = append(dst, ',') 477 | } 478 | } 479 | dst = append(dst, '}') 480 | return dst 481 | } 482 | 483 | // String returns string representation for the o. 484 | // 485 | // This function is for debugging purposes only. It isn't optimized for speed. 486 | // See MarshalTo instead. 487 | func (o *Object) String() string { 488 | b := o.MarshalTo(nil) 489 | // It is safe converting b to string without allocation, since b is no longer 490 | // reachable after this line. 491 | return b2s(b) 492 | } 493 | 494 | func (o *Object) getKV() *kv { 495 | if cap(o.kvs) > len(o.kvs) { 496 | o.kvs = o.kvs[:len(o.kvs)+1] 497 | } else { 498 | o.kvs = append(o.kvs, kv{}) 499 | } 500 | return &o.kvs[len(o.kvs)-1] 501 | } 502 | 503 | func (o *Object) unescapeKeys() { 504 | if o.keysUnescaped { 505 | return 506 | } 507 | kvs := o.kvs 508 | for i := range kvs { 509 | kv := &kvs[i] 510 | kv.k = unescapeStringBestEffort(kv.k) 511 | } 512 | o.keysUnescaped = true 513 | } 514 | 515 | // Len returns the number of items in the o. 516 | func (o *Object) Len() int { 517 | return len(o.kvs) 518 | } 519 | 520 | // Get returns the value for the given key in the o. 521 | // 522 | // Returns nil if the value for the given key isn't found. 523 | // 524 | // The returned value is valid until Parse is called on the Parser returned o. 525 | func (o *Object) Get(key string) *Value { 526 | if !o.keysUnescaped && strings.IndexByte(key, '\\') < 0 { 527 | // Fast path - try searching for the key without object keys unescaping. 528 | for _, kv := range o.kvs { 529 | if kv.k == key { 530 | return kv.v 531 | } 532 | } 533 | } 534 | 535 | // Slow path - unescape object keys. 536 | o.unescapeKeys() 537 | 538 | for _, kv := range o.kvs { 539 | if kv.k == key { 540 | return kv.v 541 | } 542 | } 543 | return nil 544 | } 545 | 546 | // Visit calls f for each item in the o in the original order 547 | // of the parsed JSON. 548 | // 549 | // f cannot hold key and/or v after returning. 550 | func (o *Object) Visit(f func(key []byte, v *Value)) { 551 | if o == nil { 552 | return 553 | } 554 | 555 | o.unescapeKeys() 556 | 557 | for _, kv := range o.kvs { 558 | f(s2b(kv.k), kv.v) 559 | } 560 | } 561 | 562 | // Value represents any JSON value. 563 | // 564 | // Call Type in order to determine the actual type of the JSON value. 565 | // 566 | // Value cannot be used from concurrent goroutines. 567 | // Use per-goroutine parsers or ParserPool instead. 568 | type Value struct { 569 | o Object 570 | a []*Value 571 | s string 572 | t Type 573 | } 574 | 575 | // MarshalTo appends marshaled v to dst and returns the result. 576 | func (v *Value) MarshalTo(dst []byte) []byte { 577 | switch v.t { 578 | case typeRawString: 579 | dst = append(dst, '"') 580 | dst = append(dst, v.s...) 581 | dst = append(dst, '"') 582 | return dst 583 | case TypeObject: 584 | return v.o.MarshalTo(dst) 585 | case TypeArray: 586 | dst = append(dst, '[') 587 | for i, vv := range v.a { 588 | dst = vv.MarshalTo(dst) 589 | if i != len(v.a)-1 { 590 | dst = append(dst, ',') 591 | } 592 | } 593 | dst = append(dst, ']') 594 | return dst 595 | case TypeString: 596 | return escapeString(dst, v.s) 597 | case TypeNumber: 598 | return append(dst, v.s...) 599 | case TypeTrue: 600 | return append(dst, "true"...) 601 | case TypeFalse: 602 | return append(dst, "false"...) 603 | case TypeNull: 604 | return append(dst, "null"...) 605 | default: 606 | panic(fmt.Errorf("BUG: unexpected Value type: %d", v.t)) 607 | } 608 | } 609 | 610 | // String returns string representation of the v. 611 | // 612 | // The function is for debugging purposes only. It isn't optimized for speed. 613 | // See MarshalTo instead. 614 | // 615 | // Don't confuse this function with StringBytes, which must be called 616 | // for obtaining the underlying JSON string for the v. 617 | func (v *Value) String() string { 618 | b := v.MarshalTo(nil) 619 | // It is safe converting b to string without allocation, since b is no longer 620 | // reachable after this line. 621 | return b2s(b) 622 | } 623 | 624 | // Type represents JSON type. 625 | type Type int 626 | 627 | const ( 628 | // TypeNull is JSON null. 629 | TypeNull Type = 0 630 | 631 | // TypeObject is JSON object type. 632 | TypeObject Type = 1 633 | 634 | // TypeArray is JSON array type. 635 | TypeArray Type = 2 636 | 637 | // TypeString is JSON string type. 638 | TypeString Type = 3 639 | 640 | // TypeNumber is JSON number type. 641 | TypeNumber Type = 4 642 | 643 | // TypeTrue is JSON true. 644 | TypeTrue Type = 5 645 | 646 | // TypeFalse is JSON false. 647 | TypeFalse Type = 6 648 | 649 | typeRawString Type = 7 650 | ) 651 | 652 | // String returns string representation of t. 653 | func (t Type) String() string { 654 | switch t { 655 | case TypeObject: 656 | return "object" 657 | case TypeArray: 658 | return "array" 659 | case TypeString: 660 | return "string" 661 | case TypeNumber: 662 | return "number" 663 | case TypeTrue: 664 | return "true" 665 | case TypeFalse: 666 | return "false" 667 | case TypeNull: 668 | return "null" 669 | 670 | // typeRawString is skipped intentionally, 671 | // since it shouldn't be visible to user. 672 | default: 673 | panic(fmt.Errorf("BUG: unknown Value type: %d", t)) 674 | } 675 | } 676 | 677 | // Type returns the type of the v. 678 | func (v *Value) Type() Type { 679 | if v.t == typeRawString { 680 | v.s = unescapeStringBestEffort(v.s) 681 | v.t = TypeString 682 | } 683 | return v.t 684 | } 685 | 686 | // Exists returns true if the field exists for the given keys path. 687 | // 688 | // Array indexes may be represented as decimal numbers in keys. 689 | func (v *Value) Exists(keys ...string) bool { 690 | v = v.Get(keys...) 691 | return v != nil 692 | } 693 | 694 | // Get returns value by the given keys path. 695 | // 696 | // Array indexes may be represented as decimal numbers in keys. 697 | // 698 | // nil is returned for non-existing keys path. 699 | // 700 | // The returned value is valid until Parse is called on the Parser returned v. 701 | func (v *Value) Get(keys ...string) *Value { 702 | if v == nil { 703 | return nil 704 | } 705 | for _, key := range keys { 706 | if v.t == TypeObject { 707 | v = v.o.Get(key) 708 | if v == nil { 709 | return nil 710 | } 711 | } else if v.t == TypeArray { 712 | n, err := strconv.Atoi(key) 713 | if err != nil || n < 0 || n >= len(v.a) { 714 | return nil 715 | } 716 | v = v.a[n] 717 | } else { 718 | return nil 719 | } 720 | } 721 | return v 722 | } 723 | 724 | // GetObject returns object value by the given keys path. 725 | // 726 | // Array indexes may be represented as decimal numbers in keys. 727 | // 728 | // nil is returned for non-existing keys path or for invalid value type. 729 | // 730 | // The returned object is valid until Parse is called on the Parser returned v. 731 | func (v *Value) GetObject(keys ...string) *Object { 732 | v = v.Get(keys...) 733 | if v == nil || v.t != TypeObject { 734 | return nil 735 | } 736 | return &v.o 737 | } 738 | 739 | // GetArray returns array value by the given keys path. 740 | // 741 | // Array indexes may be represented as decimal numbers in keys. 742 | // 743 | // nil is returned for non-existing keys path or for invalid value type. 744 | // 745 | // The returned array is valid until Parse is called on the Parser returned v. 746 | func (v *Value) GetArray(keys ...string) []*Value { 747 | v = v.Get(keys...) 748 | if v == nil || v.t != TypeArray { 749 | return nil 750 | } 751 | return v.a 752 | } 753 | 754 | // GetFloat64 returns float64 value by the given keys path. 755 | // 756 | // Array indexes may be represented as decimal numbers in keys. 757 | // 758 | // 0 is returned for non-existing keys path or for invalid value type. 759 | func (v *Value) GetFloat64(keys ...string) float64 { 760 | v = v.Get(keys...) 761 | if v == nil || v.Type() != TypeNumber { 762 | return 0 763 | } 764 | return fastfloat.ParseBestEffort(v.s) 765 | } 766 | 767 | // GetInt returns int value by the given keys path. 768 | // 769 | // Array indexes may be represented as decimal numbers in keys. 770 | // 771 | // 0 is returned for non-existing keys path or for invalid value type. 772 | func (v *Value) GetInt(keys ...string) int { 773 | v = v.Get(keys...) 774 | if v == nil || v.Type() != TypeNumber { 775 | return 0 776 | } 777 | n := fastfloat.ParseInt64BestEffort(v.s) 778 | nn := int(n) 779 | if int64(nn) != n { 780 | return 0 781 | } 782 | return nn 783 | } 784 | 785 | // GetUint returns uint value by the given keys path. 786 | // 787 | // Array indexes may be represented as decimal numbers in keys. 788 | // 789 | // 0 is returned for non-existing keys path or for invalid value type. 790 | func (v *Value) GetUint(keys ...string) uint { 791 | v = v.Get(keys...) 792 | if v == nil || v.Type() != TypeNumber { 793 | return 0 794 | } 795 | n := fastfloat.ParseUint64BestEffort(v.s) 796 | nn := uint(n) 797 | if uint64(nn) != n { 798 | return 0 799 | } 800 | return nn 801 | } 802 | 803 | // GetInt64 returns int64 value by the given keys path. 804 | // 805 | // Array indexes may be represented as decimal numbers in keys. 806 | // 807 | // 0 is returned for non-existing keys path or for invalid value type. 808 | func (v *Value) GetInt64(keys ...string) int64 { 809 | v = v.Get(keys...) 810 | if v == nil || v.Type() != TypeNumber { 811 | return 0 812 | } 813 | return fastfloat.ParseInt64BestEffort(v.s) 814 | } 815 | 816 | // GetUint64 returns uint64 value by the given keys path. 817 | // 818 | // Array indexes may be represented as decimal numbers in keys. 819 | // 820 | // 0 is returned for non-existing keys path or for invalid value type. 821 | func (v *Value) GetUint64(keys ...string) uint64 { 822 | v = v.Get(keys...) 823 | if v == nil || v.Type() != TypeNumber { 824 | return 0 825 | } 826 | return fastfloat.ParseUint64BestEffort(v.s) 827 | } 828 | 829 | // GetStringBytes returns string value by the given keys path. 830 | // 831 | // Array indexes may be represented as decimal numbers in keys. 832 | // 833 | // nil is returned for non-existing keys path or for invalid value type. 834 | // 835 | // The returned string is valid until Parse is called on the Parser returned v. 836 | func (v *Value) GetStringBytes(keys ...string) []byte { 837 | v = v.Get(keys...) 838 | if v == nil || v.Type() != TypeString { 839 | return nil 840 | } 841 | return s2b(v.s) 842 | } 843 | 844 | // GetBool returns bool value by the given keys path. 845 | // 846 | // Array indexes may be represented as decimal numbers in keys. 847 | // 848 | // false is returned for non-existing keys path or for invalid value type. 849 | func (v *Value) GetBool(keys ...string) bool { 850 | v = v.Get(keys...) 851 | if v != nil && v.t == TypeTrue { 852 | return true 853 | } 854 | return false 855 | } 856 | 857 | // Object returns the underlying JSON object for the v. 858 | // 859 | // The returned object is valid until Parse is called on the Parser returned v. 860 | // 861 | // Use GetObject if you don't need error handling. 862 | func (v *Value) Object() (*Object, error) { 863 | if v.t != TypeObject { 864 | return nil, fmt.Errorf("value doesn't contain object; it contains %s", v.Type()) 865 | } 866 | return &v.o, nil 867 | } 868 | 869 | // Array returns the underlying JSON array for the v. 870 | // 871 | // The returned array is valid until Parse is called on the Parser returned v. 872 | // 873 | // Use GetArray if you don't need error handling. 874 | func (v *Value) Array() ([]*Value, error) { 875 | if v.t != TypeArray { 876 | return nil, fmt.Errorf("value doesn't contain array; it contains %s", v.Type()) 877 | } 878 | return v.a, nil 879 | } 880 | 881 | // StringBytes returns the underlying JSON string for the v. 882 | // 883 | // The returned string is valid until Parse is called on the Parser returned v. 884 | // 885 | // Use GetStringBytes if you don't need error handling. 886 | func (v *Value) StringBytes() ([]byte, error) { 887 | if v.Type() != TypeString { 888 | return nil, fmt.Errorf("value doesn't contain string; it contains %s", v.Type()) 889 | } 890 | return s2b(v.s), nil 891 | } 892 | 893 | // Float64 returns the underlying JSON number for the v. 894 | // 895 | // Use GetFloat64 if you don't need error handling. 896 | func (v *Value) Float64() (float64, error) { 897 | if v.Type() != TypeNumber { 898 | return 0, fmt.Errorf("value doesn't contain number; it contains %s", v.Type()) 899 | } 900 | return fastfloat.Parse(v.s) 901 | } 902 | 903 | // Int returns the underlying JSON int for the v. 904 | // 905 | // Use GetInt if you don't need error handling. 906 | func (v *Value) Int() (int, error) { 907 | if v.Type() != TypeNumber { 908 | return 0, fmt.Errorf("value doesn't contain number; it contains %s", v.Type()) 909 | } 910 | n, err := fastfloat.ParseInt64(v.s) 911 | if err != nil { 912 | return 0, err 913 | } 914 | nn := int(n) 915 | if int64(nn) != n { 916 | return 0, fmt.Errorf("number %q doesn't fit int", v.s) 917 | } 918 | return nn, nil 919 | } 920 | 921 | // Uint returns the underlying JSON uint for the v. 922 | // 923 | // Use GetInt if you don't need error handling. 924 | func (v *Value) Uint() (uint, error) { 925 | if v.Type() != TypeNumber { 926 | return 0, fmt.Errorf("value doesn't contain number; it contains %s", v.Type()) 927 | } 928 | n, err := fastfloat.ParseUint64(v.s) 929 | if err != nil { 930 | return 0, err 931 | } 932 | nn := uint(n) 933 | if uint64(nn) != n { 934 | return 0, fmt.Errorf("number %q doesn't fit uint", v.s) 935 | } 936 | return nn, nil 937 | } 938 | 939 | // Int64 returns the underlying JSON int64 for the v. 940 | // 941 | // Use GetInt64 if you don't need error handling. 942 | func (v *Value) Int64() (int64, error) { 943 | if v.Type() != TypeNumber { 944 | return 0, fmt.Errorf("value doesn't contain number; it contains %s", v.Type()) 945 | } 946 | return fastfloat.ParseInt64(v.s) 947 | } 948 | 949 | // Uint64 returns the underlying JSON uint64 for the v. 950 | // 951 | // Use GetInt64 if you don't need error handling. 952 | func (v *Value) Uint64() (uint64, error) { 953 | if v.Type() != TypeNumber { 954 | return 0, fmt.Errorf("value doesn't contain number; it contains %s", v.Type()) 955 | } 956 | return fastfloat.ParseUint64(v.s) 957 | } 958 | 959 | // Bool returns the underlying JSON bool for the v. 960 | // 961 | // Use GetBool if you don't need error handling. 962 | func (v *Value) Bool() (bool, error) { 963 | if v.t == TypeTrue { 964 | return true, nil 965 | } 966 | if v.t == TypeFalse { 967 | return false, nil 968 | } 969 | return false, fmt.Errorf("value doesn't contain bool; it contains %s", v.Type()) 970 | } 971 | 972 | var ( 973 | valueTrue = &Value{t: TypeTrue} 974 | valueFalse = &Value{t: TypeFalse} 975 | valueNull = &Value{t: TypeNull} 976 | ) 977 | -------------------------------------------------------------------------------- /parser_example_test.go: -------------------------------------------------------------------------------- 1 | package fastjson_test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/valyala/fastjson" 6 | "log" 7 | "strconv" 8 | ) 9 | 10 | func ExampleParser_Parse() { 11 | var p fastjson.Parser 12 | v, err := p.Parse(`{"foo":"bar", "baz": 123}`) 13 | if err != nil { 14 | log.Fatalf("cannot parse json: %s", err) 15 | } 16 | 17 | fmt.Printf("foo=%s, baz=%d", v.GetStringBytes("foo"), v.GetInt("baz")) 18 | 19 | // Output: 20 | // foo=bar, baz=123 21 | } 22 | 23 | func ExampleParser_Parse_reuse() { 24 | var p fastjson.Parser 25 | 26 | // p may be re-used for parsing multiple json strings. 27 | // This improves parsing speed by reducing the number 28 | // of memory allocations. 29 | // 30 | // Parse call invalidates all the objects previously obtained from p, 31 | // so don't hold these objects after parsing the next json. 32 | 33 | for i := 0; i < 3; i++ { 34 | s := fmt.Sprintf(`["foo_%d","bar_%d","%d"]`, i, i, i) 35 | v, err := p.Parse(s) 36 | if err != nil { 37 | log.Fatalf("cannot parse json: %s", err) 38 | } 39 | key := strconv.Itoa(i) 40 | fmt.Printf("a[%d]=%s\n", i, v.GetStringBytes(key)) 41 | } 42 | 43 | // Output: 44 | // a[0]=foo_0 45 | // a[1]=bar_1 46 | // a[2]=2 47 | } 48 | 49 | func ExampleValue_MarshalTo() { 50 | s := `{ 51 | "name": "John", 52 | "items": [ 53 | { 54 | "key": "foo", 55 | "value": 123.456, 56 | "arr": [1, "foo"] 57 | }, 58 | { 59 | "key": "bar", 60 | "field": [3, 4, 5] 61 | } 62 | ] 63 | }` 64 | var p fastjson.Parser 65 | v, err := p.Parse(s) 66 | if err != nil { 67 | log.Fatalf("cannot parse json: %s", err) 68 | } 69 | 70 | // Marshal items.0 into newly allocated buffer. 71 | buf := v.Get("items", "0").MarshalTo(nil) 72 | fmt.Printf("items.0 = %s\n", buf) 73 | 74 | // Re-use buf for marshaling items.1. 75 | buf = v.Get("items", "1").MarshalTo(buf[:0]) 76 | fmt.Printf("items.1 = %s\n", buf) 77 | 78 | // Output: 79 | // items.0 = {"key":"foo","value":123.456,"arr":[1,"foo"]} 80 | // items.1 = {"key":"bar","field":[3,4,5]} 81 | } 82 | 83 | func ExampleValue_Get() { 84 | s := `{"foo":[{"bar":{"baz":123,"x":"434"},"y":[]},[null, false]],"qwe":true}` 85 | var p fastjson.Parser 86 | v, err := p.Parse(s) 87 | if err != nil { 88 | log.Fatalf("cannot parse json: %s", err) 89 | } 90 | 91 | vv := v.Get("foo", "0", "bar", "x") 92 | fmt.Printf("foo[0].bar.x=%s\n", vv.GetStringBytes()) 93 | 94 | vv = v.Get("qwe") 95 | fmt.Printf("qwe=%v\n", vv.GetBool()) 96 | 97 | vv = v.Get("foo", "1") 98 | fmt.Printf("foo[1]=%s\n", vv) 99 | 100 | vv = v.Get("foo").Get("1").Get("1") 101 | fmt.Printf("foo[1][1]=%s\n", vv) 102 | 103 | // non-existing key 104 | vv = v.Get("foo").Get("bar").Get("baz", "1234") 105 | fmt.Printf("foo.bar.baz[1234]=%v\n", vv) 106 | 107 | // Output: 108 | // foo[0].bar.x=434 109 | // qwe=true 110 | // foo[1]=[null,false] 111 | // foo[1][1]=false 112 | // foo.bar.baz[1234]= 113 | } 114 | 115 | func ExampleValue_Type() { 116 | s := `{ 117 | "object": {}, 118 | "array": [], 119 | "string": "foobar", 120 | "number": 123.456, 121 | "true": true, 122 | "false": false, 123 | "null": null 124 | }` 125 | 126 | var p fastjson.Parser 127 | v, err := p.Parse(s) 128 | if err != nil { 129 | log.Fatalf("cannot parse json: %s", err) 130 | } 131 | 132 | fmt.Printf("%s\n", v.Get("object").Type()) 133 | fmt.Printf("%s\n", v.Get("array").Type()) 134 | fmt.Printf("%s\n", v.Get("string").Type()) 135 | fmt.Printf("%s\n", v.Get("number").Type()) 136 | fmt.Printf("%s\n", v.Get("true").Type()) 137 | fmt.Printf("%s\n", v.Get("false").Type()) 138 | fmt.Printf("%s\n", v.Get("null").Type()) 139 | 140 | // Output: 141 | // object 142 | // array 143 | // string 144 | // number 145 | // true 146 | // false 147 | // null 148 | } 149 | 150 | func ExampleObject_Visit() { 151 | s := `{ 152 | "obj": { "foo": 1234 }, 153 | "arr": [ 23,4, "bar" ], 154 | "str": "foobar" 155 | }` 156 | 157 | var p fastjson.Parser 158 | v, err := p.Parse(s) 159 | if err != nil { 160 | log.Fatalf("cannot parse json: %s", err) 161 | } 162 | o, err := v.Object() 163 | if err != nil { 164 | log.Fatalf("cannot obtain object from json value: %s", err) 165 | } 166 | 167 | o.Visit(func(k []byte, v *fastjson.Value) { 168 | switch string(k) { 169 | case "obj": 170 | fmt.Printf("object %s\n", v) 171 | case "arr": 172 | fmt.Printf("array %s\n", v) 173 | case "str": 174 | fmt.Printf("string %s\n", v) 175 | } 176 | }) 177 | 178 | // Output: 179 | // object {"foo":1234} 180 | // array [23,4,"bar"] 181 | // string "foobar" 182 | } 183 | 184 | func ExampleValue_GetStringBytes() { 185 | s := `[ 186 | {"foo": "bar"}, 187 | [123, "baz"] 188 | ]` 189 | 190 | var p fastjson.Parser 191 | v, err := p.Parse(s) 192 | if err != nil { 193 | log.Fatalf("cannot parse json: %s", err) 194 | } 195 | fmt.Printf("v[0].foo = %q\n", v.GetStringBytes("0", "foo")) 196 | fmt.Printf("v[1][1] = %q\n", v.GetStringBytes("1", "1")) 197 | fmt.Printf("v[1][0] = %q\n", v.GetStringBytes("1", "0")) 198 | fmt.Printf("v.foo.bar.baz = %q\n", v.GetStringBytes("foo", "bar", "baz")) 199 | 200 | // Output: 201 | // v[0].foo = "bar" 202 | // v[1][1] = "baz" 203 | // v[1][0] = "" 204 | // v.foo.bar.baz = "" 205 | } 206 | -------------------------------------------------------------------------------- /parser_test.go: -------------------------------------------------------------------------------- 1 | package fastjson 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "strings" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func TestParseRawNumber(t *testing.T) { 12 | t.Run("success", func(t *testing.T) { 13 | f := func(s, expectedRN, expectedTail string) { 14 | t.Helper() 15 | 16 | rn, tail, err := parseRawNumber(s) 17 | if err != nil { 18 | t.Fatalf("unexpected error: %s", err) 19 | } 20 | if rn != expectedRN { 21 | t.Fatalf("unexpected raw number; got %q; want %q", rn, expectedRN) 22 | } 23 | if tail != expectedTail { 24 | t.Fatalf("unexpected tail; got %q; want %q", tail, expectedTail) 25 | } 26 | } 27 | 28 | f("0", "0", "") 29 | f("0tail", "0", "tail") 30 | f("123", "123", "") 31 | f("123tail", "123", "tail") 32 | f("-123tail", "-123", "tail") 33 | f("-12.345tail", "-12.345", "tail") 34 | f("-12.345e67tail", "-12.345e67", "tail") 35 | f("-12.345E+67 tail", "-12.345E+67", " tail") 36 | f("-12.345E-67,tail", "-12.345E-67", ",tail") 37 | f("-1234567.8e+90tail", "-1234567.8e+90", "tail") 38 | f("12.tail", "12.", "tail") 39 | f(".2tail", ".2", "tail") 40 | f("-.2tail", "-.2", "tail") 41 | f("NaN", "NaN", "") 42 | f("nantail", "nan", "tail") 43 | f("inf", "inf", "") 44 | f("Inftail", "Inf", "tail") 45 | f("-INF", "-INF", "") 46 | f("-Inftail", "-Inf", "tail") 47 | }) 48 | 49 | t.Run("error", func(t *testing.T) { 50 | f := func(s, expectedTail string) { 51 | t.Helper() 52 | 53 | _, tail, err := parseRawNumber(s) 54 | if err == nil { 55 | t.Fatalf("expecting non-nil error") 56 | } 57 | if tail != expectedTail { 58 | t.Fatalf("unexpected tail; got %q; want %q", tail, expectedTail) 59 | } 60 | } 61 | 62 | f("xyz", "xyz") 63 | f(" ", " ") 64 | f("[", "[") 65 | f(",", ",") 66 | f("{", "{") 67 | f("\"", "\"") 68 | }) 69 | } 70 | 71 | func TestUnescapeStringBestEffort(t *testing.T) { 72 | t.Run("success", func(t *testing.T) { 73 | testUnescapeStringBestEffort(t, ``, ``) 74 | testUnescapeStringBestEffort(t, `\"`, `"`) 75 | testUnescapeStringBestEffort(t, `\\`, `\`) 76 | testUnescapeStringBestEffort(t, `\\\"`, `\"`) 77 | testUnescapeStringBestEffort(t, `\\\"абв`, `\"абв`) 78 | testUnescapeStringBestEffort(t, `йцук\n\"\\Y`, "йцук\n\"\\Y") 79 | testUnescapeStringBestEffort(t, `q\u1234we`, "q\u1234we") 80 | testUnescapeStringBestEffort(t, `п\ud83e\udd2dи`, "п🤭и") 81 | }) 82 | 83 | t.Run("error", func(t *testing.T) { 84 | testUnescapeStringBestEffort(t, `\`, ``) 85 | testUnescapeStringBestEffort(t, `foo\qwe`, `foo\qwe`) 86 | testUnescapeStringBestEffort(t, `\"x\uyz\"`, `"x\uyz"`) 87 | testUnescapeStringBestEffort(t, `\u12\"пролw`, `\u12"пролw`) 88 | testUnescapeStringBestEffort(t, `п\ud83eи`, "п\\ud83eи") 89 | }) 90 | } 91 | 92 | func testUnescapeStringBestEffort(t *testing.T, s, expectedS string) { 93 | t.Helper() 94 | 95 | // unescapeString modifies the original s, so call it 96 | // on a byte slice copy. 97 | b := append([]byte{}, s...) 98 | us := unescapeStringBestEffort(b2s(b)) 99 | if us != expectedS { 100 | t.Fatalf("unexpected unescaped string; got %q; want %q", us, expectedS) 101 | } 102 | } 103 | 104 | func TestParseRawString(t *testing.T) { 105 | t.Run("success", func(t *testing.T) { 106 | f := func(s, expectedRS, expectedTail string) { 107 | t.Helper() 108 | 109 | rs, tail, err := parseRawString(s[1:]) 110 | if err != nil { 111 | t.Fatalf("unexpected error on parseRawString: %s", err) 112 | } 113 | if rs != expectedRS { 114 | t.Fatalf("unexpected string on parseRawString; got %q; want %q", rs, expectedRS) 115 | } 116 | if tail != expectedTail { 117 | t.Fatalf("unexpected tail on parseRawString; got %q; want %q", tail, expectedTail) 118 | } 119 | 120 | // parseRawKey results must be identical to parseRawString. 121 | rs, tail, err = parseRawKey(s[1:]) 122 | if err != nil { 123 | t.Fatalf("unexpected error on parseRawKey: %s", err) 124 | } 125 | if rs != expectedRS { 126 | t.Fatalf("unexpected string on parseRawKey; got %q; want %q", rs, expectedRS) 127 | } 128 | if tail != expectedTail { 129 | t.Fatalf("unexpected tail on parseRawKey; got %q; want %q", tail, expectedTail) 130 | } 131 | } 132 | 133 | f(`""`, "", "") 134 | f(`""xx`, "", "xx") 135 | f(`"foobar"`, "foobar", "") 136 | f(`"foobar"baz`, "foobar", "baz") 137 | f(`"\""`, `\"`, "") 138 | f(`"\""tail`, `\"`, "tail") 139 | f(`"\\"`, `\\`, "") 140 | f(`"\\"tail`, `\\`, "tail") 141 | f(`"x\\"`, `x\\`, "") 142 | f(`"x\\"tail`, `x\\`, "tail") 143 | f(`"x\\y"`, `x\\y`, "") 144 | f(`"x\\y"tail`, `x\\y`, "tail") 145 | f(`"\\\"й\n\"я"tail`, `\\\"й\n\"я`, "tail") 146 | f(`"\\\\\\\\"tail`, `\\\\\\\\`, "tail") 147 | 148 | }) 149 | 150 | t.Run("error", func(t *testing.T) { 151 | f := func(s, expectedTail string) { 152 | t.Helper() 153 | 154 | _, tail, err := parseRawString(s[1:]) 155 | if err == nil { 156 | t.Fatalf("expecting non-nil error on parseRawString") 157 | } 158 | if tail != expectedTail { 159 | t.Fatalf("unexpected tail on parseRawString; got %q; want %q", tail, expectedTail) 160 | } 161 | 162 | // parseRawKey results must be identical to parseRawString. 163 | _, tail, err = parseRawKey(s[1:]) 164 | if err == nil { 165 | t.Fatalf("expecting non-nil error on parseRawKey") 166 | } 167 | if tail != expectedTail { 168 | t.Fatalf("unexpected tail on parseRawKey; got %q; want %q", tail, expectedTail) 169 | } 170 | } 171 | 172 | f(`"`, "") 173 | f(`"unclosed string`, "") 174 | f(`"\"`, "") 175 | f(`"\"unclosed`, "") 176 | f(`"foo\\\\\"тест\n\r\t`, "") 177 | }) 178 | } 179 | 180 | func TestParserPool(t *testing.T) { 181 | var pp ParserPool 182 | for i := 0; i < 10; i++ { 183 | p := pp.Get() 184 | if _, err := p.Parse("null"); err != nil { 185 | t.Fatalf("cannot parse null: %s", err) 186 | } 187 | pp.Put(p) 188 | } 189 | } 190 | 191 | func TestValueInvalidTypeConversion(t *testing.T) { 192 | var p Parser 193 | 194 | v, err := p.Parse(`[{},[],"",123.45,true,null]`) 195 | if err != nil { 196 | t.Fatalf("unexpected error: %s", err) 197 | } 198 | a := v.GetArray() 199 | 200 | // object 201 | _, err = a[0].Object() 202 | if err != nil { 203 | t.Fatalf("unexpected error when obtaining object: %s", err) 204 | } 205 | _, err = a[0].Array() 206 | if err == nil { 207 | t.Fatalf("expecting non-nil error when trying to obtain array from object") 208 | } 209 | 210 | // array 211 | _, err = a[1].Array() 212 | if err != nil { 213 | t.Fatalf("unexpected error when obtaining array: %s", err) 214 | } 215 | _, err = a[1].Object() 216 | if err == nil { 217 | t.Fatalf("expecting non-nil error when trying to obtain object from array") 218 | } 219 | 220 | // string 221 | _, err = a[2].StringBytes() 222 | if err != nil { 223 | t.Fatalf("unexpected error when obtaining string: %s", err) 224 | } 225 | _, err = a[2].Int() 226 | if err == nil { 227 | t.Fatalf("expecting non-nil error when trying to obtain int from string") 228 | } 229 | _, err = a[2].Int64() 230 | if err == nil { 231 | t.Fatalf("expecting non-nil error when trying to obtain int64 from string") 232 | } 233 | _, err = a[2].Uint() 234 | if err == nil { 235 | t.Fatalf("expecting non-nil error when trying to obtain uint from string") 236 | } 237 | _, err = a[2].Uint64() 238 | if err == nil { 239 | t.Fatalf("expecting non-nil error when trying to obtain uint64 from string") 240 | } 241 | _, err = a[2].Float64() 242 | if err == nil { 243 | t.Fatalf("expecting non-nil error when trying to obtain float64 from string") 244 | } 245 | 246 | // number 247 | _, err = a[3].Float64() 248 | if err != nil { 249 | t.Fatalf("unexpected error when obtaining float64: %s", err) 250 | } 251 | _, err = a[3].StringBytes() 252 | if err == nil { 253 | t.Fatalf("expecting non-nil error when trying to obtain string from number") 254 | } 255 | 256 | // true 257 | _, err = a[4].Bool() 258 | if err != nil { 259 | t.Fatalf("unexpected error when obtaining bool: %s", err) 260 | } 261 | _, err = a[4].StringBytes() 262 | if err == nil { 263 | t.Fatalf("expecting non-nil error when trying to obtain string from bool") 264 | } 265 | 266 | // null 267 | _, err = a[5].Bool() 268 | if err == nil { 269 | t.Fatalf("expecting non-nil error when trying to obtain bool from null") 270 | } 271 | } 272 | 273 | func TestValueGetTyped(t *testing.T) { 274 | var p Parser 275 | 276 | v, err := p.Parse(`{"foo": 123, "bar": "433", "baz": true, "obj":{}, "arr":[1,2,3], 277 | "zero_float1": 0.00, 278 | "zero_float2": -0e123, 279 | "inf_float": Inf, 280 | "minus_inf_float": -Inf, 281 | "nan": nan 282 | }`) 283 | if err != nil { 284 | t.Fatalf("unexpected error: %s", err) 285 | } 286 | 287 | if !v.Exists("foo") { 288 | t.Fatalf("foo must exist in the v") 289 | } 290 | if v.Exists("foo", "bar") { 291 | t.Fatalf("foo.bar mustn't exist in the v") 292 | } 293 | if v.Exists("foobar") { 294 | t.Fatalf("foobar mustn't exist in the v") 295 | } 296 | 297 | o := v.GetObject("obj") 298 | os := o.String() 299 | if os != "{}" { 300 | t.Fatalf("unexpected object; got %s; want %s", os, "{}") 301 | } 302 | o = v.GetObject("arr") 303 | if o != nil { 304 | t.Fatalf("unexpected non-nil object: %s", o) 305 | } 306 | o = v.GetObject("foo", "bar") 307 | if o != nil { 308 | t.Fatalf("unexpected non-nil object: %s", o) 309 | } 310 | a := v.GetArray("arr") 311 | if len(a) != 3 { 312 | t.Fatalf("unexpected array len; got %d; want %d", len(a), 3) 313 | } 314 | a = v.GetArray("obj") 315 | if a != nil { 316 | t.Fatalf("unexpected non-nil array: %s", a) 317 | } 318 | a = v.GetArray("foo", "bar") 319 | if a != nil { 320 | t.Fatalf("unexpected non-nil array: %s", a) 321 | } 322 | n := v.GetInt("foo") 323 | if n != 123 { 324 | t.Fatalf("unexpected value; got %d; want %d", n, 123) 325 | } 326 | n64 := v.GetInt64("foo") 327 | if n != 123 { 328 | t.Fatalf("unexpected value; got %d; want %d", n64, 123) 329 | } 330 | un := v.GetUint("foo") 331 | if un != 123 { 332 | t.Fatalf("unexpected value; got %d; want %d", un, 123) 333 | } 334 | un64 := v.GetUint64("foo") 335 | if un != 123 { 336 | t.Fatalf("unexpected value; got %d; want %d", un64, 123) 337 | } 338 | n = v.GetInt("bar") 339 | if n != 0 { 340 | t.Fatalf("unexpected non-zero value; got %d", n) 341 | } 342 | n64 = v.GetInt64("bar") 343 | if n != 0 { 344 | t.Fatalf("unexpected non-zero value; got %d", n64) 345 | } 346 | un = v.GetUint("bar") 347 | if n != 0 { 348 | t.Fatalf("unexpected non-zero value; got %d", un) 349 | } 350 | un64 = v.GetUint64("bar") 351 | if n != 0 { 352 | t.Fatalf("unexpected non-zero value; got %d", n64) 353 | } 354 | f := v.GetFloat64("foo") 355 | if f != 123.0 { 356 | t.Fatalf("unexpected value; got %f; want %f", f, 123.0) 357 | } 358 | f = v.GetFloat64("bar") 359 | if f != 0 { 360 | t.Fatalf("unexpected value; got %f; want %f", f, 0.0) 361 | } 362 | f = v.GetFloat64("foooo", "bar") 363 | if f != 0 { 364 | t.Fatalf("unexpected value; got %f; want %f", f, 0.0) 365 | } 366 | f = v.GetFloat64() 367 | if f != 0 { 368 | t.Fatalf("unexpected value; got %f; want %f", f, 0.0) 369 | } 370 | sb := v.GetStringBytes("bar") 371 | if string(sb) != "433" { 372 | t.Fatalf("unexpected value; got %q; want %q", sb, "443") 373 | } 374 | sb = v.GetStringBytes("foo") 375 | if sb != nil { 376 | t.Fatalf("unexpected value; got %q; want %q", sb, []byte(nil)) 377 | } 378 | bv := v.GetBool("baz") 379 | if !bv { 380 | t.Fatalf("unexpected value; got %v; want %v", bv, true) 381 | } 382 | bv = v.GetBool("bar") 383 | if bv { 384 | t.Fatalf("unexpected value; got %v; want %v", bv, false) 385 | } 386 | 387 | zv := v.Get("zero_float1") 388 | zf, err := zv.Float64() 389 | if err != nil { 390 | t.Fatalf("unexpected error: %s", err) 391 | } 392 | if zf != 0 { 393 | t.Fatalf("unexpected zero_float1 value: %f. Expecting 0", zf) 394 | } 395 | 396 | zv = v.Get("zero_float2") 397 | zf, err = zv.Float64() 398 | if err != nil { 399 | t.Fatalf("unexpected error: %s", err) 400 | } 401 | if zf != 0 { 402 | t.Fatalf("unexpected zero_float1 value: %f. Expecting 0", zf) 403 | } 404 | 405 | infv := v.Get("inf_float") 406 | inff, err := infv.Float64() 407 | if err != nil { 408 | t.Fatalf("unexpected error: %s", err) 409 | } 410 | if !math.IsInf(inff, 1) { 411 | t.Fatalf("unexpected inf_float value: %f. Expecting %f", inff, math.Inf(1)) 412 | } 413 | 414 | ninfv := v.Get("minus_inf_float") 415 | ninff, err := ninfv.Float64() 416 | if err != nil { 417 | t.Fatalf("unexpected error: %s", err) 418 | } 419 | if !math.IsInf(ninff, -1) { 420 | t.Fatalf("unexpected inf_float value: %f. Expecting %f", ninff, math.Inf(-11)) 421 | } 422 | 423 | nanv := v.Get("nan") 424 | nanf, err := nanv.Float64() 425 | if err != nil { 426 | t.Fatalf("unexpected error: %s", err) 427 | } 428 | if !math.IsNaN(nanf) { 429 | t.Fatalf("unexpected nan value: %f. Expecting %f", nanf, math.NaN()) 430 | } 431 | } 432 | 433 | func TestVisitNil(t *testing.T) { 434 | var p Parser 435 | v, err := p.Parse(`{}`) 436 | if err != nil { 437 | t.Fatalf("unexpected error: %s", err) 438 | } 439 | o := v.GetObject("non-existing-key") 440 | if o != nil { 441 | t.Fatalf("obtained an object for non-existing key: %#v", o) 442 | } 443 | o.Visit(func(k []byte, v *Value) { 444 | t.Fatalf("unexpected visit call; k=%q; v=%s", k, v) 445 | }) 446 | } 447 | 448 | func TestValueGet(t *testing.T) { 449 | var pp ParserPool 450 | 451 | p := pp.Get() 452 | v, err := p.ParseBytes([]byte(`{"xx":33.33,"foo":[123,{"bar":["baz"],"x":"y"}], "": "empty-key", "empty-value": ""}`)) 453 | if err != nil { 454 | t.Fatalf("unexpected error: %s", err) 455 | } 456 | 457 | t.Run("positive", func(t *testing.T) { 458 | sb := v.GetStringBytes("") 459 | if string(sb) != "empty-key" { 460 | t.Fatalf("unexpected value for empty key; got %q; want %q", sb, "empty-key") 461 | } 462 | sb = v.GetStringBytes("empty-value") 463 | if string(sb) != "" { 464 | t.Fatalf("unexpected non-empty value: %q", sb) 465 | } 466 | 467 | vv := v.Get("foo", "1") 468 | if vv == nil { 469 | t.Fatalf("cannot find the required value") 470 | } 471 | o, err := vv.Object() 472 | if err != nil { 473 | t.Fatalf("cannot obtain object: %s", err) 474 | } 475 | 476 | n := 0 477 | o.Visit(func(k []byte, v *Value) { 478 | n++ 479 | switch string(k) { 480 | case "bar": 481 | if v.Type() != TypeArray { 482 | t.Fatalf("unexpected value type; got %d; want %d", v.Type(), TypeArray) 483 | } 484 | s := v.String() 485 | if s != `["baz"]` { 486 | t.Fatalf("unexpected array; got %q; want %q", s, `["baz"]`) 487 | } 488 | case "x": 489 | sb, err := v.StringBytes() 490 | if err != nil { 491 | t.Fatalf("cannot obtain string: %s", err) 492 | } 493 | if string(sb) != "y" { 494 | t.Fatalf("unexpected string; got %q; want %q", sb, "y") 495 | } 496 | default: 497 | t.Fatalf("unknown key: %s", k) 498 | } 499 | }) 500 | if n != 2 { 501 | t.Fatalf("unexpected number of items visited in the array; got %d; want %d", n, 2) 502 | } 503 | }) 504 | 505 | t.Run("negative", func(t *testing.T) { 506 | vv := v.Get("nonexisting", "path") 507 | if vv != nil { 508 | t.Fatalf("expecting nil value for nonexisting path. Got %#v", vv) 509 | } 510 | vv = v.Get("foo", "bar", "baz") 511 | if vv != nil { 512 | t.Fatalf("expecting nil value for nonexisting path. Got %#v", vv) 513 | } 514 | vv = v.Get("foo", "-123") 515 | if vv != nil { 516 | t.Fatalf("expecting nil value for nonexisting path. Got %#v", vv) 517 | } 518 | vv = v.Get("foo", "234") 519 | if vv != nil { 520 | t.Fatalf("expecting nil value for nonexisting path. Got %#v", vv) 521 | } 522 | vv = v.Get("xx", "yy") 523 | if vv != nil { 524 | t.Fatalf("expecting nil value for nonexisting path. Got %#v", vv) 525 | } 526 | }) 527 | 528 | pp.Put(p) 529 | } 530 | 531 | func TestParserParse(t *testing.T) { 532 | var p Parser 533 | 534 | t.Run("complex-string", func(t *testing.T) { 535 | v, err := p.Parse(`{"тест":1, "\\\"фыва\"":2, "\\\"\u1234x":"\\fЗУ\\\\"}`) 536 | if err != nil { 537 | t.Fatalf("unexpected error: %s", err) 538 | } 539 | n := v.GetInt("тест") 540 | if n != 1 { 541 | t.Fatalf("unexpected int; got %d; want %d", n, 1) 542 | } 543 | n = v.GetInt(`\"фыва"`) 544 | if n != 2 { 545 | t.Fatalf("unexpected int; got %d; want %d", n, 2) 546 | } 547 | sb := v.GetStringBytes("\\\"\u1234x") 548 | if string(sb) != `\fЗУ\\` { 549 | t.Fatalf("unexpected string; got %q; want %q", sb, `\fЗУ\\`) 550 | } 551 | }) 552 | 553 | t.Run("invalid-string-escape", func(t *testing.T) { 554 | v, err := p.Parse(`"fo\u"`) 555 | if err != nil { 556 | t.Fatalf("unexpected error when parsing string") 557 | } 558 | // Make sure only valid string part remains 559 | sb, err := v.StringBytes() 560 | if err != nil { 561 | t.Fatalf("cannot obtain string: %s", err) 562 | } 563 | if string(sb) != "fo\\u" { 564 | t.Fatalf("unexpected string; got %q; want %q", sb, "fo\\u") 565 | } 566 | 567 | v, err = p.Parse(`"foo\ubarz2134"`) 568 | if err != nil { 569 | t.Fatalf("unexpected error when parsing string") 570 | } 571 | sb, err = v.StringBytes() 572 | if err != nil { 573 | t.Fatalf("cannot obtain string: %s", err) 574 | } 575 | if string(sb) != "foo\\ubarz2134" { 576 | t.Fatalf("unexpected string; got %q; want %q", sb, "foo") 577 | } 578 | 579 | v, err = p.Parse(`"fo` + "\x19" + `\u"`) 580 | if err != nil { 581 | t.Fatalf("unexpected error when parsing string") 582 | } 583 | sb, err = v.StringBytes() 584 | if err != nil { 585 | t.Fatalf("cannot obtain string: %s", err) 586 | } 587 | if string(sb) != "fo\x19\\u" { 588 | t.Fatalf("unexpected string; got %q; want %q", sb, "fo\x19\\u") 589 | } 590 | }) 591 | 592 | t.Run("invalid-number", func(t *testing.T) { 593 | v, err := p.Parse("123+456") 594 | if err != nil { 595 | t.Fatalf("unexpected error when parsing int") 596 | } 597 | 598 | // Make sure invalid int isn't parsed. 599 | n, err := v.Int() 600 | if err == nil { 601 | t.Fatalf("expecting non-nil error") 602 | } 603 | if n != 0 { 604 | t.Fatalf("unexpected int; got %d; want %d", n, 0) 605 | } 606 | }) 607 | 608 | t.Run("empty-json", func(t *testing.T) { 609 | _, err := p.Parse("") 610 | if err == nil { 611 | t.Fatalf("expecting non-nil error when parsing empty json") 612 | } 613 | _, err = p.Parse("\n\t \n") 614 | if err == nil { 615 | t.Fatalf("expecting non-nil error when parsing empty json") 616 | } 617 | }) 618 | 619 | t.Run("invalid-tail", func(t *testing.T) { 620 | _, err := p.Parse("123 456") 621 | if err == nil { 622 | t.Fatalf("expecting non-nil error when parsing invalid tail") 623 | } 624 | _, err = p.Parse("[] 1223") 625 | if err == nil { 626 | t.Fatalf("expecting non-nil error when parsing invalid tail") 627 | } 628 | }) 629 | 630 | t.Run("invalid-json", func(t *testing.T) { 631 | f := func(s string) { 632 | t.Helper() 633 | if _, err := p.Parse(s); err == nil { 634 | t.Fatalf("expecting non-nil error when parsing invalid json %q", s) 635 | } 636 | } 637 | 638 | f("free") 639 | f("tree") 640 | f("\x00\x10123") 641 | f("1 \n\x01") 642 | f("{\x00}") 643 | f("[\x00]") 644 | f("\"foo\"\x00") 645 | f("{\"foo\"\x00:123}") 646 | f("nil") 647 | f("[foo]") 648 | f("{foo}") 649 | f("[123 34]") 650 | f(`{"foo" "bar"}`) 651 | f(`{"foo":123 "bar":"baz"}`) 652 | f("-2134.453eec+43") 653 | 654 | if _, err := p.Parse("-2134.453E+43"); err != nil { 655 | t.Fatalf("unexpected error when parsing number: %s", err) 656 | } 657 | 658 | // Incomplete object key key. 659 | f(`{"foo: 123}`) 660 | 661 | // Incomplete string. 662 | f(`"{\"foo\": 123}`) 663 | 664 | v, err := p.Parse(`"{\"foo\": 123}"`) 665 | if err != nil { 666 | t.Fatalf("unexpected error when parsing json string: %s", err) 667 | } 668 | sb := v.GetStringBytes() 669 | if string(sb) != `{"foo": 123}` { 670 | t.Fatalf("unexpected string value; got %q; want %q", sb, `{"foo": 123}`) 671 | } 672 | }) 673 | 674 | t.Run("incomplete-object", func(t *testing.T) { 675 | f := func(s string) { 676 | t.Helper() 677 | if _, err := p.Parse(s); err == nil { 678 | t.Fatalf("expecting non-nil error when parsing incomplete object %q", s) 679 | } 680 | } 681 | 682 | f(" { ") 683 | f(`{"foo"`) 684 | f(`{"foo":`) 685 | f(`{"foo":null`) 686 | f(`{"foo":null,`) 687 | f(`{"foo":null,}`) 688 | f(`{"foo":null,"bar"}`) 689 | 690 | if _, err := p.Parse(`{"foo":null,"bar":"baz"}`); err != nil { 691 | t.Fatalf("unexpected error when parsing object: %s", err) 692 | } 693 | }) 694 | 695 | t.Run("incomplete-array", func(t *testing.T) { 696 | f := func(s string) { 697 | t.Helper() 698 | if _, err := p.Parse(s); err == nil { 699 | t.Fatalf("expecting non-nil error when parsing incomplete array %q", s) 700 | } 701 | } 702 | 703 | f(" [ ") 704 | f("[123") 705 | f("[123,") 706 | f("[123,]") 707 | f("[123,{}") 708 | f("[123,{},]") 709 | 710 | if _, err := p.Parse("[123,{},[]]"); err != nil { 711 | t.Fatalf("unexpected error when parsing array: %s", err) 712 | } 713 | }) 714 | 715 | t.Run("incomplete-string", func(t *testing.T) { 716 | f := func(s string) { 717 | t.Helper() 718 | if _, err := p.Parse(s); err == nil { 719 | t.Fatalf("expecting non-nil error when parsing incomplete string %q", s) 720 | } 721 | } 722 | 723 | f(` "foo`) 724 | f(`"foo\`) 725 | f(`"foo\"`) 726 | f(`"foo\\\"`) 727 | f(`"foo'`) 728 | f(`"foo'bar'`) 729 | 730 | if _, err := p.Parse(`"foo\\\""`); err != nil { 731 | t.Fatalf("unexpected error when parsing string: %s", err) 732 | } 733 | }) 734 | 735 | t.Run("empty-object", func(t *testing.T) { 736 | v, err := p.Parse("{}") 737 | if err != nil { 738 | t.Fatalf("cannot parse empty object: %s", err) 739 | } 740 | tp := v.Type() 741 | if tp != TypeObject || tp.String() != "object" { 742 | t.Fatalf("unexpected value obtained for empty object: %#v", v) 743 | } 744 | o, err := v.Object() 745 | if err != nil { 746 | t.Fatalf("cannot obtain object: %s", err) 747 | } 748 | n := o.Len() 749 | if n != 0 { 750 | t.Fatalf("unexpected number of items in empty object: %d; want 0", n) 751 | } 752 | s := v.String() 753 | if s != "{}" { 754 | t.Fatalf("unexpected string representation of empty object: got %q; want %q", s, "{}") 755 | } 756 | }) 757 | 758 | t.Run("empty-array", func(t *testing.T) { 759 | v, err := p.Parse("[]") 760 | if err != nil { 761 | t.Fatalf("cannot parse empty array: %s", err) 762 | } 763 | tp := v.Type() 764 | if tp != TypeArray || tp.String() != "array" { 765 | t.Fatalf("unexpected value obtained for empty array: %#v", v) 766 | } 767 | a, err := v.Array() 768 | if err != nil { 769 | t.Fatalf("unexpected error: %s", err) 770 | } 771 | n := len(a) 772 | if n != 0 { 773 | t.Fatalf("unexpected number of items in empty array: %d; want 0", n) 774 | } 775 | s := v.String() 776 | if s != "[]" { 777 | t.Fatalf("unexpected string representation of empty array: got %q; want %q", s, "[]") 778 | } 779 | }) 780 | 781 | t.Run("null", func(t *testing.T) { 782 | v, err := p.Parse("null") 783 | if err != nil { 784 | t.Fatalf("cannot parse null: %s", err) 785 | } 786 | tp := v.Type() 787 | if tp != TypeNull || tp.String() != "null" { 788 | t.Fatalf("unexpected value obtained for null: %#v", v) 789 | } 790 | s := v.String() 791 | if s != "null" { 792 | t.Fatalf("unexpected string representation of null; got %q; want %q", s, "null") 793 | } 794 | }) 795 | 796 | t.Run("true", func(t *testing.T) { 797 | v, err := p.Parse("true") 798 | if err != nil { 799 | t.Fatalf("cannot parse true: %s", err) 800 | } 801 | tp := v.Type() 802 | if tp != TypeTrue || tp.String() != "true" { 803 | t.Fatalf("unexpected value obtained for true: %#v", v) 804 | } 805 | b, err := v.Bool() 806 | if err != nil { 807 | t.Fatalf("unexpected error: %s", err) 808 | } 809 | if !b { 810 | t.Fatalf("expecting true; got false") 811 | } 812 | s := v.String() 813 | if s != "true" { 814 | t.Fatalf("unexpected string representation of true; got %q; want %q", s, "true") 815 | } 816 | }) 817 | 818 | t.Run("false", func(t *testing.T) { 819 | v, err := p.Parse("false") 820 | if err != nil { 821 | t.Fatalf("cannot parse false: %s", err) 822 | } 823 | tp := v.Type() 824 | if tp != TypeFalse || tp.String() != "false" { 825 | t.Fatalf("unexpected value obtained for false: %#v", v) 826 | } 827 | b, err := v.Bool() 828 | if err != nil { 829 | t.Fatalf("unexpected error: %s", err) 830 | } 831 | if b { 832 | t.Fatalf("expecting false; got true") 833 | } 834 | s := v.String() 835 | if s != "false" { 836 | t.Fatalf("unexpected string representation of false; got %q; want %q", s, "false") 837 | } 838 | }) 839 | 840 | t.Run("integer", func(t *testing.T) { 841 | v, err := p.Parse("12345") 842 | if err != nil { 843 | t.Fatalf("cannot parse integer: %s", err) 844 | } 845 | tp := v.Type() 846 | if tp != TypeNumber || tp.String() != "number" { 847 | t.Fatalf("unexpected type obtained for integer: %#v", v) 848 | } 849 | n, err := v.Int() 850 | if err != nil { 851 | t.Fatalf("cannot obtain int: %s", err) 852 | } 853 | if n != 12345 { 854 | t.Fatalf("unexpected value obtained for integer; got %d; want %d", n, 12345) 855 | } 856 | s := v.String() 857 | if s != "12345" { 858 | t.Fatalf("unexpected string representation of integer; got %q; want %q", s, "12345") 859 | } 860 | }) 861 | 862 | t.Run("int64", func(t *testing.T) { 863 | v, err := p.Parse("-8838840643388017390") 864 | if err != nil { 865 | t.Fatalf("cannot parse int64: %s", err) 866 | } 867 | tp := v.Type() 868 | if tp != TypeNumber || tp.String() != "number" { 869 | t.Fatalf("unexpected type obtained for int64: %#v", v) 870 | } 871 | n, err := v.Int64() 872 | if err != nil { 873 | t.Fatalf("cannot obtain int64: %s", err) 874 | } 875 | if n != int64(-8838840643388017390) { 876 | t.Fatalf("unexpected value obtained for int64; got %d; want %d", n, int64(-8838840643388017390)) 877 | } 878 | s := v.String() 879 | if s != "-8838840643388017390" { 880 | t.Fatalf("unexpected string representation of int64; got %q; want %q", s, "-8838840643388017390") 881 | } 882 | }) 883 | 884 | t.Run("uint", func(t *testing.T) { 885 | v, err := p.Parse("18446744073709551615") 886 | if err != nil { 887 | t.Fatalf("cannot parse uint: %s", err) 888 | } 889 | tp := v.Type() 890 | if tp != TypeNumber || tp.String() != "number" { 891 | t.Fatalf("unexpected type obtained for uint: %#v", v) 892 | } 893 | n, err := v.Uint64() 894 | if err != nil { 895 | t.Fatalf("cannot obtain uint64: %s", err) 896 | } 897 | if n != uint64(18446744073709551615) { 898 | t.Fatalf("unexpected value obtained for uint; got %d; want %d", n, uint64(18446744073709551615)) 899 | } 900 | s := v.String() 901 | if s != "18446744073709551615" { 902 | t.Fatalf("unexpected string representation of uint; got %q; want %q", s, "18446744073709551615") 903 | } 904 | }) 905 | 906 | t.Run("uint64", func(t *testing.T) { 907 | v, err := p.Parse("18446744073709551615") 908 | if err != nil { 909 | t.Fatalf("cannot parse uint64: %s", err) 910 | } 911 | tp := v.Type() 912 | if tp != TypeNumber || tp.String() != "number" { 913 | t.Fatalf("unexpected type obtained for uint64: %#v", v) 914 | } 915 | n, err := v.Uint64() 916 | if err != nil { 917 | t.Fatalf("cannot obtain uint64: %s", err) 918 | } 919 | if n != 18446744073709551615 { 920 | t.Fatalf("unexpected value obtained for uint64; got %d; want %d", n, uint64(18446744073709551615)) 921 | } 922 | s := v.String() 923 | if s != "18446744073709551615" { 924 | t.Fatalf("unexpected string representation of uint64; got %q; want %q", s, "18446744073709551615") 925 | } 926 | }) 927 | 928 | t.Run("float", func(t *testing.T) { 929 | v, err := p.Parse("-12.345") 930 | if err != nil { 931 | t.Fatalf("cannot parse integer: %s", err) 932 | } 933 | n, err := v.Float64() 934 | if err != nil { 935 | t.Fatalf("unexpected error: %s", err) 936 | } 937 | tp := v.Type() 938 | if tp != TypeNumber || tp.String() != "number" { 939 | t.Fatalf("unexpected type obtained for integer: %#v", v) 940 | } 941 | if n != -12.345 { 942 | t.Fatalf("unexpected value obtained for integer; got %f; want %f", n, -12.345) 943 | } 944 | s := v.String() 945 | if s != "-12.345" { 946 | t.Fatalf("unexpected string representation of integer; got %q; want %q", s, "-12.345") 947 | } 948 | }) 949 | 950 | t.Run("string", func(t *testing.T) { 951 | v, err := p.Parse(`"foo bar"`) 952 | if err != nil { 953 | t.Fatalf("cannot parse string: %s", err) 954 | } 955 | tp := v.Type() 956 | if tp != TypeString || tp.String() != "string" { 957 | t.Fatalf("unexpected type obtained for string: %#v", v) 958 | } 959 | sb, err := v.StringBytes() 960 | if err != nil { 961 | t.Fatalf("cannot obtain string: %s", err) 962 | } 963 | if string(sb) != "foo bar" { 964 | t.Fatalf("unexpected value obtained for string; got %q; want %q", sb, "foo bar") 965 | } 966 | ss := v.String() 967 | if ss != `"foo bar"` { 968 | t.Fatalf("unexpected string representation of string; got %q; want %q", ss, `"foo bar"`) 969 | } 970 | }) 971 | 972 | t.Run("string-escaped", func(t *testing.T) { 973 | v, err := p.Parse(`"\n\t\\foo\"bar\u3423x\/\b\f\r\\"`) 974 | if err != nil { 975 | t.Fatalf("cannot parse string: %s", err) 976 | } 977 | tp := v.Type() 978 | if tp != TypeString { 979 | t.Fatalf("unexpected type obtained for string: %#v", v) 980 | } 981 | sb, err := v.StringBytes() 982 | if err != nil { 983 | t.Fatalf("cannot obtain string: %s", err) 984 | } 985 | if string(sb) != "\n\t\\foo\"bar\u3423x/\b\f\r\\" { 986 | t.Fatalf("unexpected value obtained for string; got %q; want %q", sb, "\n\t\\foo\"bar\u3423x/\b\f\r\\") 987 | } 988 | ss := v.String() 989 | if ss != `"\n\t\\foo\"bar㐣x/\b\f\r\\"` { 990 | t.Fatalf("unexpected string representation of string; got %q; want %q", ss, `"\n\t\\foo\"bar㐣x/\b\f\r\\"`) 991 | } 992 | }) 993 | 994 | t.Run("object-one-element", func(t *testing.T) { 995 | v, err := p.Parse(` { 996 | "foo" : "bar" } `) 997 | if err != nil { 998 | t.Fatalf("cannot parse object: %s", err) 999 | } 1000 | tp := v.Type() 1001 | if tp != TypeObject { 1002 | t.Fatalf("unexpected type obtained for object: %#v", v) 1003 | } 1004 | o, err := v.Object() 1005 | if err != nil { 1006 | t.Fatalf("cannot obtain object: %s", err) 1007 | } 1008 | vv := o.Get("foo") 1009 | if vv.Type() != TypeString { 1010 | t.Fatalf("unexpected type for foo item: got %d; want %d", vv.Type(), TypeString) 1011 | } 1012 | vv = o.Get("non-existing key") 1013 | if vv != nil { 1014 | t.Fatalf("unexpected value obtained for non-existing key: %#v", vv) 1015 | } 1016 | 1017 | s := v.String() 1018 | if s != `{"foo":"bar"}` { 1019 | t.Fatalf("unexpected string representation for object; got %q; want %q", s, `{"foo":"bar"}`) 1020 | } 1021 | }) 1022 | 1023 | t.Run("object-multi-elements", func(t *testing.T) { 1024 | v, err := p.Parse(`{"foo": [1,2,3 ] ,"bar":{},"baz":123.456}`) 1025 | if err != nil { 1026 | t.Fatalf("cannot parse object: %s", err) 1027 | } 1028 | tp := v.Type() 1029 | if tp != TypeObject { 1030 | t.Fatalf("unexpected type obtained for object: %#v", v) 1031 | } 1032 | o, err := v.Object() 1033 | if err != nil { 1034 | t.Fatalf("cannot obtain object: %s", err) 1035 | } 1036 | vv := o.Get("foo") 1037 | if vv.Type() != TypeArray { 1038 | t.Fatalf("unexpected type for foo item; got %d; want %d", vv.Type(), TypeArray) 1039 | } 1040 | vv = o.Get("bar") 1041 | if vv.Type() != TypeObject { 1042 | t.Fatalf("unexpected type for bar item; got %d; want %d", vv.Type(), TypeObject) 1043 | } 1044 | vv = o.Get("baz") 1045 | if vv.Type() != TypeNumber { 1046 | t.Fatalf("unexpected type for baz item; got %d; want %d", vv.Type(), TypeNumber) 1047 | } 1048 | vv = o.Get("non-existing-key") 1049 | if vv != nil { 1050 | t.Fatalf("unexpected value obtained for non-existing key: %#v", vv) 1051 | } 1052 | 1053 | s := v.String() 1054 | if s != `{"foo":[1,2,3],"bar":{},"baz":123.456}` { 1055 | t.Fatalf("unexpected string representation for object; got %q; want %q", s, `{"foo":[1,2,3],"bar":{},"baz":123.456}`) 1056 | } 1057 | }) 1058 | 1059 | t.Run("array-one-element", func(t *testing.T) { 1060 | v, err := p.Parse(` [{"bar":[ [],[[]] ]} ] `) 1061 | if err != nil { 1062 | t.Fatalf("cannot parse array: %s", err) 1063 | } 1064 | tp := v.Type() 1065 | if tp != TypeArray { 1066 | t.Fatalf("unexpected type obtained for array: %#v", v) 1067 | } 1068 | a, err := v.Array() 1069 | if err != nil { 1070 | t.Fatalf("unexpected error: %s", err) 1071 | } 1072 | if len(a) != 1 { 1073 | t.Fatalf("unexpected array len; got %d; want %d", len(a), 1) 1074 | } 1075 | if a[0].Type() != TypeObject { 1076 | t.Fatalf("unexpected type for a[0]; got %d; want %d", a[0].Type(), TypeObject) 1077 | } 1078 | 1079 | s := v.String() 1080 | if s != `[{"bar":[[],[[]]]}]` { 1081 | t.Fatalf("unexpected string representation for array; got %q; want %q", s, `[{"bar":[[],[[]]]}]`) 1082 | } 1083 | }) 1084 | 1085 | t.Run("array-multi-elements", func(t *testing.T) { 1086 | v, err := p.Parse(` [1,"foo",{"bar":[ ],"baz":""} ,[ "x" , "y" ] ] `) 1087 | if err != nil { 1088 | t.Fatalf("cannot parse array: %s", err) 1089 | } 1090 | tp := v.Type() 1091 | if tp != TypeArray { 1092 | t.Fatalf("unexpected type obtained for array: %#v", v) 1093 | } 1094 | a, err := v.Array() 1095 | if err != nil { 1096 | t.Fatalf("unexpected error: %s", err) 1097 | } 1098 | if len(a) != 4 { 1099 | t.Fatalf("unexpected array len; got %d; want %d", len(a), 4) 1100 | } 1101 | if a[0].Type() != TypeNumber { 1102 | t.Fatalf("unexpected type for a[0]; got %d; want %d", a[0].Type(), TypeNumber) 1103 | } 1104 | if a[1].Type() != TypeString { 1105 | t.Fatalf("unexpected type for a[1]; got %d; want %d", a[1].Type(), TypeString) 1106 | } 1107 | if a[2].Type() != TypeObject { 1108 | t.Fatalf("unexpected type for a[2]; got %d; want %d", a[2].Type(), TypeObject) 1109 | } 1110 | if a[3].Type() != TypeArray { 1111 | t.Fatalf("unexpected type for a[3]; got %d; want %d", a[3].Type(), TypeArray) 1112 | } 1113 | 1114 | s := v.String() 1115 | if s != `[1,"foo",{"bar":[],"baz":""},["x","y"]]` { 1116 | t.Fatalf("unexpected string representation for array; got %q; want %q", s, `[1,"foo",{"bar":[],"baz":""},["x","y"]]`) 1117 | } 1118 | }) 1119 | 1120 | t.Run("complex-object", func(t *testing.T) { 1121 | s := `{"foo":[-1.345678,[[[[[]]]],{}],"bar"],"baz":{"bbb":123}}` 1122 | v, err := p.Parse(s) 1123 | if err != nil { 1124 | t.Fatalf("cannot parse complex object: %s", err) 1125 | } 1126 | if v.Type() != TypeObject { 1127 | t.Fatalf("unexpected type obtained for object: %#v", v) 1128 | } 1129 | 1130 | ss := v.String() 1131 | if ss != s { 1132 | t.Fatalf("unexpected string representation for object; got %q; want %q", ss, s) 1133 | } 1134 | 1135 | s = strings.TrimSpace(largeFixture) 1136 | v, err = p.Parse(s) 1137 | if err != nil { 1138 | t.Fatalf("cannot parse largeFixture: %s", err) 1139 | } 1140 | ss = v.String() 1141 | if ss != s { 1142 | t.Fatalf("unexpected string representation for object; got\n%q; want\n%q", ss, s) 1143 | } 1144 | }) 1145 | 1146 | t.Run("complex-object-visit-all", func(t *testing.T) { 1147 | n := 0 1148 | var f func(k []byte, v *Value) 1149 | f = func(k []byte, v *Value) { 1150 | switch v.Type() { 1151 | case TypeObject: 1152 | o, err := v.Object() 1153 | if err != nil { 1154 | t.Fatalf("cannot obtain object: %s", err) 1155 | } 1156 | o.Visit(f) 1157 | case TypeArray: 1158 | a, err := v.Array() 1159 | if err != nil { 1160 | t.Fatalf("unexpected error: %s", err) 1161 | } 1162 | for _, vv := range a { 1163 | f(nil, vv) 1164 | } 1165 | case TypeString: 1166 | sb, err := v.StringBytes() 1167 | if err != nil { 1168 | t.Fatalf("cannot obtain string: %s", err) 1169 | } 1170 | n += len(sb) 1171 | case TypeNumber: 1172 | nn, err := v.Int() 1173 | if err != nil { 1174 | t.Fatalf("cannot obtain int: %s", err) 1175 | } 1176 | n += nn 1177 | } 1178 | } 1179 | 1180 | s := strings.TrimSpace(largeFixture) 1181 | v, err := p.Parse(s) 1182 | if err != nil { 1183 | t.Fatalf("cannot parse largeFixture: %s", err) 1184 | } 1185 | o, err := v.Object() 1186 | if err != nil { 1187 | t.Fatalf("cannot obtain object: %s", err) 1188 | } 1189 | o.Visit(f) 1190 | 1191 | if n != 21473 { 1192 | t.Fatalf("unexpected n; got %d; want %d", n, 21473) 1193 | } 1194 | 1195 | // Make sure the json remains valid after visiting all the items. 1196 | ss := v.String() 1197 | if ss != s { 1198 | t.Fatalf("unexpected string representation for object; got\n%q; want\n%q", ss, s) 1199 | } 1200 | 1201 | }) 1202 | } 1203 | 1204 | func TestParseBigObject(t *testing.T) { 1205 | const itemsCount = 10000 1206 | 1207 | // build big json object 1208 | var ss []string 1209 | for i := 0; i < itemsCount; i++ { 1210 | s := fmt.Sprintf(`"key_%d": "value_%d"`, i, i) 1211 | ss = append(ss, s) 1212 | } 1213 | s := "{" + strings.Join(ss, ",") + "}" 1214 | 1215 | // parse it 1216 | var p Parser 1217 | v, err := p.Parse(s) 1218 | if err != nil { 1219 | t.Fatalf("unexpected error: %s", err) 1220 | } 1221 | 1222 | // Look up object items 1223 | for i := 0; i < itemsCount; i++ { 1224 | k := fmt.Sprintf("key_%d", i) 1225 | expectedV := fmt.Sprintf("value_%d", i) 1226 | sb := v.GetStringBytes(k) 1227 | if string(sb) != expectedV { 1228 | t.Fatalf("unexpected value obtained; got %q; want %q", sb, expectedV) 1229 | } 1230 | } 1231 | 1232 | // verify non-existing key returns nil 1233 | sb := v.GetStringBytes("non-existing-key") 1234 | if sb != nil { 1235 | t.Fatalf("unexpected non-nil value for non-existing-key: %q", sb) 1236 | } 1237 | } 1238 | 1239 | func TestParseGetConcurrent(t *testing.T) { 1240 | concurrency := 10 1241 | ch := make(chan error, concurrency) 1242 | s := `{"foo": "bar", "empty_obj": {}}` 1243 | for i := 0; i < concurrency; i++ { 1244 | go func() { 1245 | ch <- testParseGetSerial(s) 1246 | }() 1247 | } 1248 | for i := 0; i < concurrency; i++ { 1249 | select { 1250 | case err := <-ch: 1251 | if err != nil { 1252 | t.Fatalf("unexpected error during concurrent test: %s", err) 1253 | } 1254 | case <-time.After(time.Second): 1255 | t.Fatalf("timeout") 1256 | } 1257 | } 1258 | } 1259 | 1260 | func testParseGetSerial(s string) error { 1261 | var p Parser 1262 | for i := 0; i < 100; i++ { 1263 | v, err := p.Parse(s) 1264 | if err != nil { 1265 | return fmt.Errorf("cannot parse %q: %s", s, err) 1266 | } 1267 | sb := v.GetStringBytes("foo") 1268 | if string(sb) != "bar" { 1269 | return fmt.Errorf("unexpected value for key=%q; got %q; want %q", "foo", sb, "bar") 1270 | } 1271 | vv := v.Get("empty_obj", "non-existing-key") 1272 | if vv != nil { 1273 | return fmt.Errorf("unexpected non-nil value got: %s", vv) 1274 | } 1275 | } 1276 | return nil 1277 | } 1278 | -------------------------------------------------------------------------------- /parser_timing_test.go: -------------------------------------------------------------------------------- 1 | package fastjson 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "strings" 8 | "testing" 9 | ) 10 | 11 | func BenchmarkParseRawString(b *testing.B) { 12 | for _, s := range []string{`""`, `"a"`, `"abcd"`, `"abcdefghijk"`, `"qwertyuiopasdfghjklzxcvb"`} { 13 | b.Run(s, func(b *testing.B) { 14 | benchmarkParseRawString(b, s) 15 | }) 16 | } 17 | } 18 | 19 | func benchmarkParseRawString(b *testing.B, s string) { 20 | b.ReportAllocs() 21 | b.SetBytes(int64(len(s))) 22 | s = s[1:] // skip the opening '"' 23 | b.RunParallel(func(pb *testing.PB) { 24 | for pb.Next() { 25 | rs, tail, err := parseRawString(s) 26 | if err != nil { 27 | panic(fmt.Errorf("cannot parse %q: %s", s, err)) 28 | } 29 | if rs != s[:len(s)-1] { 30 | panic(fmt.Errorf("invalid string obtained; got %q; want %q", rs, s[:len(s)-1])) 31 | } 32 | if len(tail) > 0 { 33 | panic(fmt.Errorf("non-empty tail got: %q", tail)) 34 | } 35 | } 36 | }) 37 | } 38 | 39 | func BenchmarkParseRawNumber(b *testing.B) { 40 | for _, s := range []string{"1", "1234", "123456", "-1234", "1234567890.1234567", "-1.32434e+12"} { 41 | b.Run(s, func(b *testing.B) { 42 | benchmarkParseRawNumber(b, s) 43 | }) 44 | } 45 | } 46 | 47 | func benchmarkParseRawNumber(b *testing.B, s string) { 48 | b.ReportAllocs() 49 | b.SetBytes(int64(len(s))) 50 | b.RunParallel(func(pb *testing.PB) { 51 | for pb.Next() { 52 | rn, tail, err := parseRawNumber(s) 53 | if err != nil { 54 | panic(fmt.Errorf("cannot parse %q: %s", s, err)) 55 | } 56 | if rn != s { 57 | panic(fmt.Errorf("invalid number obtained; got %q; want %q", rn, s)) 58 | } 59 | if len(tail) > 0 { 60 | panic(fmt.Errorf("non-empty tail got: %q", tail)) 61 | } 62 | } 63 | }) 64 | } 65 | 66 | func BenchmarkObjectGet(b *testing.B) { 67 | for _, itemsCount := range []int{10, 100, 1000, 10000, 100000} { 68 | b.Run(fmt.Sprintf("items_%d", itemsCount), func(b *testing.B) { 69 | for _, lookupsCount := range []int{0, 1, 2, 4, 8, 16, 32, 64} { 70 | b.Run(fmt.Sprintf("lookups_%d", lookupsCount), func(b *testing.B) { 71 | benchmarkObjectGet(b, itemsCount, lookupsCount) 72 | }) 73 | } 74 | }) 75 | } 76 | } 77 | 78 | func benchmarkObjectGet(b *testing.B, itemsCount, lookupsCount int) { 79 | b.StopTimer() 80 | var ss []string 81 | for i := 0; i < itemsCount; i++ { 82 | s := fmt.Sprintf(`"key_%d": "value_%d"`, i, i) 83 | ss = append(ss, s) 84 | } 85 | s := "{" + strings.Join(ss, ",") + "}" 86 | key := fmt.Sprintf("key_%d", len(ss)/2) 87 | expectedValue := fmt.Sprintf("value_%d", len(ss)/2) 88 | b.StartTimer() 89 | b.ReportAllocs() 90 | b.SetBytes(int64(len(s))) 91 | 92 | b.RunParallel(func(pb *testing.PB) { 93 | p := benchPool.Get() 94 | for pb.Next() { 95 | v, err := p.Parse(s) 96 | if err != nil { 97 | panic(fmt.Errorf("unexpected error: %s", err)) 98 | } 99 | o := v.GetObject() 100 | for i := 0; i < lookupsCount; i++ { 101 | sb := o.Get(key).GetStringBytes() 102 | if string(sb) != expectedValue { 103 | panic(fmt.Errorf("unexpected value; got %q; want %q", sb, expectedValue)) 104 | } 105 | } 106 | } 107 | benchPool.Put(p) 108 | }) 109 | } 110 | 111 | func BenchmarkMarshalTo(b *testing.B) { 112 | b.Run("small", func(b *testing.B) { 113 | benchmarkMarshalTo(b, smallFixture) 114 | }) 115 | b.Run("medium", func(b *testing.B) { 116 | benchmarkMarshalTo(b, mediumFixture) 117 | }) 118 | b.Run("large", func(b *testing.B) { 119 | benchmarkMarshalTo(b, largeFixture) 120 | }) 121 | b.Run("canada", func(b *testing.B) { 122 | benchmarkMarshalTo(b, canadaFixture) 123 | }) 124 | b.Run("citm", func(b *testing.B) { 125 | benchmarkMarshalTo(b, citmFixture) 126 | }) 127 | b.Run("twitter", func(b *testing.B) { 128 | benchmarkMarshalTo(b, twitterFixture) 129 | }) 130 | } 131 | 132 | func benchmarkMarshalTo(b *testing.B, s string) { 133 | p := benchPool.Get() 134 | v, err := p.Parse(s) 135 | if err != nil { 136 | panic(fmt.Errorf("unexpected error: %s", err)) 137 | } 138 | 139 | b.ReportAllocs() 140 | b.SetBytes(int64(len(s))) 141 | b.RunParallel(func(pb *testing.PB) { 142 | var b []byte 143 | for pb.Next() { 144 | // It is ok calling v.MarshalTo from concurrent 145 | // goroutines, since MarshalTo doesn't modify v. 146 | b = v.MarshalTo(b[:0]) 147 | } 148 | }) 149 | benchPool.Put(p) 150 | } 151 | 152 | func BenchmarkParse(b *testing.B) { 153 | b.Run("small", func(b *testing.B) { 154 | benchmarkParse(b, smallFixture) 155 | }) 156 | b.Run("medium", func(b *testing.B) { 157 | benchmarkParse(b, mediumFixture) 158 | }) 159 | b.Run("large", func(b *testing.B) { 160 | benchmarkParse(b, largeFixture) 161 | }) 162 | b.Run("canada", func(b *testing.B) { 163 | benchmarkParse(b, canadaFixture) 164 | }) 165 | b.Run("citm", func(b *testing.B) { 166 | benchmarkParse(b, citmFixture) 167 | }) 168 | b.Run("twitter", func(b *testing.B) { 169 | benchmarkParse(b, twitterFixture) 170 | }) 171 | } 172 | 173 | var ( 174 | // small, medium and large fixtures are from https://github.com/buger/jsonparser/blob/f04e003e4115787c6272636780bc206e5ffad6c4/benchmark/benchmark.go 175 | smallFixture = getFromFile("testdata/small.json") 176 | mediumFixture = getFromFile("testdata/medium.json") 177 | largeFixture = getFromFile("testdata/large.json") 178 | 179 | // canada, citm and twitter fixtures are from https://github.com/serde-rs/json-benchmark/tree/0db02e043b3ae87dc5065e7acb8654c1f7670c43/data 180 | canadaFixture = getFromFile("testdata/canada.json") 181 | citmFixture = getFromFile("testdata/citm_catalog.json") 182 | twitterFixture = getFromFile("testdata/twitter.json") 183 | ) 184 | 185 | func getFromFile(filename string) string { 186 | data, err := ioutil.ReadFile(filename) 187 | if err != nil { 188 | panic(fmt.Errorf("cannot read %s: %s", filename, err)) 189 | } 190 | return string(data) 191 | } 192 | 193 | func benchmarkParse(b *testing.B, s string) { 194 | b.Run("stdjson-map", func(b *testing.B) { 195 | benchmarkStdJSONParseMap(b, s) 196 | }) 197 | b.Run("stdjson-struct", func(b *testing.B) { 198 | benchmarkStdJSONParseStruct(b, s) 199 | }) 200 | b.Run("stdjson-empty-struct", func(b *testing.B) { 201 | benchmarkStdJSONParseEmptyStruct(b, s) 202 | }) 203 | b.Run("fastjson", func(b *testing.B) { 204 | benchmarkFastJSONParse(b, s) 205 | }) 206 | b.Run("fastjson-get", func(b *testing.B) { 207 | benchmarkFastJSONParseGet(b, s) 208 | }) 209 | } 210 | 211 | func benchmarkFastJSONParse(b *testing.B, s string) { 212 | b.ReportAllocs() 213 | b.SetBytes(int64(len(s))) 214 | b.RunParallel(func(pb *testing.PB) { 215 | p := benchPool.Get() 216 | for pb.Next() { 217 | v, err := p.Parse(s) 218 | if err != nil { 219 | panic(fmt.Errorf("unexpected error: %s", err)) 220 | } 221 | if v.Type() != TypeObject { 222 | panic(fmt.Errorf("unexpected value type; got %s; want %s", v.Type(), TypeObject)) 223 | } 224 | } 225 | benchPool.Put(p) 226 | }) 227 | } 228 | 229 | func benchmarkFastJSONParseGet(b *testing.B, s string) { 230 | b.ReportAllocs() 231 | b.SetBytes(int64(len(s))) 232 | b.RunParallel(func(pb *testing.PB) { 233 | p := benchPool.Get() 234 | var n int 235 | for pb.Next() { 236 | v, err := p.Parse(s) 237 | if err != nil { 238 | panic(fmt.Errorf("unexpected error: %s", err)) 239 | } 240 | n += v.GetInt("sid") 241 | n += len(v.GetStringBytes("uuid")) 242 | p := v.Get("person") 243 | if p != nil { 244 | n++ 245 | } 246 | c := v.Get("company") 247 | if c != nil { 248 | n++ 249 | } 250 | u := v.Get("users") 251 | if u != nil { 252 | n++ 253 | } 254 | a := v.GetArray("features") 255 | n += len(a) 256 | a = v.GetArray("topicSubTopics") 257 | n += len(a) 258 | o := v.Get("search_metadata") 259 | if o != nil { 260 | n++ 261 | } 262 | } 263 | benchPool.Put(p) 264 | }) 265 | } 266 | 267 | var benchPool ParserPool 268 | 269 | func benchmarkStdJSONParseMap(b *testing.B, s string) { 270 | b.ReportAllocs() 271 | b.SetBytes(int64(len(s))) 272 | bb := s2b(s) 273 | b.RunParallel(func(pb *testing.PB) { 274 | var m map[string]interface{} 275 | for pb.Next() { 276 | if err := json.Unmarshal(bb, &m); err != nil { 277 | panic(fmt.Errorf("unexpected error: %s", err)) 278 | } 279 | } 280 | }) 281 | } 282 | 283 | func benchmarkStdJSONParseStruct(b *testing.B, s string) { 284 | b.ReportAllocs() 285 | b.SetBytes(int64(len(s))) 286 | bb := s2b(s) 287 | b.RunParallel(func(pb *testing.PB) { 288 | var m struct { 289 | Sid int 290 | UUID string 291 | Person map[string]interface{} 292 | Company map[string]interface{} 293 | Users []interface{} 294 | Features []map[string]interface{} 295 | TopicSubTopics map[string]interface{} 296 | SearchMetadata map[string]interface{} 297 | } 298 | for pb.Next() { 299 | if err := json.Unmarshal(bb, &m); err != nil { 300 | panic(fmt.Errorf("unexpected error: %s", err)) 301 | } 302 | } 303 | }) 304 | } 305 | 306 | func benchmarkStdJSONParseEmptyStruct(b *testing.B, s string) { 307 | b.ReportAllocs() 308 | b.SetBytes(int64(len(s))) 309 | bb := s2b(s) 310 | b.RunParallel(func(pb *testing.PB) { 311 | var m struct{} 312 | for pb.Next() { 313 | if err := json.Unmarshal(bb, &m); err != nil { 314 | panic(fmt.Errorf("unexpected error: %s", err)) 315 | } 316 | } 317 | }) 318 | } 319 | -------------------------------------------------------------------------------- /pool.go: -------------------------------------------------------------------------------- 1 | package fastjson 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | // ParserPool may be used for pooling Parsers for similarly typed JSONs. 8 | type ParserPool struct { 9 | pool sync.Pool 10 | } 11 | 12 | // Get returns a Parser from pp. 13 | // 14 | // The Parser must be Put to pp after use. 15 | func (pp *ParserPool) Get() *Parser { 16 | v := pp.pool.Get() 17 | if v == nil { 18 | return &Parser{} 19 | } 20 | return v.(*Parser) 21 | } 22 | 23 | // Put returns p to pp. 24 | // 25 | // p and objects recursively returned from p cannot be used after p 26 | // is put into pp. 27 | func (pp *ParserPool) Put(p *Parser) { 28 | pp.pool.Put(p) 29 | } 30 | 31 | // ArenaPool may be used for pooling Arenas for similarly typed JSONs. 32 | type ArenaPool struct { 33 | pool sync.Pool 34 | } 35 | 36 | // Get returns an Arena from ap. 37 | // 38 | // The Arena must be Put to ap after use. 39 | func (ap *ArenaPool) Get() *Arena { 40 | v := ap.pool.Get() 41 | if v == nil { 42 | return &Arena{} 43 | } 44 | return v.(*Arena) 45 | } 46 | 47 | // Put returns a to ap. 48 | // 49 | // a and objects created by a cannot be used after a is put into ap. 50 | func (ap *ArenaPool) Put(a *Arena) { 51 | ap.pool.Put(a) 52 | } 53 | -------------------------------------------------------------------------------- /scanner.go: -------------------------------------------------------------------------------- 1 | package fastjson 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | // Scanner scans a series of JSON values. Values may be delimited by whitespace. 8 | // 9 | // Scanner may parse JSON lines ( http://jsonlines.org/ ). 10 | // 11 | // Scanner may be re-used for subsequent parsing. 12 | // 13 | // Scanner cannot be used from concurrent goroutines. 14 | // 15 | // Use Parser for parsing only a single JSON value. 16 | type Scanner struct { 17 | // b contains a working copy of json value passed to Init. 18 | b []byte 19 | 20 | // s points to the next JSON value to parse. 21 | s string 22 | 23 | // err contains the last error. 24 | err error 25 | 26 | // v contains the last parsed JSON value. 27 | v *Value 28 | 29 | // c is used for caching JSON values. 30 | c cache 31 | } 32 | 33 | // Init initializes sc with the given s. 34 | // 35 | // s may contain multiple JSON values, which may be delimited by whitespace. 36 | func (sc *Scanner) Init(s string) { 37 | sc.b = append(sc.b[:0], s...) 38 | sc.s = b2s(sc.b) 39 | sc.err = nil 40 | sc.v = nil 41 | } 42 | 43 | // InitBytes initializes sc with the given b. 44 | // 45 | // b may contain multiple JSON values, which may be delimited by whitespace. 46 | func (sc *Scanner) InitBytes(b []byte) { 47 | sc.Init(b2s(b)) 48 | } 49 | 50 | // Next parses the next JSON value from s passed to Init. 51 | // 52 | // Returns true on success. The parsed value is available via Value call. 53 | // 54 | // Returns false either on error or on the end of s. 55 | // Call Error in order to determine the cause of the returned false. 56 | func (sc *Scanner) Next() bool { 57 | if sc.err != nil { 58 | return false 59 | } 60 | 61 | sc.s = skipWS(sc.s) 62 | if len(sc.s) == 0 { 63 | sc.err = errEOF 64 | return false 65 | } 66 | 67 | sc.c.reset() 68 | v, tail, err := parseValue(sc.s, &sc.c, 0) 69 | if err != nil { 70 | sc.err = err 71 | return false 72 | } 73 | 74 | sc.s = tail 75 | sc.v = v 76 | return true 77 | } 78 | 79 | // Error returns the last error. 80 | func (sc *Scanner) Error() error { 81 | if sc.err == errEOF { 82 | return nil 83 | } 84 | return sc.err 85 | } 86 | 87 | // Value returns the last parsed value. 88 | // 89 | // The value is valid until the Next call. 90 | func (sc *Scanner) Value() *Value { 91 | return sc.v 92 | } 93 | 94 | var errEOF = errors.New("end of s") 95 | -------------------------------------------------------------------------------- /scanner_example_test.go: -------------------------------------------------------------------------------- 1 | package fastjson_test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/valyala/fastjson" 6 | "log" 7 | ) 8 | 9 | func ExampleScanner() { 10 | var sc fastjson.Scanner 11 | 12 | sc.Init(` {"foo": "bar" }[ ] 13 | 12345"xyz" true false null `) 14 | 15 | for sc.Next() { 16 | fmt.Printf("%s\n", sc.Value()) 17 | } 18 | if err := sc.Error(); err != nil { 19 | log.Fatalf("unexpected error: %s", err) 20 | } 21 | 22 | // Output: 23 | // {"foo":"bar"} 24 | // [] 25 | // 12345 26 | // "xyz" 27 | // true 28 | // false 29 | // null 30 | } 31 | 32 | func ExampleScanner_reuse() { 33 | var sc fastjson.Scanner 34 | 35 | // The sc may be re-used in order to reduce the number 36 | // of memory allocations. 37 | for i := 0; i < 3; i++ { 38 | s := fmt.Sprintf(`[%d] "%d"`, i, i) 39 | sc.Init(s) 40 | for sc.Next() { 41 | fmt.Printf("%s,", sc.Value()) 42 | } 43 | if err := sc.Error(); err != nil { 44 | log.Fatalf("unexpected error: %s", err) 45 | } 46 | fmt.Printf("\n") 47 | } 48 | 49 | // Output: 50 | // [0],"0", 51 | // [1],"1", 52 | // [2],"2", 53 | } 54 | -------------------------------------------------------------------------------- /scanner_test.go: -------------------------------------------------------------------------------- 1 | package fastjson 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "testing" 7 | ) 8 | 9 | func TestScanner(t *testing.T) { 10 | var sc Scanner 11 | 12 | t.Run("success", func(t *testing.T) { 13 | sc.InitBytes([]byte(`[] {} "" 123`)) 14 | var bb bytes.Buffer 15 | for sc.Next() { 16 | v := sc.Value() 17 | fmt.Fprintf(&bb, "%s", v) 18 | } 19 | if err := sc.Error(); err != nil { 20 | t.Fatalf("unexpected error: %s", err) 21 | } 22 | s := bb.String() 23 | if s != `[]{}""123` { 24 | t.Fatalf("unexpected string obtained; got %q; want %q", s, `[]{}""123`) 25 | } 26 | }) 27 | 28 | t.Run("error", func(t *testing.T) { 29 | sc.Init(`[] sdfdsfdf`) 30 | for sc.Next() { 31 | } 32 | if err := sc.Error(); err == nil { 33 | t.Fatalf("expecting non-nil error") 34 | } 35 | if sc.Next() { 36 | t.Fatalf("Next must return false") 37 | } 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /testdata/large.json: -------------------------------------------------------------------------------- 1 | {"users":[{"id":-1,"username":"system","avatar_template":"/user_avatar/discourse.metabase.com/system/{size}/6_1.png"},{"id":89,"username":"zergot","avatar_template":"https://avatars.discourse.org/v2/letter/z/0ea827/{size}.png"},{"id":1,"username":"sameer","avatar_template":"https://avatars.discourse.org/v2/letter/s/bbce88/{size}.png"},{"id":84,"username":"HenryMirror","avatar_template":"https://avatars.discourse.org/v2/letter/h/ecd19e/{size}.png"},{"id":73,"username":"fimp","avatar_template":"https://avatars.discourse.org/v2/letter/f/ee59a6/{size}.png"},{"id":14,"username":"agilliland","avatar_template":"/user_avatar/discourse.metabase.com/agilliland/{size}/26_1.png"},{"id":87,"username":"amir","avatar_template":"https://avatars.discourse.org/v2/letter/a/c37758/{size}.png"},{"id":82,"username":"waseem","avatar_template":"https://avatars.discourse.org/v2/letter/w/9dc877/{size}.png"},{"id":78,"username":"tovenaar","avatar_template":"https://avatars.discourse.org/v2/letter/t/9de0a6/{size}.png"},{"id":74,"username":"Ben","avatar_template":"https://avatars.discourse.org/v2/letter/b/df788c/{size}.png"},{"id":71,"username":"MarkLaFay","avatar_template":"https://avatars.discourse.org/v2/letter/m/3bc359/{size}.png"},{"id":72,"username":"camsaul","avatar_template":"/user_avatar/discourse.metabase.com/camsaul/{size}/70_1.png"},{"id":53,"username":"mhjb","avatar_template":"/user_avatar/discourse.metabase.com/mhjb/{size}/54_1.png"},{"id":58,"username":"jbwiv","avatar_template":"https://avatars.discourse.org/v2/letter/j/6bbea6/{size}.png"},{"id":70,"username":"Maggs","avatar_template":"https://avatars.discourse.org/v2/letter/m/bbce88/{size}.png"},{"id":69,"username":"andrefaria","avatar_template":"/user_avatar/discourse.metabase.com/andrefaria/{size}/65_1.png"},{"id":60,"username":"bencarter78","avatar_template":"/user_avatar/discourse.metabase.com/bencarter78/{size}/59_1.png"},{"id":55,"username":"vikram","avatar_template":"https://avatars.discourse.org/v2/letter/v/e47774/{size}.png"},{"id":68,"username":"edchan77","avatar_template":"/user_avatar/discourse.metabase.com/edchan77/{size}/66_1.png"},{"id":9,"username":"karthikd","avatar_template":"https://avatars.discourse.org/v2/letter/k/cab0a1/{size}.png"},{"id":23,"username":"arthurz","avatar_template":"/user_avatar/discourse.metabase.com/arthurz/{size}/32_1.png"},{"id":3,"username":"tom","avatar_template":"/user_avatar/discourse.metabase.com/tom/{size}/21_1.png"},{"id":50,"username":"LeoNogueira","avatar_template":"/user_avatar/discourse.metabase.com/leonogueira/{size}/52_1.png"},{"id":66,"username":"ss06vi","avatar_template":"https://avatars.discourse.org/v2/letter/s/3ab097/{size}.png"},{"id":34,"username":"mattcollins","avatar_template":"/user_avatar/discourse.metabase.com/mattcollins/{size}/41_1.png"},{"id":51,"username":"krmmalik","avatar_template":"/user_avatar/discourse.metabase.com/krmmalik/{size}/53_1.png"},{"id":46,"username":"odysseas","avatar_template":"https://avatars.discourse.org/v2/letter/o/5f8ce5/{size}.png"},{"id":5,"username":"jonthewayne","avatar_template":"/user_avatar/discourse.metabase.com/jonthewayne/{size}/18_1.png"},{"id":11,"username":"anandiyer","avatar_template":"/user_avatar/discourse.metabase.com/anandiyer/{size}/23_1.png"},{"id":25,"username":"alnorth","avatar_template":"/user_avatar/discourse.metabase.com/alnorth/{size}/34_1.png"},{"id":52,"username":"j_at_svg","avatar_template":"https://avatars.discourse.org/v2/letter/j/96bed5/{size}.png"},{"id":42,"username":"styts","avatar_template":"/user_avatar/discourse.metabase.com/styts/{size}/47_1.png"}],"topics":{"can_create_topic":false,"more_topics_url":"/c/uncategorized/l/latest?page=1","draft":null,"draft_key":"new_topic","draft_sequence":null,"per_page":30,"topics":[{"id":8,"title":"Welcome to Metabase's Discussion Forum","fancy_title":"Welcome to Metabase’s Discussion Forum","slug":"welcome-to-metabases-discussion-forum","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":"/images/welcome/discourse-edit-post-animated.gif","created_at":"2015-10-17T00:14:49.526Z","last_posted_at":"2015-10-17T00:14:49.557Z","bumped":true,"bumped_at":"2015-10-21T02:32:22.486Z","unseen":false,"pinned":true,"unpinned":null,"excerpt":"Welcome to Metabase's discussion forum. This is a place to get help on installation, setting up as well as sharing tips and tricks.","visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"views":197,"like_count":0,"has_summary":false,"archetype":"regular","last_poster_username":"system","category_id":1,"pinned_globally":true,"posters":[{"extras":"latest single","description":"Original Poster, Most Recent Poster","user_id":-1}]},{"id":169,"title":"Formatting Dates","fancy_title":"Formatting Dates","slug":"formatting-dates","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":null,"created_at":"2016-01-14T06:30:45.311Z","last_posted_at":"2016-01-14T06:30:45.397Z","bumped":true,"bumped_at":"2016-01-14T06:30:45.397Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"views":11,"like_count":0,"has_summary":false,"archetype":"regular","last_poster_username":"zergot","category_id":1,"pinned_globally":false,"posters":[{"extras":"latest single","description":"Original Poster, Most Recent Poster","user_id":89}]},{"id":168,"title":"Setting for google api key","fancy_title":"Setting for google api key","slug":"setting-for-google-api-key","posts_count":2,"reply_count":0,"highest_post_number":2,"image_url":null,"created_at":"2016-01-13T17:14:31.799Z","last_posted_at":"2016-01-14T06:24:03.421Z","bumped":true,"bumped_at":"2016-01-14T06:24:03.421Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"views":16,"like_count":0,"has_summary":false,"archetype":"regular","last_poster_username":"zergot","category_id":1,"pinned_globally":false,"posters":[{"extras":"latest single","description":"Original Poster, Most Recent Poster","user_id":89}]},{"id":167,"title":"Cannot see non-US timezones on the admin","fancy_title":"Cannot see non-US timezones on the admin","slug":"cannot-see-non-us-timezones-on-the-admin","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":null,"created_at":"2016-01-13T17:07:36.764Z","last_posted_at":"2016-01-13T17:07:36.831Z","bumped":true,"bumped_at":"2016-01-13T17:07:36.831Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"views":11,"like_count":0,"has_summary":false,"archetype":"regular","last_poster_username":"zergot","category_id":1,"pinned_globally":false,"posters":[{"extras":"latest single","description":"Original Poster, Most Recent Poster","user_id":89}]},{"id":164,"title":"External (Metabase level) linkages in data schema","fancy_title":"External (Metabase level) linkages in data schema","slug":"external-metabase-level-linkages-in-data-schema","posts_count":4,"reply_count":1,"highest_post_number":4,"image_url":null,"created_at":"2016-01-11T13:51:02.286Z","last_posted_at":"2016-01-12T11:06:37.259Z","bumped":true,"bumped_at":"2016-01-12T11:06:37.259Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"views":32,"like_count":0,"has_summary":false,"archetype":"regular","last_poster_username":"zergot","category_id":1,"pinned_globally":false,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":89},{"extras":null,"description":"Frequent Poster","user_id":1}]},{"id":155,"title":"Query working on \"Questions\" but not in \"Pulses\"","fancy_title":"Query working on “Questions” but not in “Pulses”","slug":"query-working-on-questions-but-not-in-pulses","posts_count":3,"reply_count":0,"highest_post_number":3,"image_url":null,"created_at":"2016-01-01T14:06:10.083Z","last_posted_at":"2016-01-08T22:37:51.772Z","bumped":true,"bumped_at":"2016-01-08T22:37:51.772Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"views":72,"like_count":0,"has_summary":false,"archetype":"regular","last_poster_username":"agilliland","category_id":1,"pinned_globally":false,"posters":[{"extras":null,"description":"Original Poster","user_id":84},{"extras":null,"description":"Frequent Poster","user_id":73},{"extras":"latest","description":"Most Recent Poster","user_id":14}]},{"id":161,"title":"Pulses posted to Slack don't show question output","fancy_title":"Pulses posted to Slack don’t show question output","slug":"pulses-posted-to-slack-dont-show-question-output","posts_count":2,"reply_count":0,"highest_post_number":2,"image_url":"/uploads/default/original/1X/9d2806517bf3598b10be135b2c58923b47ba23e7.png","created_at":"2016-01-08T22:09:58.205Z","last_posted_at":"2016-01-08T22:28:44.685Z","bumped":true,"bumped_at":"2016-01-08T22:28:44.685Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"views":34,"like_count":0,"has_summary":false,"archetype":"regular","last_poster_username":"sameer","category_id":1,"pinned_globally":false,"posters":[{"extras":null,"description":"Original Poster","user_id":87},{"extras":"latest","description":"Most Recent Poster","user_id":1}]},{"id":152,"title":"Should we build Kafka connecter or Kafka plugin","fancy_title":"Should we build Kafka connecter or Kafka plugin","slug":"should-we-build-kafka-connecter-or-kafka-plugin","posts_count":4,"reply_count":1,"highest_post_number":4,"image_url":null,"created_at":"2015-12-28T20:37:23.501Z","last_posted_at":"2015-12-31T18:16:45.477Z","bumped":true,"bumped_at":"2015-12-31T18:16:45.477Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"views":84,"like_count":0,"has_summary":false,"archetype":"regular","last_poster_username":"sameer","category_id":1,"pinned_globally":false,"posters":[{"extras":null,"description":"Original Poster","user_id":82},{"extras":"latest","description":"Most Recent Poster, Frequent Poster","user_id":1}]},{"id":147,"title":"Change X and Y on graph","fancy_title":"Change X and Y on graph","slug":"change-x-and-y-on-graph","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":null,"created_at":"2015-12-21T17:52:46.581Z","last_posted_at":"2015-12-21T17:52:46.684Z","bumped":true,"bumped_at":"2015-12-21T18:19:13.003Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"views":68,"like_count":0,"has_summary":false,"archetype":"regular","last_poster_username":"tovenaar","category_id":1,"pinned_globally":false,"posters":[{"extras":"latest single","description":"Original Poster, Most Recent Poster","user_id":78}]},{"id":142,"title":"Issues sending mail via office365 relay","fancy_title":"Issues sending mail via office365 relay","slug":"issues-sending-mail-via-office365-relay","posts_count":5,"reply_count":2,"highest_post_number":5,"image_url":null,"created_at":"2015-12-16T10:38:47.315Z","last_posted_at":"2015-12-21T09:26:27.167Z","bumped":true,"bumped_at":"2015-12-21T09:26:27.167Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"views":122,"like_count":0,"has_summary":false,"archetype":"regular","last_poster_username":"Ben","category_id":1,"pinned_globally":false,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":74},{"extras":null,"description":"Frequent Poster","user_id":1}]},{"id":137,"title":"I see triplicates of my mongoDB collections","fancy_title":"I see triplicates of my mongoDB collections","slug":"i-see-triplicates-of-my-mongodb-collections","posts_count":3,"reply_count":0,"highest_post_number":3,"image_url":null,"created_at":"2015-12-14T13:33:03.426Z","last_posted_at":"2015-12-17T18:40:05.487Z","bumped":true,"bumped_at":"2015-12-17T18:40:05.487Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"views":97,"like_count":0,"has_summary":false,"archetype":"regular","last_poster_username":"MarkLaFay","category_id":1,"pinned_globally":false,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":71},{"extras":null,"description":"Frequent Poster","user_id":14}]},{"id":140,"title":"Google Analytics plugin","fancy_title":"Google Analytics plugin","slug":"google-analytics-plugin","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":null,"created_at":"2015-12-15T13:00:55.644Z","last_posted_at":"2015-12-15T13:00:55.705Z","bumped":true,"bumped_at":"2015-12-15T13:00:55.705Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"views":105,"like_count":0,"has_summary":false,"archetype":"regular","last_poster_username":"fimp","category_id":1,"pinned_globally":false,"posters":[{"extras":"latest single","description":"Original Poster, Most Recent Poster","user_id":73}]},{"id":138,"title":"With-mongo-connection failed: bad connection details:","fancy_title":"With-mongo-connection failed: bad connection details:","slug":"with-mongo-connection-failed-bad-connection-details","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":null,"created_at":"2015-12-14T17:28:11.041Z","last_posted_at":"2015-12-14T17:28:11.111Z","bumped":true,"bumped_at":"2015-12-14T17:28:11.111Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"views":56,"like_count":0,"has_summary":false,"archetype":"regular","last_poster_username":"MarkLaFay","category_id":1,"pinned_globally":false,"posters":[{"extras":"latest single","description":"Original Poster, Most Recent Poster","user_id":71}]},{"id":133,"title":"\"We couldn't understand your question.\" when I query mongoDB","fancy_title":"“We couldn’t understand your question.” when I query mongoDB","slug":"we-couldnt-understand-your-question-when-i-query-mongodb","posts_count":3,"reply_count":0,"highest_post_number":3,"image_url":null,"created_at":"2015-12-11T17:38:30.576Z","last_posted_at":"2015-12-14T13:31:26.395Z","bumped":true,"bumped_at":"2015-12-14T13:31:26.395Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"views":107,"like_count":0,"has_summary":false,"archetype":"regular","last_poster_username":"MarkLaFay","category_id":1,"pinned_globally":false,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":71},{"extras":null,"description":"Frequent Poster","user_id":72}]},{"id":129,"title":"My bar charts are all thin","fancy_title":"My bar charts are all thin","slug":"my-bar-charts-are-all-thin","posts_count":4,"reply_count":1,"highest_post_number":4,"image_url":"/uploads/default/original/1X/41bcf3b2a00dc7cfaff01cb3165d35d32a85bf1d.png","created_at":"2015-12-09T22:09:56.394Z","last_posted_at":"2015-12-11T19:00:45.289Z","bumped":true,"bumped_at":"2015-12-11T19:00:45.289Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"views":116,"like_count":0,"has_summary":false,"archetype":"regular","last_poster_username":"mhjb","category_id":1,"pinned_globally":false,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":53},{"extras":null,"description":"Frequent Poster","user_id":1}]},{"id":106,"title":"What is the expected return order of columns for graphing results when using raw SQL?","fancy_title":"What is the expected return order of columns for graphing results when using raw SQL?","slug":"what-is-the-expected-return-order-of-columns-for-graphing-results-when-using-raw-sql","posts_count":3,"reply_count":0,"highest_post_number":3,"image_url":null,"created_at":"2015-11-24T19:07:14.561Z","last_posted_at":"2015-12-11T17:04:14.149Z","bumped":true,"bumped_at":"2015-12-11T17:04:14.149Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"views":153,"like_count":0,"has_summary":false,"archetype":"regular","last_poster_username":"jbwiv","category_id":1,"pinned_globally":false,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":58},{"extras":null,"description":"Frequent Poster","user_id":14}]},{"id":131,"title":"Set site url from admin panel","fancy_title":"Set site url from admin panel","slug":"set-site-url-from-admin-panel","posts_count":2,"reply_count":0,"highest_post_number":2,"image_url":null,"created_at":"2015-12-10T06:22:46.042Z","last_posted_at":"2015-12-10T19:12:57.449Z","bumped":true,"bumped_at":"2015-12-10T19:12:57.449Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"views":77,"like_count":0,"has_summary":false,"archetype":"regular","last_poster_username":"sameer","category_id":1,"pinned_globally":false,"posters":[{"extras":null,"description":"Original Poster","user_id":70},{"extras":"latest","description":"Most Recent Poster","user_id":1}]},{"id":127,"title":"Internationalization (i18n)","fancy_title":"Internationalization (i18n)","slug":"internationalization-i18n","posts_count":2,"reply_count":0,"highest_post_number":2,"image_url":null,"created_at":"2015-12-08T16:55:37.397Z","last_posted_at":"2015-12-09T16:49:55.816Z","bumped":true,"bumped_at":"2015-12-09T16:49:55.816Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"views":85,"like_count":0,"has_summary":false,"archetype":"regular","last_poster_username":"agilliland","category_id":1,"pinned_globally":false,"posters":[{"extras":null,"description":"Original Poster","user_id":69},{"extras":"latest","description":"Most Recent Poster","user_id":14}]},{"id":109,"title":"Returning raw data with no filters always returns We couldn't understand your question","fancy_title":"Returning raw data with no filters always returns We couldn’t understand your question","slug":"returning-raw-data-with-no-filters-always-returns-we-couldnt-understand-your-question","posts_count":3,"reply_count":1,"highest_post_number":3,"image_url":null,"created_at":"2015-11-25T21:35:01.315Z","last_posted_at":"2015-12-09T10:26:12.255Z","bumped":true,"bumped_at":"2015-12-09T10:26:12.255Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"views":133,"like_count":0,"has_summary":false,"archetype":"regular","last_poster_username":"bencarter78","category_id":1,"pinned_globally":false,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":60},{"extras":null,"description":"Frequent Poster","user_id":14}]},{"id":103,"title":"Support for Cassandra?","fancy_title":"Support for Cassandra?","slug":"support-for-cassandra","posts_count":5,"reply_count":1,"highest_post_number":5,"image_url":null,"created_at":"2015-11-20T06:45:31.741Z","last_posted_at":"2015-12-09T03:18:51.274Z","bumped":true,"bumped_at":"2015-12-09T03:18:51.274Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"views":169,"like_count":0,"has_summary":false,"archetype":"regular","last_poster_username":"vikram","category_id":1,"pinned_globally":false,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":55},{"extras":null,"description":"Frequent Poster","user_id":1}]},{"id":128,"title":"Mongo query with Date breaks [solved: Mongo 3.0 required]","fancy_title":"Mongo query with Date breaks [solved: Mongo 3.0 required]","slug":"mongo-query-with-date-breaks-solved-mongo-3-0-required","posts_count":5,"reply_count":0,"highest_post_number":5,"image_url":null,"created_at":"2015-12-08T18:30:56.562Z","last_posted_at":"2015-12-08T21:03:02.421Z","bumped":true,"bumped_at":"2015-12-08T21:03:02.421Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"views":102,"like_count":1,"has_summary":false,"archetype":"regular","last_poster_username":"edchan77","category_id":1,"pinned_globally":false,"posters":[{"extras":"latest","description":"Original Poster, Most Recent Poster","user_id":68},{"extras":null,"description":"Frequent Poster","user_id":1}]},{"id":23,"title":"Can this connect to MS SQL Server?","fancy_title":"Can this connect to MS SQL Server?","slug":"can-this-connect-to-ms-sql-server","posts_count":7,"reply_count":1,"highest_post_number":7,"image_url":null,"created_at":"2015-10-21T18:52:37.987Z","last_posted_at":"2015-12-07T17:41:51.609Z","bumped":true,"bumped_at":"2015-12-07T17:41:51.609Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"views":367,"like_count":0,"has_summary":false,"archetype":"regular","last_poster_username":"sameer","category_id":1,"pinned_globally":false,"posters":[{"extras":null,"description":"Original Poster","user_id":9},{"extras":null,"description":"Frequent Poster","user_id":23},{"extras":null,"description":"Frequent Poster","user_id":3},{"extras":null,"description":"Frequent Poster","user_id":50},{"extras":"latest","description":"Most Recent Poster","user_id":1}]},{"id":121,"title":"Cannot restart metabase in docker","fancy_title":"Cannot restart metabase in docker","slug":"cannot-restart-metabase-in-docker","posts_count":5,"reply_count":1,"highest_post_number":5,"image_url":null,"created_at":"2015-12-04T21:28:58.137Z","last_posted_at":"2015-12-04T23:02:00.488Z","bumped":true,"bumped_at":"2015-12-04T23:02:00.488Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"views":96,"like_count":0,"has_summary":false,"archetype":"regular","last_poster_username":"sameer","category_id":1,"pinned_globally":false,"posters":[{"extras":null,"description":"Original Poster","user_id":66},{"extras":"latest","description":"Most Recent Poster, Frequent Poster","user_id":1}]},{"id":85,"title":"Edit Max Rows Count","fancy_title":"Edit Max Rows Count","slug":"edit-max-rows-count","posts_count":4,"reply_count":2,"highest_post_number":4,"image_url":null,"created_at":"2015-11-11T23:46:52.917Z","last_posted_at":"2015-11-24T01:01:14.569Z","bumped":true,"bumped_at":"2015-11-24T01:01:14.569Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"views":169,"like_count":1,"has_summary":false,"archetype":"regular","last_poster_username":"sameer","category_id":1,"pinned_globally":false,"posters":[{"extras":null,"description":"Original Poster","user_id":34},{"extras":"latest","description":"Most Recent Poster, Frequent Poster","user_id":1}]},{"id":96,"title":"Creating charts by querying more than one table at a time","fancy_title":"Creating charts by querying more than one table at a time","slug":"creating-charts-by-querying-more-than-one-table-at-a-time","posts_count":6,"reply_count":4,"highest_post_number":6,"image_url":null,"created_at":"2015-11-17T11:20:18.442Z","last_posted_at":"2015-11-21T02:12:25.995Z","bumped":true,"bumped_at":"2015-11-21T02:12:25.995Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"views":217,"like_count":0,"has_summary":false,"archetype":"regular","last_poster_username":"sameer","category_id":1,"pinned_globally":false,"posters":[{"extras":null,"description":"Original Poster","user_id":51},{"extras":"latest","description":"Most Recent Poster, Frequent Poster","user_id":1}]},{"id":90,"title":"Trying to add RDS postgresql as the database fails silently","fancy_title":"Trying to add RDS postgresql as the database fails silently","slug":"trying-to-add-rds-postgresql-as-the-database-fails-silently","posts_count":4,"reply_count":2,"highest_post_number":4,"image_url":null,"created_at":"2015-11-14T23:45:02.967Z","last_posted_at":"2015-11-21T01:08:45.915Z","bumped":true,"bumped_at":"2015-11-21T01:08:45.915Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"views":162,"like_count":0,"has_summary":false,"archetype":"regular","last_poster_username":"sameer","category_id":1,"pinned_globally":false,"posters":[{"extras":null,"description":"Original Poster","user_id":46},{"extras":"latest","description":"Most Recent Poster, Frequent Poster","user_id":1}]},{"id":17,"title":"Deploy to Heroku isn't working","fancy_title":"Deploy to Heroku isn’t working","slug":"deploy-to-heroku-isnt-working","posts_count":9,"reply_count":3,"highest_post_number":9,"image_url":null,"created_at":"2015-10-21T16:42:03.096Z","last_posted_at":"2015-11-20T18:34:14.044Z","bumped":true,"bumped_at":"2015-11-20T18:34:14.044Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"views":332,"like_count":2,"has_summary":false,"archetype":"regular","last_poster_username":"agilliland","category_id":1,"pinned_globally":false,"posters":[{"extras":null,"description":"Original Poster","user_id":5},{"extras":null,"description":"Frequent Poster","user_id":3},{"extras":null,"description":"Frequent Poster","user_id":11},{"extras":null,"description":"Frequent Poster","user_id":25},{"extras":"latest","description":"Most Recent Poster","user_id":14}]},{"id":100,"title":"Can I use DATEPART() in SQL queries?","fancy_title":"Can I use DATEPART() in SQL queries?","slug":"can-i-use-datepart-in-sql-queries","posts_count":2,"reply_count":0,"highest_post_number":2,"image_url":null,"created_at":"2015-11-17T23:15:58.033Z","last_posted_at":"2015-11-18T00:19:48.763Z","bumped":true,"bumped_at":"2015-11-18T00:19:48.763Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"views":112,"like_count":1,"has_summary":false,"archetype":"regular","last_poster_username":"sameer","category_id":1,"pinned_globally":false,"posters":[{"extras":null,"description":"Original Poster","user_id":53},{"extras":"latest","description":"Most Recent Poster","user_id":1}]},{"id":98,"title":"Feature Request: LDAP Authentication","fancy_title":"Feature Request: LDAP Authentication","slug":"feature-request-ldap-authentication","posts_count":1,"reply_count":0,"highest_post_number":1,"image_url":null,"created_at":"2015-11-17T17:22:44.484Z","last_posted_at":"2015-11-17T17:22:44.577Z","bumped":true,"bumped_at":"2015-11-17T17:22:44.577Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"views":97,"like_count":0,"has_summary":false,"archetype":"regular","last_poster_username":"j_at_svg","category_id":1,"pinned_globally":false,"posters":[{"extras":"latest single","description":"Original Poster, Most Recent Poster","user_id":52}]},{"id":87,"title":"Migrating from internal H2 to Postgres","fancy_title":"Migrating from internal H2 to Postgres","slug":"migrating-from-internal-h2-to-postgres","posts_count":2,"reply_count":0,"highest_post_number":2,"image_url":null,"created_at":"2015-11-12T14:36:06.745Z","last_posted_at":"2015-11-12T18:05:10.796Z","bumped":true,"bumped_at":"2015-11-12T18:05:10.796Z","unseen":false,"pinned":false,"unpinned":null,"visible":true,"closed":false,"archived":false,"bookmarked":null,"liked":null,"views":111,"like_count":0,"has_summary":false,"archetype":"regular","last_poster_username":"sameer","category_id":1,"pinned_globally":false,"posters":[{"extras":null,"description":"Original Poster","user_id":42},{"extras":"latest","description":"Most Recent Poster","user_id":1}]}]}} 2 | -------------------------------------------------------------------------------- /testdata/medium.json: -------------------------------------------------------------------------------- 1 | { 2 | "person": { 3 | "id": "d50887ca-a6ce-4e59-b89f-14f0b5d03b03", 4 | "name": { 5 | "fullName": "Leonid Bugaev", 6 | "givenName": "Leonid", 7 | "familyName": "Bugaev" 8 | }, 9 | "email": "leonsbox@gmail.com", 10 | "gender": "male", 11 | "location": "Saint Petersburg, Saint Petersburg, RU", 12 | "geo": { 13 | "city": "Saint Petersburg", 14 | "state": "Saint Petersburg", 15 | "country": "Russia", 16 | "lat": 59.9342802, 17 | "lng": 30.3350986 18 | }, 19 | "bio": "Senior engineer at Granify.com", 20 | "site": "http://flickfaver.com", 21 | "avatar": "https://d1ts43dypk8bqh.cloudfront.net/v1/avatars/d50887ca-a6ce-4e59-b89f-14f0b5d03b03", 22 | "employment": { 23 | "name": "www.latera.ru", 24 | "title": "Software Engineer", 25 | "domain": "gmail.com" 26 | }, 27 | "facebook": { 28 | "handle": "leonid.bugaev" 29 | }, 30 | "github": { 31 | "handle": "buger", 32 | "id": 14009, 33 | "avatar": "https://avatars.githubusercontent.com/u/14009?v=3", 34 | "company": "Granify", 35 | "blog": "http://leonsbox.com", 36 | "followers": 95, 37 | "following": 10 38 | }, 39 | "twitter": { 40 | "handle": "flickfaver", 41 | "id": 77004410, 42 | "bio": null, 43 | "followers": 2, 44 | "following": 1, 45 | "statuses": 5, 46 | "favorites": 0, 47 | "location": "", 48 | "site": "http://flickfaver.com", 49 | "avatar": null 50 | }, 51 | "linkedin": { 52 | "handle": "in/leonidbugaev" 53 | }, 54 | "googleplus": { 55 | "handle": null 56 | }, 57 | "angellist": { 58 | "handle": "leonid-bugaev", 59 | "id": 61541, 60 | "bio": "Senior engineer at Granify.com", 61 | "blog": "http://buger.github.com", 62 | "site": "http://buger.github.com", 63 | "followers": 41, 64 | "avatar": "https://d1qb2nb5cznatu.cloudfront.net/users/61541-medium_jpg?1405474390" 65 | }, 66 | "klout": { 67 | "handle": null, 68 | "score": null 69 | }, 70 | "foursquare": { 71 | "handle": null 72 | }, 73 | "aboutme": { 74 | "handle": "leonid.bugaev", 75 | "bio": null, 76 | "avatar": null 77 | }, 78 | "gravatar": { 79 | "handle": "buger", 80 | "urls": [ 81 | ], 82 | "avatar": "http://1.gravatar.com/avatar/f7c8edd577d13b8930d5522f28123510", 83 | "avatars": [ 84 | { 85 | "url": "http://1.gravatar.com/avatar/f7c8edd577d13b8930d5522f28123510", 86 | "type": "thumbnail" 87 | } 88 | ] 89 | }, 90 | "fuzzy": false 91 | }, 92 | "company": null 93 | } 94 | -------------------------------------------------------------------------------- /testdata/small.json: -------------------------------------------------------------------------------- 1 | { 2 | "st": 1, 3 | "sid": 486, 4 | "tt": "active", 5 | "gr": 0, 6 | "uuid": "de305d54-75b4-431b-adb2-eb6b9e546014", 7 | "ip": "127.0.0.1", 8 | "ua": "user_agent", 9 | "tz": -6, 10 | "v": 1 11 | } 12 | -------------------------------------------------------------------------------- /update.go: -------------------------------------------------------------------------------- 1 | package fastjson 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | ) 7 | 8 | // Del deletes the entry with the given key from o. 9 | func (o *Object) Del(key string) { 10 | if o == nil { 11 | return 12 | } 13 | if !o.keysUnescaped && strings.IndexByte(key, '\\') < 0 { 14 | // Fast path - try searching for the key without object keys unescaping. 15 | for i, kv := range o.kvs { 16 | if kv.k == key { 17 | o.kvs = append(o.kvs[:i], o.kvs[i+1:]...) 18 | return 19 | } 20 | } 21 | } 22 | 23 | // Slow path - unescape object keys before item search. 24 | o.unescapeKeys() 25 | 26 | for i, kv := range o.kvs { 27 | if kv.k == key { 28 | o.kvs = append(o.kvs[:i], o.kvs[i+1:]...) 29 | return 30 | } 31 | } 32 | } 33 | 34 | // Del deletes the entry with the given key from array or object v. 35 | func (v *Value) Del(key string) { 36 | if v == nil { 37 | return 38 | } 39 | if v.t == TypeObject { 40 | v.o.Del(key) 41 | return 42 | } 43 | if v.t == TypeArray { 44 | n, err := strconv.Atoi(key) 45 | if err != nil || n < 0 || n >= len(v.a) { 46 | return 47 | } 48 | v.a = append(v.a[:n], v.a[n+1:]...) 49 | } 50 | } 51 | 52 | // Set sets (key, value) entry in the o. 53 | // 54 | // The value must be unchanged during o lifetime. 55 | func (o *Object) Set(key string, value *Value) { 56 | if o == nil { 57 | return 58 | } 59 | if value == nil { 60 | value = valueNull 61 | } 62 | o.unescapeKeys() 63 | 64 | // Try substituting already existing entry with the given key. 65 | for i := range o.kvs { 66 | kv := &o.kvs[i] 67 | if kv.k == key { 68 | kv.v = value 69 | return 70 | } 71 | } 72 | 73 | // Add new entry. 74 | kv := o.getKV() 75 | kv.k = key 76 | kv.v = value 77 | } 78 | 79 | // Set sets (key, value) entry in the array or object v. 80 | // 81 | // The value must be unchanged during v lifetime. 82 | func (v *Value) Set(key string, value *Value) { 83 | if v == nil { 84 | return 85 | } 86 | if v.t == TypeObject { 87 | v.o.Set(key, value) 88 | return 89 | } 90 | if v.t == TypeArray { 91 | idx, err := strconv.Atoi(key) 92 | if err != nil || idx < 0 { 93 | return 94 | } 95 | v.SetArrayItem(idx, value) 96 | } 97 | } 98 | 99 | // SetArrayItem sets the value in the array v at idx position. 100 | // 101 | // The value must be unchanged during v lifetime. 102 | func (v *Value) SetArrayItem(idx int, value *Value) { 103 | if v == nil || v.t != TypeArray { 104 | return 105 | } 106 | for idx >= len(v.a) { 107 | v.a = append(v.a, valueNull) 108 | } 109 | v.a[idx] = value 110 | } 111 | -------------------------------------------------------------------------------- /update_example_test.go: -------------------------------------------------------------------------------- 1 | package fastjson_test 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/valyala/fastjson" 8 | ) 9 | 10 | func ExampleObject_Del() { 11 | v := fastjson.MustParse(`{"foo": 123, "bar": [1,2], "baz": "xyz"}`) 12 | o, err := v.Object() 13 | if err != nil { 14 | log.Fatalf("cannot otain object: %s", err) 15 | } 16 | fmt.Printf("%s\n", o) 17 | 18 | o.Del("bar") 19 | fmt.Printf("%s\n", o) 20 | 21 | o.Del("foo") 22 | fmt.Printf("%s\n", o) 23 | 24 | o.Del("baz") 25 | fmt.Printf("%s\n", o) 26 | 27 | // Output: 28 | // {"foo":123,"bar":[1,2],"baz":"xyz"} 29 | // {"foo":123,"baz":"xyz"} 30 | // {"baz":"xyz"} 31 | // {} 32 | } 33 | 34 | func ExampleValue_Del() { 35 | v := fastjson.MustParse(`{"foo": 123, "bar": [1,2], "baz": "xyz"}`) 36 | fmt.Printf("%s\n", v) 37 | 38 | v.Del("foo") 39 | fmt.Printf("%s\n", v) 40 | 41 | v.Get("bar").Del("0") 42 | fmt.Printf("%s\n", v) 43 | 44 | // Output: 45 | // {"foo":123,"bar":[1,2],"baz":"xyz"} 46 | // {"bar":[1,2],"baz":"xyz"} 47 | // {"bar":[2],"baz":"xyz"} 48 | } 49 | 50 | func ExampleValue_Set() { 51 | v := fastjson.MustParse(`{"foo":1,"bar":[2,3]}`) 52 | 53 | // Replace `foo` value with "xyz" 54 | v.Set("foo", fastjson.MustParse(`"xyz"`)) 55 | // Add "newv":123 56 | v.Set("newv", fastjson.MustParse(`123`)) 57 | fmt.Printf("%s\n", v) 58 | 59 | // Replace `bar.1` with {"x":"y"} 60 | v.Get("bar").Set("1", fastjson.MustParse(`{"x":"y"}`)) 61 | // Add `bar.3="qwe" 62 | v.Get("bar").Set("3", fastjson.MustParse(`"qwe"`)) 63 | fmt.Printf("%s\n", v) 64 | 65 | // Output: 66 | // {"foo":"xyz","bar":[2,3],"newv":123} 67 | // {"foo":"xyz","bar":[2,{"x":"y"},null,"qwe"],"newv":123} 68 | } 69 | -------------------------------------------------------------------------------- /update_test.go: -------------------------------------------------------------------------------- 1 | package fastjson 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestObjectDelSet(t *testing.T) { 8 | var p Parser 9 | var o *Object 10 | 11 | o.Del("xx") 12 | 13 | v, err := p.Parse(`{"fo\no": "bar", "x": [1,2,3]}`) 14 | if err != nil { 15 | t.Fatalf("unexpected error during parse: %s", err) 16 | } 17 | o, err = v.Object() 18 | if err != nil { 19 | t.Fatalf("cannot obtain object: %s", err) 20 | } 21 | 22 | // Delete x 23 | o.Del("x") 24 | if o.Len() != 1 { 25 | t.Fatalf("unexpected number of items left; got %d; want %d", o.Len(), 1) 26 | } 27 | 28 | // Try deleting non-existing value 29 | o.Del("xxx") 30 | if o.Len() != 1 { 31 | t.Fatalf("unexpected number of items left; got %d; want %d", o.Len(), 1) 32 | } 33 | 34 | // Set new value 35 | vNew := MustParse(`{"foo":[1,2,3]}`) 36 | o.Set("new_key", vNew) 37 | 38 | // Delete item with escaped key 39 | o.Del("fo\no") 40 | if o.Len() != 1 { 41 | t.Fatalf("unexpected number of items left; got %d; want %d", o.Len(), 1) 42 | } 43 | 44 | str := o.String() 45 | strExpected := `{"new_key":{"foo":[1,2,3]}}` 46 | if str != strExpected { 47 | t.Fatalf("unexpected string representation for o: got %q; want %q", str, strExpected) 48 | } 49 | 50 | // Set and Del function as no-op on nil value 51 | o = nil 52 | o.Del("x") 53 | o.Set("x", MustParse(`[3]`)) 54 | } 55 | 56 | func TestValueDelSet(t *testing.T) { 57 | var p Parser 58 | v, err := p.Parse(`{"xx": 123, "x": [1,2,3]}`) 59 | if err != nil { 60 | t.Fatalf("unexpected error during parse: %s", err) 61 | } 62 | 63 | // Delete xx 64 | v.Del("xx") 65 | n := v.GetObject().Len() 66 | if n != 1 { 67 | t.Fatalf("unexpected number of items left; got %d; want %d", n, 1) 68 | } 69 | 70 | // Try deleting non-existing value in the array 71 | va := v.Get("x") 72 | va.Del("foobar") 73 | 74 | // Delete middle element in the array 75 | va.Del("1") 76 | a := v.GetArray("x") 77 | if len(a) != 2 { 78 | t.Fatalf("unexpected number of items left in the array; got %d; want %d", len(a), 2) 79 | } 80 | 81 | // Update the first element in the array 82 | vNew := MustParse(`"foobar"`) 83 | va.Set("0", vNew) 84 | 85 | // Add third element to the array 86 | vNew = MustParse(`[3]`) 87 | va.Set("3", vNew) 88 | 89 | // Add invalid array index to the array 90 | va.Set("invalid", MustParse(`"nonsense"`)) 91 | 92 | str := v.String() 93 | strExpected := `{"x":["foobar",3,null,[3]]}` 94 | if str != strExpected { 95 | t.Fatalf("unexpected string representation for o: got %q; want %q", str, strExpected) 96 | } 97 | 98 | // Set and Del function as no-op on nil value 99 | v = nil 100 | v.Del("x") 101 | v.Set("x", MustParse(`[]`)) 102 | v.SetArrayItem(1, MustParse(`[]`)) 103 | } 104 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package fastjson 2 | 3 | import ( 4 | "reflect" 5 | "unsafe" 6 | ) 7 | 8 | func b2s(b []byte) string { 9 | return *(*string)(unsafe.Pointer(&b)) 10 | } 11 | 12 | func s2b(s string) (b []byte) { 13 | strh := (*reflect.StringHeader)(unsafe.Pointer(&s)) 14 | sh := (*reflect.SliceHeader)(unsafe.Pointer(&b)) 15 | sh.Data = strh.Data 16 | sh.Len = strh.Len 17 | sh.Cap = strh.Len 18 | return b 19 | } 20 | 21 | const maxStartEndStringLen = 80 22 | 23 | func startEndString(s string) string { 24 | if len(s) <= maxStartEndStringLen { 25 | return s 26 | } 27 | start := s[:40] 28 | end := s[len(s)-40:] 29 | return start + "..." + end 30 | } 31 | -------------------------------------------------------------------------------- /util_test.go: -------------------------------------------------------------------------------- 1 | package fastjson 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestStartEndString(t *testing.T) { 8 | f := func(s, expectedResult string) { 9 | t.Helper() 10 | result := startEndString(s) 11 | if result != expectedResult { 12 | t.Fatalf("unexpected result for startEndString(%q); got %q; want %q", s, result, expectedResult) 13 | } 14 | } 15 | f("", "") 16 | f("foo", "foo") 17 | 18 | getString := func(n int) string { 19 | b := make([]byte, 0, n) 20 | for i := 0; i < n; i++ { 21 | b = append(b, 'a'+byte(i%26)) 22 | } 23 | return string(b) 24 | } 25 | s := getString(maxStartEndStringLen) 26 | f(s, s) 27 | 28 | f(getString(maxStartEndStringLen+1), "abcdefghijklmnopqrstuvwxyzabcdefghijklmn...pqrstuvwxyzabcdefghijklmnopqrstuvwxyzabc") 29 | f(getString(100*maxStartEndStringLen), "abcdefghijklmnopqrstuvwxyzabcdefghijklmn...efghijklmnopqrstuvwxyzabcdefghijklmnopqr") 30 | } 31 | -------------------------------------------------------------------------------- /validate.go: -------------------------------------------------------------------------------- 1 | package fastjson 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | // Validate validates JSON s. 10 | func Validate(s string) error { 11 | s = skipWS(s) 12 | 13 | tail, err := validateValue(s) 14 | if err != nil { 15 | return fmt.Errorf("cannot parse JSON: %s; unparsed tail: %q", err, startEndString(tail)) 16 | } 17 | tail = skipWS(tail) 18 | if len(tail) > 0 { 19 | return fmt.Errorf("unexpected tail: %q", startEndString(tail)) 20 | } 21 | return nil 22 | } 23 | 24 | // ValidateBytes validates JSON b. 25 | func ValidateBytes(b []byte) error { 26 | return Validate(b2s(b)) 27 | } 28 | 29 | func validateValue(s string) (string, error) { 30 | if len(s) == 0 { 31 | return s, fmt.Errorf("cannot parse empty string") 32 | } 33 | 34 | if s[0] == '{' { 35 | tail, err := validateObject(s[1:]) 36 | if err != nil { 37 | return tail, fmt.Errorf("cannot parse object: %s", err) 38 | } 39 | return tail, nil 40 | } 41 | if s[0] == '[' { 42 | tail, err := validateArray(s[1:]) 43 | if err != nil { 44 | return tail, fmt.Errorf("cannot parse array: %s", err) 45 | } 46 | return tail, nil 47 | } 48 | if s[0] == '"' { 49 | sv, tail, err := validateString(s[1:]) 50 | if err != nil { 51 | return tail, fmt.Errorf("cannot parse string: %s", err) 52 | } 53 | // Scan the string for control chars. 54 | for i := 0; i < len(sv); i++ { 55 | if sv[i] < 0x20 { 56 | return tail, fmt.Errorf("string cannot contain control char 0x%02X", sv[i]) 57 | } 58 | } 59 | return tail, nil 60 | } 61 | if s[0] == 't' { 62 | if len(s) < len("true") || s[:len("true")] != "true" { 63 | return s, fmt.Errorf("unexpected value found: %q", s) 64 | } 65 | return s[len("true"):], nil 66 | } 67 | if s[0] == 'f' { 68 | if len(s) < len("false") || s[:len("false")] != "false" { 69 | return s, fmt.Errorf("unexpected value found: %q", s) 70 | } 71 | return s[len("false"):], nil 72 | } 73 | if s[0] == 'n' { 74 | if len(s) < len("null") || s[:len("null")] != "null" { 75 | return s, fmt.Errorf("unexpected value found: %q", s) 76 | } 77 | return s[len("null"):], nil 78 | } 79 | 80 | tail, err := validateNumber(s) 81 | if err != nil { 82 | return tail, fmt.Errorf("cannot parse number: %s", err) 83 | } 84 | return tail, nil 85 | } 86 | 87 | func validateArray(s string) (string, error) { 88 | s = skipWS(s) 89 | if len(s) == 0 { 90 | return s, fmt.Errorf("missing ']'") 91 | } 92 | if s[0] == ']' { 93 | return s[1:], nil 94 | } 95 | 96 | for { 97 | var err error 98 | 99 | s = skipWS(s) 100 | s, err = validateValue(s) 101 | if err != nil { 102 | return s, fmt.Errorf("cannot parse array value: %s", err) 103 | } 104 | 105 | s = skipWS(s) 106 | if len(s) == 0 { 107 | return s, fmt.Errorf("unexpected end of array") 108 | } 109 | if s[0] == ',' { 110 | s = s[1:] 111 | continue 112 | } 113 | if s[0] == ']' { 114 | s = s[1:] 115 | return s, nil 116 | } 117 | return s, fmt.Errorf("missing ',' after array value") 118 | } 119 | } 120 | 121 | func validateObject(s string) (string, error) { 122 | s = skipWS(s) 123 | if len(s) == 0 { 124 | return s, fmt.Errorf("missing '}'") 125 | } 126 | if s[0] == '}' { 127 | return s[1:], nil 128 | } 129 | 130 | for { 131 | var err error 132 | 133 | // Parse key. 134 | s = skipWS(s) 135 | if len(s) == 0 || s[0] != '"' { 136 | return s, fmt.Errorf(`cannot find opening '"" for object key`) 137 | } 138 | 139 | var key string 140 | key, s, err = validateKey(s[1:]) 141 | if err != nil { 142 | return s, fmt.Errorf("cannot parse object key: %s", err) 143 | } 144 | // Scan the key for control chars. 145 | for i := 0; i < len(key); i++ { 146 | if key[i] < 0x20 { 147 | return s, fmt.Errorf("object key cannot contain control char 0x%02X", key[i]) 148 | } 149 | } 150 | s = skipWS(s) 151 | if len(s) == 0 || s[0] != ':' { 152 | return s, fmt.Errorf("missing ':' after object key") 153 | } 154 | s = s[1:] 155 | 156 | // Parse value 157 | s = skipWS(s) 158 | s, err = validateValue(s) 159 | if err != nil { 160 | return s, fmt.Errorf("cannot parse object value: %s", err) 161 | } 162 | s = skipWS(s) 163 | if len(s) == 0 { 164 | return s, fmt.Errorf("unexpected end of object") 165 | } 166 | if s[0] == ',' { 167 | s = s[1:] 168 | continue 169 | } 170 | if s[0] == '}' { 171 | return s[1:], nil 172 | } 173 | return s, fmt.Errorf("missing ',' after object value") 174 | } 175 | } 176 | 177 | // validateKey is similar to validateString, but is optimized 178 | // for typical object keys, which are quite small and have no escape sequences. 179 | func validateKey(s string) (string, string, error) { 180 | for i := 0; i < len(s); i++ { 181 | if s[i] == '"' { 182 | // Fast path - the key doesn't contain escape sequences. 183 | return s[:i], s[i+1:], nil 184 | } 185 | if s[i] == '\\' { 186 | // Slow path - the key contains escape sequences. 187 | return validateString(s) 188 | } 189 | } 190 | return "", s, fmt.Errorf(`missing closing '"'`) 191 | } 192 | 193 | func validateString(s string) (string, string, error) { 194 | // Try fast path - a string without escape sequences. 195 | if n := strings.IndexByte(s, '"'); n >= 0 && strings.IndexByte(s[:n], '\\') < 0 { 196 | return s[:n], s[n+1:], nil 197 | } 198 | 199 | // Slow path - escape sequences are present. 200 | rs, tail, err := parseRawString(s) 201 | if err != nil { 202 | return rs, tail, err 203 | } 204 | for { 205 | n := strings.IndexByte(rs, '\\') 206 | if n < 0 { 207 | return rs, tail, nil 208 | } 209 | n++ 210 | if n >= len(rs) { 211 | return rs, tail, fmt.Errorf("BUG: parseRawString returned invalid string with trailing backslash: %q", rs) 212 | } 213 | ch := rs[n] 214 | rs = rs[n+1:] 215 | switch ch { 216 | case '"', '\\', '/', 'b', 'f', 'n', 'r', 't': 217 | // Valid escape sequences - see http://json.org/ 218 | break 219 | case 'u': 220 | if len(rs) < 4 { 221 | return rs, tail, fmt.Errorf(`too short escape sequence: \u%s`, rs) 222 | } 223 | xs := rs[:4] 224 | _, err := strconv.ParseUint(xs, 16, 16) 225 | if err != nil { 226 | return rs, tail, fmt.Errorf(`invalid escape sequence \u%s: %s`, xs, err) 227 | } 228 | rs = rs[4:] 229 | default: 230 | return rs, tail, fmt.Errorf(`unknown escape sequence \%c`, ch) 231 | } 232 | } 233 | } 234 | 235 | func validateNumber(s string) (string, error) { 236 | if len(s) == 0 { 237 | return s, fmt.Errorf("zero-length number") 238 | } 239 | if s[0] == '-' { 240 | s = s[1:] 241 | if len(s) == 0 { 242 | return s, fmt.Errorf("missing number after minus") 243 | } 244 | } 245 | i := 0 246 | for i < len(s) { 247 | if s[i] < '0' || s[i] > '9' { 248 | break 249 | } 250 | i++ 251 | } 252 | if i <= 0 { 253 | return s, fmt.Errorf("expecting 0..9 digit, got %c", s[0]) 254 | } 255 | if s[0] == '0' && i != 1 { 256 | return s, fmt.Errorf("unexpected number starting from 0") 257 | } 258 | if i >= len(s) { 259 | return "", nil 260 | } 261 | if s[i] == '.' { 262 | // Validate fractional part 263 | s = s[i+1:] 264 | if len(s) == 0 { 265 | return s, fmt.Errorf("missing fractional part") 266 | } 267 | i = 0 268 | for i < len(s) { 269 | if s[i] < '0' || s[i] > '9' { 270 | break 271 | } 272 | i++ 273 | } 274 | if i == 0 { 275 | return s, fmt.Errorf("expecting 0..9 digit in fractional part, got %c", s[0]) 276 | } 277 | if i >= len(s) { 278 | return "", nil 279 | } 280 | } 281 | if s[i] == 'e' || s[i] == 'E' { 282 | // Validate exponent part 283 | s = s[i+1:] 284 | if len(s) == 0 { 285 | return s, fmt.Errorf("missing exponent part") 286 | } 287 | if s[0] == '-' || s[0] == '+' { 288 | s = s[1:] 289 | if len(s) == 0 { 290 | return s, fmt.Errorf("missing exponent part") 291 | } 292 | } 293 | i = 0 294 | for i < len(s) { 295 | if s[i] < '0' || s[i] > '9' { 296 | break 297 | } 298 | i++ 299 | } 300 | if i == 0 { 301 | return s, fmt.Errorf("expecting 0..9 digit in exponent part, got %c", s[0]) 302 | } 303 | if i >= len(s) { 304 | return "", nil 305 | } 306 | } 307 | return s[i:], nil 308 | } 309 | -------------------------------------------------------------------------------- /validate_test.go: -------------------------------------------------------------------------------- 1 | package fastjson 2 | 3 | import ( 4 | "encoding/json" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestValidateSimple(t *testing.T) { 10 | if err := Validate(`123`); err != nil { 11 | t.Fatalf("cannot validate number: %s", err) 12 | } 13 | if err := Validate(`"foobar"`); err != nil { 14 | t.Fatalf("cannot validate string: %s", err) 15 | } 16 | if err := Validate(`null`); err != nil { 17 | t.Fatalf("cannot validate null: %s", err) 18 | } 19 | if err := Validate(`true`); err != nil { 20 | t.Fatalf("cannot validate true: %s", err) 21 | } 22 | if err := Validate(`false`); err != nil { 23 | t.Fatalf("cannot validate false: %s", err) 24 | } 25 | if err := Validate(`foobar`); err == nil { 26 | t.Fatalf("validation unexpectedly passed") 27 | } 28 | if err := Validate(`XDF`); err == nil { 29 | t.Fatalf("validation unexpectedly passed") 30 | } 31 | 32 | if err := ValidateBytes([]byte(`{"foo":["bar", 123]}`)); err != nil { 33 | t.Fatalf("cannot validate valid JSON: %s", err) 34 | } 35 | if err := ValidateBytes([]byte(`{"foo": bar`)); err == nil { 36 | t.Fatalf("validation unexpectedly passed") 37 | } 38 | } 39 | 40 | func TestValidateNumberZeroLen(t *testing.T) { 41 | tail, err := validateNumber("") 42 | if err == nil { 43 | t.Fatalf("expecting non-nil error") 44 | } 45 | if tail != "" { 46 | t.Fatalf("unexpected non-empty tail: %q", tail) 47 | } 48 | } 49 | 50 | func TestValidate(t *testing.T) { 51 | var tests = []string{ 52 | "", 53 | " ", 54 | " z", 55 | " 1 1", 56 | " 1 {}", 57 | " 1 []", 58 | " 1 true", 59 | " 1 null", 60 | " 1 \"n\"", 61 | 62 | // string 63 | `"foo"`, 64 | "\"\xe2\x80\xa8\xe2\x80\xa9\"", // line-sep and paragraph-sep 65 | ` "\uaaaa" `, 66 | `"\uz"`, 67 | ` "\`, 68 | ` "\z`, 69 | " \"f\x00o\"", // control char 70 | "\"foo\nbar\"", // control char 71 | `"foo\qw"`, // unknown escape sequence 72 | ` "foo`, 73 | ` "\uazaa" `, 74 | `"\"\\\/\b\f\n\r\t"`, 75 | 76 | // number 77 | "1", 78 | " 0 ", 79 | " 0e1 ", 80 | " 0e+0 ", 81 | " -0e+0 ", 82 | "-0", 83 | "1e6", 84 | "1e+6", 85 | "-1e+6", 86 | "-0e+6", 87 | " -103e+1 ", 88 | "-0.01e+006", 89 | "-z", 90 | "-", 91 | "1e", 92 | "1e+", 93 | " 03e+1 ", 94 | " 1e.1 ", 95 | " 00 ", 96 | "1.e3", 97 | "01e+6", 98 | "-0.01e+0.6", 99 | "123.", 100 | "123.345", 101 | "001 ", 102 | "001", 103 | 104 | // object 105 | "{}", 106 | `{"foo": 3}`, 107 | "{\"f\x00oo\": 3}", 108 | `{"foo\WW": 4}`, // unknown escape sequence 109 | `{"foo": 3 "bar"}`, 110 | ` {} `, 111 | strings.Repeat(`{"f":`, 1000) + "{}" + strings.Repeat("}", 1000), 112 | `{"foo": [{"":3, "4": "3"}, 4, {}], "t_wo": 1}`, 113 | ` {"foo": 2,"fudge}`, 114 | `{{"foo": }}`, 115 | `{{"foo": [{"":3, 4: "3"}, 4, "5": {4}]}, "t_wo": 1}`, 116 | "{", 117 | `{"foo"`, 118 | `{"foo",f}`, 119 | `{"foo",`, 120 | `{"foo"f`, 121 | "{}}", 122 | `{"foo": 234`, 123 | `{"foo\"bar": 123}`, 124 | "{\n\t\"foo\" \n\b\f: \t123}", 125 | 126 | // array 127 | `[]`, 128 | `[ 1, {}]`, 129 | strings.Repeat("[", 1000) + strings.Repeat("]", 1000), 130 | `[1, 2, 3, 4, {}]`, 131 | `[`, 132 | `[1,`, 133 | `[1a`, 134 | `[]]`, 135 | `[1 `, 136 | 137 | // boolean 138 | "true", 139 | " true ", 140 | "tree", 141 | "false", 142 | " true f", 143 | "fals", 144 | "falsee", 145 | 146 | // null 147 | "null ", 148 | " null ", 149 | " nulll ", 150 | "no", 151 | } 152 | for i, test := range tests { 153 | in := []byte(test) 154 | got := ValidateBytes(in) == nil 155 | exp := json.Valid(in) 156 | 157 | if got != exp { 158 | t.Errorf("#%d: %q got valid? %v, exp? %v", i, in, got, exp) 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /validate_timing_test.go: -------------------------------------------------------------------------------- 1 | package fastjson 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "testing" 7 | ) 8 | 9 | func BenchmarkValidate(b *testing.B) { 10 | b.Run("small", func(b *testing.B) { 11 | benchmarkValidate(b, smallFixture) 12 | }) 13 | b.Run("medium", func(b *testing.B) { 14 | benchmarkValidate(b, mediumFixture) 15 | }) 16 | b.Run("large", func(b *testing.B) { 17 | benchmarkValidate(b, largeFixture) 18 | }) 19 | b.Run("canada", func(b *testing.B) { 20 | benchmarkValidate(b, canadaFixture) 21 | }) 22 | b.Run("citm", func(b *testing.B) { 23 | benchmarkValidate(b, citmFixture) 24 | }) 25 | b.Run("twitter", func(b *testing.B) { 26 | benchmarkValidate(b, twitterFixture) 27 | }) 28 | } 29 | 30 | func benchmarkValidate(b *testing.B, s string) { 31 | b.Run("stdjson", func(b *testing.B) { 32 | benchmarkValidateStdJSON(b, s) 33 | }) 34 | b.Run("fastjson", func(b *testing.B) { 35 | benchmarkValidateFastJSON(b, s) 36 | }) 37 | } 38 | 39 | func benchmarkValidateStdJSON(b *testing.B, s string) { 40 | b.ReportAllocs() 41 | b.SetBytes(int64(len(s))) 42 | bb := s2b(s) 43 | b.RunParallel(func(pb *testing.PB) { 44 | for pb.Next() { 45 | if !json.Valid(bb) { 46 | panic("json.Valid unexpectedly returned false") 47 | } 48 | } 49 | }) 50 | } 51 | 52 | func benchmarkValidateFastJSON(b *testing.B, s string) { 53 | b.ReportAllocs() 54 | b.SetBytes(int64(len(s))) 55 | b.RunParallel(func(pb *testing.PB) { 56 | for pb.Next() { 57 | if err := Validate(s); err != nil { 58 | panic(fmt.Errorf("unexpected error: %s", err)) 59 | } 60 | } 61 | }) 62 | } 63 | --------------------------------------------------------------------------------