Initial QSfera import
This commit is contained in:
@@ -0,0 +1 @@
|
||||
tags
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.10.x
|
||||
|
||||
script:
|
||||
# build test for supported platforms
|
||||
- GOOS=linux go build
|
||||
- GOOS=darwin go build
|
||||
- GOOS=freebsd go build
|
||||
- GOOS=windows go build
|
||||
|
||||
# run tests on a standard platform
|
||||
- go test -v ./... -coverprofile=coverage.txt -covermode=atomic
|
||||
- go test -v ./... -race
|
||||
|
||||
after_success:
|
||||
# Upload coverage results to codecov.io
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 Aliaksandr Valialkin
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
+227
@@ -0,0 +1,227 @@
|
||||
[](https://travis-ci.org/valyala/fastjson)
|
||||
[](http://godoc.org/github.com/valyala/fastjson)
|
||||
[](https://goreportcard.com/report/github.com/valyala/fastjson)
|
||||
[](https://codecov.io/gh/valyala/fastjson)
|
||||
|
||||
# fastjson - fast JSON parser and validator for Go
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
* Fast. As usual, up to 15x faster than the standard [encoding/json](https://golang.org/pkg/encoding/json/).
|
||||
See [benchmarks](#benchmarks).
|
||||
* Parses arbitrary JSON without schema, reflection, struct magic and code generation
|
||||
contrary to [easyjson](https://github.com/mailru/easyjson).
|
||||
* Provides simple [API](http://godoc.org/github.com/valyala/fastjson).
|
||||
* Outperforms [jsonparser](https://github.com/buger/jsonparser) and [gjson](https://github.com/tidwall/gjson)
|
||||
when accessing multiple unrelated fields, since `fastjson` parses the input JSON only once.
|
||||
* Validates the parsed JSON unlike [jsonparser](https://github.com/buger/jsonparser)
|
||||
and [gjson](https://github.com/tidwall/gjson).
|
||||
* May quickly extract a part of the original JSON with `Value.Get(...).MarshalTo` and modify it
|
||||
with [Del](https://godoc.org/github.com/valyala/fastjson#Value.Del)
|
||||
and [Set](https://godoc.org/github.com/valyala/fastjson#Value.Set) functions.
|
||||
* May parse array containing values with distinct types (aka non-homogenous types).
|
||||
For instance, `fastjson` easily parses the following JSON array `[123, "foo", [456], {"k": "v"}, null]`.
|
||||
* `fastjson` preserves the original order of object items when calling
|
||||
[Object.Visit](https://godoc.org/github.com/valyala/fastjson#Object.Visit).
|
||||
|
||||
|
||||
## Known limitations
|
||||
|
||||
* Requies extra care to work with - references to certain objects recursively
|
||||
returned by [Parser](https://godoc.org/github.com/valyala/fastjson#Parser)
|
||||
must be released before the next call to [Parse](https://godoc.org/github.com/valyala/fastjson#Parser.Parse).
|
||||
Otherwise the program may work improperly. The same applies to objects returned by [Arena](https://godoc.org/github.com/valyala/fastjson#Arena).
|
||||
Adhere recommendations from [docs](https://godoc.org/github.com/valyala/fastjson).
|
||||
* Cannot parse JSON from `io.Reader`. There is [Scanner](https://godoc.org/github.com/valyala/fastjson#Scanner)
|
||||
for parsing stream of JSON values from a string.
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
One-liner accessing a single field:
|
||||
```go
|
||||
s := []byte(`{"foo": [123, "bar"]}`)
|
||||
fmt.Printf("foo.0=%d\n", fastjson.GetInt(s, "foo", "0"))
|
||||
|
||||
// Output:
|
||||
// foo.0=123
|
||||
```
|
||||
|
||||
Accessing multiple fields with error handling:
|
||||
```go
|
||||
var p fastjson.Parser
|
||||
v, err := p.Parse(`{
|
||||
"str": "bar",
|
||||
"int": 123,
|
||||
"float": 1.23,
|
||||
"bool": true,
|
||||
"arr": [1, "foo", {}]
|
||||
}`)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("foo=%s\n", v.GetStringBytes("str"))
|
||||
fmt.Printf("int=%d\n", v.GetInt("int"))
|
||||
fmt.Printf("float=%f\n", v.GetFloat64("float"))
|
||||
fmt.Printf("bool=%v\n", v.GetBool("bool"))
|
||||
fmt.Printf("arr.1=%s\n", v.GetStringBytes("arr", "1"))
|
||||
|
||||
// Output:
|
||||
// foo=bar
|
||||
// int=123
|
||||
// float=1.230000
|
||||
// bool=true
|
||||
// arr.1=foo
|
||||
```
|
||||
|
||||
See also [examples](https://godoc.org/github.com/valyala/fastjson#pkg-examples).
|
||||
|
||||
|
||||
## Security
|
||||
|
||||
* `fastjson` shouldn't crash or panic when parsing input strings specially crafted
|
||||
by an attacker. It must return error on invalid input JSON.
|
||||
* `fastjson` requires up to `sizeof(Value) * len(inputJSON)` bytes of memory
|
||||
for parsing `inputJSON` string. Limit the maximum size of the `inputJSON`
|
||||
before parsing it in order to limit the maximum memory usage.
|
||||
|
||||
|
||||
## Performance optimization tips
|
||||
|
||||
* Re-use [Parser](https://godoc.org/github.com/valyala/fastjson#Parser) and [Scanner](https://godoc.org/github.com/valyala/fastjson#Scanner)
|
||||
for parsing many JSONs. This reduces memory allocations overhead.
|
||||
[ParserPool](https://godoc.org/github.com/valyala/fastjson#ParserPool) may be useful in this case.
|
||||
* Prefer calling `Value.Get*` on the value returned from [Parser](https://godoc.org/github.com/valyala/fastjson#Parser)
|
||||
instead of calling `Get*` one-liners when multiple fields
|
||||
must be obtained from JSON, since each `Get*` one-liner re-parses
|
||||
the input JSON again.
|
||||
* Prefer calling once [Value.Get](https://godoc.org/github.com/valyala/fastjson#Value.Get)
|
||||
for common prefix paths and then calling `Value.Get*` on the returned value
|
||||
for distinct suffix paths.
|
||||
* Prefer iterating over array returned from [Value.GetArray](https://godoc.org/github.com/valyala/fastjson#Object.Visit)
|
||||
with a range loop instead of calling `Value.Get*` for each array item.
|
||||
|
||||
## Fuzzing
|
||||
Install [go-fuzz](https://github.com/dvyukov/go-fuzz) & optionally the go-fuzz-corpus.
|
||||
|
||||
```bash
|
||||
go get -u github.com/dvyukov/go-fuzz/go-fuzz github.com/dvyukov/go-fuzz/go-fuzz-build
|
||||
```
|
||||
|
||||
Build using `go-fuzz-build` and run `go-fuzz` with an optional corpus.
|
||||
|
||||
```bash
|
||||
mkdir -p workdir/corpus
|
||||
cp $GOPATH/src/github.com/dvyukov/go-fuzz-corpus/json/corpus/* workdir/corpus
|
||||
go-fuzz-build github.com/valyala/fastjson
|
||||
go-fuzz -bin=fastjson-fuzz.zip -workdir=workdir
|
||||
```
|
||||
|
||||
## Benchmarks
|
||||
|
||||
Go 1.12 has been used for benchmarking.
|
||||
|
||||
Legend:
|
||||
|
||||
* `small` - parse [small.json](testdata/small.json) (190 bytes).
|
||||
* `medium` - parse [medium.json](testdata/medium.json) (2.3KB).
|
||||
* `large` - parse [large.json](testdata/large.json) (28KB).
|
||||
* `canada` - parse [canada.json](testdata/canada.json) (2.2MB).
|
||||
* `citm` - parse [citm_catalog.json](testdata/citm_catalog.json) (1.7MB).
|
||||
* `twitter` - parse [twitter.json](testdata/twitter.json) (617KB).
|
||||
|
||||
* `stdjson-map` - parse into a `map[string]interface{}` using `encoding/json`.
|
||||
* `stdjson-struct` - parse into a struct containing
|
||||
a subset of fields of the parsed JSON, using `encoding/json`.
|
||||
* `stdjson-empty-struct` - parse into an empty struct using `encoding/json`.
|
||||
This is the fastest possible solution for `encoding/json`, may be used
|
||||
for json validation. See also benchmark results for json validation.
|
||||
* `fastjson` - parse using `fastjson` without fields access.
|
||||
* `fastjson-get` - parse using `fastjson` with fields access similar to `stdjson-struct`.
|
||||
|
||||
```
|
||||
$ GOMAXPROCS=1 go test github.com/valyala/fastjson -bench='Parse$'
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
pkg: github.com/valyala/fastjson
|
||||
BenchmarkParse/small/stdjson-map 200000 7305 ns/op 26.01 MB/s 960 B/op 51 allocs/op
|
||||
BenchmarkParse/small/stdjson-struct 500000 3431 ns/op 55.37 MB/s 224 B/op 4 allocs/op
|
||||
BenchmarkParse/small/stdjson-empty-struct 500000 2273 ns/op 83.58 MB/s 168 B/op 2 allocs/op
|
||||
BenchmarkParse/small/fastjson 5000000 347 ns/op 547.53 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkParse/small/fastjson-get 2000000 620 ns/op 306.39 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkParse/medium/stdjson-map 30000 40672 ns/op 57.26 MB/s 10196 B/op 208 allocs/op
|
||||
BenchmarkParse/medium/stdjson-struct 30000 47792 ns/op 48.73 MB/s 9174 B/op 258 allocs/op
|
||||
BenchmarkParse/medium/stdjson-empty-struct 100000 22096 ns/op 105.40 MB/s 280 B/op 5 allocs/op
|
||||
BenchmarkParse/medium/fastjson 500000 3025 ns/op 769.90 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkParse/medium/fastjson-get 500000 3211 ns/op 725.20 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkParse/large/stdjson-map 2000 614079 ns/op 45.79 MB/s 210734 B/op 2785 allocs/op
|
||||
BenchmarkParse/large/stdjson-struct 5000 298554 ns/op 94.18 MB/s 15616 B/op 353 allocs/op
|
||||
BenchmarkParse/large/stdjson-empty-struct 5000 268577 ns/op 104.69 MB/s 280 B/op 5 allocs/op
|
||||
BenchmarkParse/large/fastjson 50000 35210 ns/op 798.56 MB/s 5 B/op 0 allocs/op
|
||||
BenchmarkParse/large/fastjson-get 50000 35171 ns/op 799.46 MB/s 5 B/op 0 allocs/op
|
||||
BenchmarkParse/canada/stdjson-map 20 68147307 ns/op 33.03 MB/s 12260502 B/op 392539 allocs/op
|
||||
BenchmarkParse/canada/stdjson-struct 20 68044518 ns/op 33.08 MB/s 12260123 B/op 392534 allocs/op
|
||||
BenchmarkParse/canada/stdjson-empty-struct 100 17709250 ns/op 127.11 MB/s 280 B/op 5 allocs/op
|
||||
BenchmarkParse/canada/fastjson 300 4182404 ns/op 538.22 MB/s 254902 B/op 381 allocs/op
|
||||
BenchmarkParse/canada/fastjson-get 300 4274744 ns/op 526.60 MB/s 254902 B/op 381 allocs/op
|
||||
BenchmarkParse/citm/stdjson-map 50 27772612 ns/op 62.19 MB/s 5214163 B/op 95402 allocs/op
|
||||
BenchmarkParse/citm/stdjson-struct 100 14936191 ns/op 115.64 MB/s 1989 B/op 75 allocs/op
|
||||
BenchmarkParse/citm/stdjson-empty-struct 100 14946034 ns/op 115.56 MB/s 280 B/op 5 allocs/op
|
||||
BenchmarkParse/citm/fastjson 1000 1879714 ns/op 918.87 MB/s 17628 B/op 30 allocs/op
|
||||
BenchmarkParse/citm/fastjson-get 1000 1881598 ns/op 917.94 MB/s 17628 B/op 30 allocs/op
|
||||
BenchmarkParse/twitter/stdjson-map 100 11289146 ns/op 55.94 MB/s 2187878 B/op 31266 allocs/op
|
||||
BenchmarkParse/twitter/stdjson-struct 300 5779442 ns/op 109.27 MB/s 408 B/op 6 allocs/op
|
||||
BenchmarkParse/twitter/stdjson-empty-struct 300 5738504 ns/op 110.05 MB/s 408 B/op 6 allocs/op
|
||||
BenchmarkParse/twitter/fastjson 2000 774042 ns/op 815.86 MB/s 2541 B/op 2 allocs/op
|
||||
BenchmarkParse/twitter/fastjson-get 2000 777833 ns/op 811.89 MB/s 2541 B/op 2 allocs/op
|
||||
```
|
||||
|
||||
Benchmark results for json validation:
|
||||
|
||||
```
|
||||
$ GOMAXPROCS=1 go test github.com/valyala/fastjson -bench='Validate$'
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
pkg: github.com/valyala/fastjson
|
||||
BenchmarkValidate/small/stdjson 2000000 955 ns/op 198.83 MB/s 72 B/op 2 allocs/op
|
||||
BenchmarkValidate/small/fastjson 5000000 384 ns/op 493.60 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkValidate/medium/stdjson 200000 10799 ns/op 215.66 MB/s 184 B/op 5 allocs/op
|
||||
BenchmarkValidate/medium/fastjson 300000 3809 ns/op 611.30 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkValidate/large/stdjson 10000 133064 ns/op 211.31 MB/s 184 B/op 5 allocs/op
|
||||
BenchmarkValidate/large/fastjson 30000 45268 ns/op 621.14 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkValidate/canada/stdjson 200 8470904 ns/op 265.74 MB/s 184 B/op 5 allocs/op
|
||||
BenchmarkValidate/canada/fastjson 500 2973377 ns/op 757.07 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkValidate/citm/stdjson 200 7273172 ns/op 237.48 MB/s 184 B/op 5 allocs/op
|
||||
BenchmarkValidate/citm/fastjson 1000 1684430 ns/op 1025.39 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkValidate/twitter/stdjson 500 2849439 ns/op 221.63 MB/s 312 B/op 6 allocs/op
|
||||
BenchmarkValidate/twitter/fastjson 2000 1036796 ns/op 609.10 MB/s 0 B/op 0 allocs/op
|
||||
```
|
||||
|
||||
## FAQ
|
||||
|
||||
* Q: _There are a ton of other high-perf packages for JSON parsing in Go. Why creating yet another package?_
|
||||
A: Because other packages require either rigid JSON schema via struct magic
|
||||
and code generation or perform poorly when multiple unrelated fields
|
||||
must be obtained from the parsed JSON.
|
||||
Additionally, `fastjson` provides nicer [API](http://godoc.org/github.com/valyala/fastjson).
|
||||
|
||||
* Q: _What is the main purpose for `fastjson`?_
|
||||
A: High-perf JSON parsing for [RTB](https://www.iab.com/wp-content/uploads/2015/05/OpenRTB_API_Specification_Version_2_3_1.pdf)
|
||||
and other [JSON-RPC](https://en.wikipedia.org/wiki/JSON-RPC) services.
|
||||
|
||||
* Q: _Why fastjson doesn't provide fast marshaling (serialization)?_
|
||||
A: Actually it provides some sort of marshaling - see [Value.MarshalTo](https://godoc.org/github.com/valyala/fastjson#Value.MarshalTo).
|
||||
But I'd recommend using [quicktemplate](https://github.com/valyala/quicktemplate#use-cases)
|
||||
for high-performance JSON marshaling :)
|
||||
|
||||
* Q: _`fastjson` crashes my program!_
|
||||
A: There is high probability of improper use.
|
||||
* Make sure you don't hold references to objects recursively returned by `Parser` / `Scanner`
|
||||
beyond the next `Parser.Parse` / `Scanner.Next` call
|
||||
if such restriction is mentioned in [docs](https://github.com/valyala/fastjson/issues/new).
|
||||
* Make sure you don't access `fastjson` objects from concurrently running goroutines
|
||||
if such restriction is mentioned in [docs](https://github.com/valyala/fastjson/issues/new).
|
||||
* Build and run your program with [-race](https://golang.org/doc/articles/race_detector.html) flag.
|
||||
Make sure the race detector detects zero races.
|
||||
* If your program continue crashing after fixing issues mentioned above, [file a bug](https://github.com/valyala/fastjson/issues/new).
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
package fastjson
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Arena may be used for fast creation and re-use of Values.
|
||||
//
|
||||
// Typical Arena lifecycle:
|
||||
//
|
||||
// 1. Construct Values via the Arena and Value.Set* calls.
|
||||
// 2. Marshal the constructed Values with Value.MarshalTo call.
|
||||
// 3. Reset all the constructed Values at once by Arena.Reset call.
|
||||
// 4. Go to 1 and re-use the Arena.
|
||||
//
|
||||
// It is unsafe calling Arena methods from concurrent goroutines.
|
||||
// Use per-goroutine Arenas or ArenaPool instead.
|
||||
type Arena struct {
|
||||
b []byte
|
||||
c cache
|
||||
}
|
||||
|
||||
// Reset resets all the Values allocated by a.
|
||||
//
|
||||
// Values previously allocated by a cannot be used after the Reset call.
|
||||
func (a *Arena) Reset() {
|
||||
a.b = a.b[:0]
|
||||
a.c.reset()
|
||||
}
|
||||
|
||||
// NewObject returns new empty object value.
|
||||
//
|
||||
// New entries may be added to the returned object via Set call.
|
||||
//
|
||||
// The returned object is valid until Reset is called on a.
|
||||
func (a *Arena) NewObject() *Value {
|
||||
v := a.c.getValue()
|
||||
v.t = TypeObject
|
||||
v.o.reset()
|
||||
return v
|
||||
}
|
||||
|
||||
// NewArray returns new empty array value.
|
||||
//
|
||||
// New entries may be added to the returned array via Set* calls.
|
||||
//
|
||||
// The returned array is valid until Reset is called on a.
|
||||
func (a *Arena) NewArray() *Value {
|
||||
v := a.c.getValue()
|
||||
v.t = TypeArray
|
||||
v.a = v.a[:0]
|
||||
return v
|
||||
}
|
||||
|
||||
// NewString returns new string value containing s.
|
||||
//
|
||||
// The returned string is valid until Reset is called on a.
|
||||
func (a *Arena) NewString(s string) *Value {
|
||||
v := a.c.getValue()
|
||||
v.t = typeRawString
|
||||
bLen := len(a.b)
|
||||
a.b = escapeString(a.b, s)
|
||||
v.s = b2s(a.b[bLen+1 : len(a.b)-1])
|
||||
return v
|
||||
}
|
||||
|
||||
// NewStringBytes returns new string value containing b.
|
||||
//
|
||||
// The returned string is valid until Reset is called on a.
|
||||
func (a *Arena) NewStringBytes(b []byte) *Value {
|
||||
v := a.c.getValue()
|
||||
v.t = typeRawString
|
||||
bLen := len(a.b)
|
||||
a.b = escapeString(a.b, b2s(b))
|
||||
v.s = b2s(a.b[bLen+1 : len(a.b)-1])
|
||||
return v
|
||||
}
|
||||
|
||||
// NewNumberFloat64 returns new number value containing f.
|
||||
//
|
||||
// The returned number is valid until Reset is called on a.
|
||||
func (a *Arena) NewNumberFloat64(f float64) *Value {
|
||||
v := a.c.getValue()
|
||||
v.t = TypeNumber
|
||||
bLen := len(a.b)
|
||||
a.b = strconv.AppendFloat(a.b, f, 'g', -1, 64)
|
||||
v.s = b2s(a.b[bLen:])
|
||||
return v
|
||||
}
|
||||
|
||||
// NewNumberInt returns new number value containing n.
|
||||
//
|
||||
// The returned number is valid until Reset is called on a.
|
||||
func (a *Arena) NewNumberInt(n int) *Value {
|
||||
v := a.c.getValue()
|
||||
v.t = TypeNumber
|
||||
bLen := len(a.b)
|
||||
a.b = strconv.AppendInt(a.b, int64(n), 10)
|
||||
v.s = b2s(a.b[bLen:])
|
||||
return v
|
||||
}
|
||||
|
||||
// NewNumberString returns new number value containing s.
|
||||
//
|
||||
// The returned number is valid until Reset is called on a.
|
||||
func (a *Arena) NewNumberString(s string) *Value {
|
||||
v := a.c.getValue()
|
||||
v.t = TypeNumber
|
||||
v.s = s
|
||||
return v
|
||||
}
|
||||
|
||||
// NewNull returns null value.
|
||||
func (a *Arena) NewNull() *Value {
|
||||
return valueNull
|
||||
}
|
||||
|
||||
// NewTrue returns true value.
|
||||
func (a *Arena) NewTrue() *Value {
|
||||
return valueTrue
|
||||
}
|
||||
|
||||
// NewFalse return false value.
|
||||
func (a *Arena) NewFalse() *Value {
|
||||
return valueFalse
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
Package fastjson provides fast JSON parsing.
|
||||
|
||||
Arbitrary JSON may be parsed by fastjson without the need for creating structs
|
||||
or for generating go code. Just parse JSON and get the required fields with
|
||||
Get* functions.
|
||||
*/
|
||||
package fastjson
|
||||
+515
@@ -0,0 +1,515 @@
|
||||
package fastfloat
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ParseUint64BestEffort parses uint64 number s.
|
||||
//
|
||||
// It is equivalent to strconv.ParseUint(s, 10, 64), but is faster.
|
||||
//
|
||||
// 0 is returned if the number cannot be parsed.
|
||||
// See also ParseUint64, which returns parse error if the number cannot be parsed.
|
||||
func ParseUint64BestEffort(s string) uint64 {
|
||||
if len(s) == 0 {
|
||||
return 0
|
||||
}
|
||||
i := uint(0)
|
||||
d := uint64(0)
|
||||
j := i
|
||||
for i < uint(len(s)) {
|
||||
if s[i] >= '0' && s[i] <= '9' {
|
||||
d = d*10 + uint64(s[i]-'0')
|
||||
i++
|
||||
if i > 18 {
|
||||
// The integer part may be out of range for uint64.
|
||||
// Fall back to slow parsing.
|
||||
dd, err := strconv.ParseUint(s, 10, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return dd
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if i <= j {
|
||||
return 0
|
||||
}
|
||||
if i < uint(len(s)) {
|
||||
// Unparsed tail left.
|
||||
return 0
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// ParseUint64 parses uint64 from s.
|
||||
//
|
||||
// It is equivalent to strconv.ParseUint(s, 10, 64), but is faster.
|
||||
//
|
||||
// See also ParseUint64BestEffort.
|
||||
func ParseUint64(s string) (uint64, error) {
|
||||
if len(s) == 0 {
|
||||
return 0, fmt.Errorf("cannot parse uint64 from empty string")
|
||||
}
|
||||
i := uint(0)
|
||||
d := uint64(0)
|
||||
j := i
|
||||
for i < uint(len(s)) {
|
||||
if s[i] >= '0' && s[i] <= '9' {
|
||||
d = d*10 + uint64(s[i]-'0')
|
||||
i++
|
||||
if i > 18 {
|
||||
// The integer part may be out of range for uint64.
|
||||
// Fall back to slow parsing.
|
||||
dd, err := strconv.ParseUint(s, 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return dd, nil
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if i <= j {
|
||||
return 0, fmt.Errorf("cannot parse uint64 from %q", s)
|
||||
}
|
||||
if i < uint(len(s)) {
|
||||
// Unparsed tail left.
|
||||
return 0, fmt.Errorf("unparsed tail left after parsing uint64 from %q: %q", s, s[i:])
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// ParseInt64BestEffort parses int64 number s.
|
||||
//
|
||||
// It is equivalent to strconv.ParseInt(s, 10, 64), but is faster.
|
||||
//
|
||||
// 0 is returned if the number cannot be parsed.
|
||||
// See also ParseInt64, which returns parse error if the number cannot be parsed.
|
||||
func ParseInt64BestEffort(s string) int64 {
|
||||
if len(s) == 0 {
|
||||
return 0
|
||||
}
|
||||
i := uint(0)
|
||||
minus := s[0] == '-'
|
||||
if minus {
|
||||
i++
|
||||
if i >= uint(len(s)) {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
d := int64(0)
|
||||
j := i
|
||||
for i < uint(len(s)) {
|
||||
if s[i] >= '0' && s[i] <= '9' {
|
||||
d = d*10 + int64(s[i]-'0')
|
||||
i++
|
||||
if i > 18 {
|
||||
// The integer part may be out of range for int64.
|
||||
// Fall back to slow parsing.
|
||||
dd, err := strconv.ParseInt(s, 10, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return dd
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if i <= j {
|
||||
return 0
|
||||
}
|
||||
if i < uint(len(s)) {
|
||||
// Unparsed tail left.
|
||||
return 0
|
||||
}
|
||||
if minus {
|
||||
d = -d
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// ParseInt64 parses int64 number s.
|
||||
//
|
||||
// It is equivalent to strconv.ParseInt(s, 10, 64), but is faster.
|
||||
//
|
||||
// See also ParseInt64BestEffort.
|
||||
func ParseInt64(s string) (int64, error) {
|
||||
if len(s) == 0 {
|
||||
return 0, fmt.Errorf("cannot parse int64 from empty string")
|
||||
}
|
||||
i := uint(0)
|
||||
minus := s[0] == '-'
|
||||
if minus {
|
||||
i++
|
||||
if i >= uint(len(s)) {
|
||||
return 0, fmt.Errorf("cannot parse int64 from %q", s)
|
||||
}
|
||||
}
|
||||
|
||||
d := int64(0)
|
||||
j := i
|
||||
for i < uint(len(s)) {
|
||||
if s[i] >= '0' && s[i] <= '9' {
|
||||
d = d*10 + int64(s[i]-'0')
|
||||
i++
|
||||
if i > 18 {
|
||||
// The integer part may be out of range for int64.
|
||||
// Fall back to slow parsing.
|
||||
dd, err := strconv.ParseInt(s, 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return dd, nil
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if i <= j {
|
||||
return 0, fmt.Errorf("cannot parse int64 from %q", s)
|
||||
}
|
||||
if i < uint(len(s)) {
|
||||
// Unparsed tail left.
|
||||
return 0, fmt.Errorf("unparsed tail left after parsing int64 form %q: %q", s, s[i:])
|
||||
}
|
||||
if minus {
|
||||
d = -d
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// Exact powers of 10.
|
||||
//
|
||||
// This works faster than math.Pow10, since it avoids additional multiplication.
|
||||
var float64pow10 = [...]float64{
|
||||
1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16,
|
||||
}
|
||||
|
||||
// ParseBestEffort parses floating-point number s.
|
||||
//
|
||||
// It is equivalent to strconv.ParseFloat(s, 64), but is faster.
|
||||
//
|
||||
// 0 is returned if the number cannot be parsed.
|
||||
// See also Parse, which returns parse error if the number cannot be parsed.
|
||||
func ParseBestEffort(s string) float64 {
|
||||
if len(s) == 0 {
|
||||
return 0
|
||||
}
|
||||
i := uint(0)
|
||||
minus := s[0] == '-'
|
||||
if minus {
|
||||
i++
|
||||
if i >= uint(len(s)) {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// the integer part might be elided to remain compliant
|
||||
// with https://go.dev/ref/spec#Floating-point_literals
|
||||
if s[i] == '.' && (i+1 >= uint(len(s)) || s[i+1] < '0' || s[i+1] > '9') {
|
||||
return 0
|
||||
}
|
||||
|
||||
d := uint64(0)
|
||||
j := i
|
||||
for i < uint(len(s)) {
|
||||
if s[i] >= '0' && s[i] <= '9' {
|
||||
d = d*10 + uint64(s[i]-'0')
|
||||
i++
|
||||
if i > 18 {
|
||||
// The integer part may be out of range for uint64.
|
||||
// Fall back to slow parsing.
|
||||
f, err := strconv.ParseFloat(s, 64)
|
||||
if err != nil && !math.IsInf(f, 0) {
|
||||
return 0
|
||||
}
|
||||
return f
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if i <= j && s[i] != '.' {
|
||||
s = s[i:]
|
||||
if strings.HasPrefix(s, "+") {
|
||||
s = s[1:]
|
||||
}
|
||||
// "infinity" is needed for OpenMetrics support.
|
||||
// See https://github.com/OpenObservability/OpenMetrics/blob/master/OpenMetrics.md
|
||||
if strings.EqualFold(s, "inf") || strings.EqualFold(s, "infinity") {
|
||||
if minus {
|
||||
return -inf
|
||||
}
|
||||
return inf
|
||||
}
|
||||
if strings.EqualFold(s, "nan") {
|
||||
return nan
|
||||
}
|
||||
return 0
|
||||
}
|
||||
f := float64(d)
|
||||
if i >= uint(len(s)) {
|
||||
// Fast path - just integer.
|
||||
if minus {
|
||||
f = -f
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
if s[i] == '.' {
|
||||
// Parse fractional part.
|
||||
i++
|
||||
if i >= uint(len(s)) {
|
||||
// the fractional part may be elided to remain compliant
|
||||
// with https://go.dev/ref/spec#Floating-point_literals
|
||||
return f
|
||||
}
|
||||
k := i
|
||||
for i < uint(len(s)) {
|
||||
if s[i] >= '0' && s[i] <= '9' {
|
||||
d = d*10 + uint64(s[i]-'0')
|
||||
i++
|
||||
if i-j >= uint(len(float64pow10)) {
|
||||
// The mantissa is out of range. Fall back to standard parsing.
|
||||
f, err := strconv.ParseFloat(s, 64)
|
||||
if err != nil && !math.IsInf(f, 0) {
|
||||
return 0
|
||||
}
|
||||
return f
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if i < k {
|
||||
return 0
|
||||
}
|
||||
// Convert the entire mantissa to a float at once to avoid rounding errors.
|
||||
f = float64(d) / float64pow10[i-k]
|
||||
if i >= uint(len(s)) {
|
||||
// Fast path - parsed fractional number.
|
||||
if minus {
|
||||
f = -f
|
||||
}
|
||||
return f
|
||||
}
|
||||
}
|
||||
if s[i] == 'e' || s[i] == 'E' {
|
||||
// Parse exponent part.
|
||||
i++
|
||||
if i >= uint(len(s)) {
|
||||
return 0
|
||||
}
|
||||
expMinus := false
|
||||
if s[i] == '+' || s[i] == '-' {
|
||||
expMinus = s[i] == '-'
|
||||
i++
|
||||
if i >= uint(len(s)) {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
exp := int16(0)
|
||||
j := i
|
||||
for i < uint(len(s)) {
|
||||
if s[i] >= '0' && s[i] <= '9' {
|
||||
exp = exp*10 + int16(s[i]-'0')
|
||||
i++
|
||||
if exp > 300 {
|
||||
// The exponent may be too big for float64.
|
||||
// Fall back to standard parsing.
|
||||
f, err := strconv.ParseFloat(s, 64)
|
||||
if err != nil && !math.IsInf(f, 0) {
|
||||
return 0
|
||||
}
|
||||
return f
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if i <= j {
|
||||
return 0
|
||||
}
|
||||
if expMinus {
|
||||
exp = -exp
|
||||
}
|
||||
f *= math.Pow10(int(exp))
|
||||
if i >= uint(len(s)) {
|
||||
if minus {
|
||||
f = -f
|
||||
}
|
||||
return f
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Parse parses floating-point number s.
|
||||
//
|
||||
// It is equivalent to strconv.ParseFloat(s, 64), but is faster.
|
||||
//
|
||||
// See also ParseBestEffort.
|
||||
func Parse(s string) (float64, error) {
|
||||
if len(s) == 0 {
|
||||
return 0, fmt.Errorf("cannot parse float64 from empty string")
|
||||
}
|
||||
i := uint(0)
|
||||
minus := s[0] == '-'
|
||||
if minus {
|
||||
i++
|
||||
if i >= uint(len(s)) {
|
||||
return 0, fmt.Errorf("cannot parse float64 from %q", s)
|
||||
}
|
||||
}
|
||||
|
||||
// the integer part might be elided to remain compliant
|
||||
// with https://go.dev/ref/spec#Floating-point_literals
|
||||
if s[i] == '.' && (i+1 >= uint(len(s)) || s[i+1] < '0' || s[i+1] > '9') {
|
||||
return 0, fmt.Errorf("missing integer and fractional part in %q", s)
|
||||
}
|
||||
|
||||
d := uint64(0)
|
||||
j := i
|
||||
for i < uint(len(s)) {
|
||||
if s[i] >= '0' && s[i] <= '9' {
|
||||
d = d*10 + uint64(s[i]-'0')
|
||||
i++
|
||||
if i > 18 {
|
||||
// The integer part may be out of range for uint64.
|
||||
// Fall back to slow parsing.
|
||||
f, err := strconv.ParseFloat(s, 64)
|
||||
if err != nil && !math.IsInf(f, 0) {
|
||||
return 0, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if i <= j && s[i] != '.' {
|
||||
ss := s[i:]
|
||||
if strings.HasPrefix(ss, "+") {
|
||||
ss = ss[1:]
|
||||
}
|
||||
// "infinity" is needed for OpenMetrics support.
|
||||
// See https://github.com/OpenObservability/OpenMetrics/blob/master/OpenMetrics.md
|
||||
if strings.EqualFold(ss, "inf") || strings.EqualFold(ss, "infinity") {
|
||||
if minus {
|
||||
return -inf, nil
|
||||
}
|
||||
return inf, nil
|
||||
}
|
||||
if strings.EqualFold(ss, "nan") {
|
||||
return nan, nil
|
||||
}
|
||||
return 0, fmt.Errorf("unparsed tail left after parsing float64 from %q: %q", s, ss)
|
||||
}
|
||||
f := float64(d)
|
||||
if i >= uint(len(s)) {
|
||||
// Fast path - just integer.
|
||||
if minus {
|
||||
f = -f
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
if s[i] == '.' {
|
||||
// Parse fractional part.
|
||||
i++
|
||||
if i >= uint(len(s)) {
|
||||
// the fractional part might be elided to remain compliant
|
||||
// with https://go.dev/ref/spec#Floating-point_literals
|
||||
return f, nil
|
||||
}
|
||||
k := i
|
||||
for i < uint(len(s)) {
|
||||
if s[i] >= '0' && s[i] <= '9' {
|
||||
d = d*10 + uint64(s[i]-'0')
|
||||
i++
|
||||
if i-j >= uint(len(float64pow10)) {
|
||||
// The mantissa is out of range. Fall back to standard parsing.
|
||||
f, err := strconv.ParseFloat(s, 64)
|
||||
if err != nil && !math.IsInf(f, 0) {
|
||||
return 0, fmt.Errorf("cannot parse mantissa in %q: %s", s, err)
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if i < k {
|
||||
return 0, fmt.Errorf("cannot find mantissa in %q", s)
|
||||
}
|
||||
// Convert the entire mantissa to a float at once to avoid rounding errors.
|
||||
f = float64(d) / float64pow10[i-k]
|
||||
if i >= uint(len(s)) {
|
||||
// Fast path - parsed fractional number.
|
||||
if minus {
|
||||
f = -f
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
}
|
||||
if s[i] == 'e' || s[i] == 'E' {
|
||||
// Parse exponent part.
|
||||
i++
|
||||
if i >= uint(len(s)) {
|
||||
return 0, fmt.Errorf("cannot parse exponent in %q", s)
|
||||
}
|
||||
expMinus := false
|
||||
if s[i] == '+' || s[i] == '-' {
|
||||
expMinus = s[i] == '-'
|
||||
i++
|
||||
if i >= uint(len(s)) {
|
||||
return 0, fmt.Errorf("cannot parse exponent in %q", s)
|
||||
}
|
||||
}
|
||||
exp := int16(0)
|
||||
j := i
|
||||
for i < uint(len(s)) {
|
||||
if s[i] >= '0' && s[i] <= '9' {
|
||||
exp = exp*10 + int16(s[i]-'0')
|
||||
i++
|
||||
if exp > 300 {
|
||||
// The exponent may be too big for float64.
|
||||
// Fall back to standard parsing.
|
||||
f, err := strconv.ParseFloat(s, 64)
|
||||
if err != nil && !math.IsInf(f, 0) {
|
||||
return 0, fmt.Errorf("cannot parse exponent in %q: %s", s, err)
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if i <= j {
|
||||
return 0, fmt.Errorf("cannot parse exponent in %q", s)
|
||||
}
|
||||
if expMinus {
|
||||
exp = -exp
|
||||
}
|
||||
f *= math.Pow10(int(exp))
|
||||
if i >= uint(len(s)) {
|
||||
if minus {
|
||||
f = -f
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("cannot parse float64 from %q", s)
|
||||
}
|
||||
|
||||
var inf = math.Inf(1)
|
||||
var nan = math.NaN()
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
//go:build gofuzz
|
||||
// +build gofuzz
|
||||
|
||||
package fastjson
|
||||
|
||||
func Fuzz(data []byte) int {
|
||||
err := ValidateBytes(data)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
v := MustParseBytes(data)
|
||||
|
||||
dst := make([]byte, 0)
|
||||
dst = v.MarshalTo(dst)
|
||||
|
||||
err = ValidateBytes(dst)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
package fastjson
|
||||
|
||||
var handyPool ParserPool
|
||||
|
||||
// GetString returns string value for the field identified by keys path
|
||||
// in JSON data.
|
||||
//
|
||||
// Array indexes may be represented as decimal numbers in keys.
|
||||
//
|
||||
// An empty string is returned on error. Use Parser for proper error handling.
|
||||
//
|
||||
// Parser is faster for obtaining multiple fields from JSON.
|
||||
func GetString(data []byte, keys ...string) string {
|
||||
p := handyPool.Get()
|
||||
v, err := p.ParseBytes(data)
|
||||
if err != nil {
|
||||
handyPool.Put(p)
|
||||
return ""
|
||||
}
|
||||
sb := v.GetStringBytes(keys...)
|
||||
str := string(sb)
|
||||
handyPool.Put(p)
|
||||
return str
|
||||
}
|
||||
|
||||
// GetBytes returns string value for the field identified by keys path
|
||||
// in JSON data.
|
||||
//
|
||||
// Array indexes may be represented as decimal numbers in keys.
|
||||
//
|
||||
// nil is returned on error. Use Parser for proper error handling.
|
||||
//
|
||||
// Parser is faster for obtaining multiple fields from JSON.
|
||||
func GetBytes(data []byte, keys ...string) []byte {
|
||||
p := handyPool.Get()
|
||||
v, err := p.ParseBytes(data)
|
||||
if err != nil {
|
||||
handyPool.Put(p)
|
||||
return nil
|
||||
}
|
||||
sb := v.GetStringBytes(keys...)
|
||||
|
||||
// Make a copy of sb, since sb belongs to p.
|
||||
var b []byte
|
||||
if sb != nil {
|
||||
b = append(b, sb...)
|
||||
}
|
||||
|
||||
handyPool.Put(p)
|
||||
return b
|
||||
}
|
||||
|
||||
// GetInt returns int value for the field identified by keys path
|
||||
// in JSON data.
|
||||
//
|
||||
// Array indexes may be represented as decimal numbers in keys.
|
||||
//
|
||||
// 0 is returned on error. Use Parser for proper error handling.
|
||||
//
|
||||
// Parser is faster for obtaining multiple fields from JSON.
|
||||
func GetInt(data []byte, keys ...string) int {
|
||||
p := handyPool.Get()
|
||||
v, err := p.ParseBytes(data)
|
||||
if err != nil {
|
||||
handyPool.Put(p)
|
||||
return 0
|
||||
}
|
||||
n := v.GetInt(keys...)
|
||||
handyPool.Put(p)
|
||||
return n
|
||||
}
|
||||
|
||||
// GetFloat64 returns float64 value for the field identified by keys path
|
||||
// in JSON data.
|
||||
//
|
||||
// Array indexes may be represented as decimal numbers in keys.
|
||||
//
|
||||
// 0 is returned on error. Use Parser for proper error handling.
|
||||
//
|
||||
// Parser is faster for obtaining multiple fields from JSON.
|
||||
func GetFloat64(data []byte, keys ...string) float64 {
|
||||
p := handyPool.Get()
|
||||
v, err := p.ParseBytes(data)
|
||||
if err != nil {
|
||||
handyPool.Put(p)
|
||||
return 0
|
||||
}
|
||||
f := v.GetFloat64(keys...)
|
||||
handyPool.Put(p)
|
||||
return f
|
||||
}
|
||||
|
||||
// GetBool returns boolean value for the field identified by keys path
|
||||
// in JSON data.
|
||||
//
|
||||
// Array indexes may be represented as decimal numbers in keys.
|
||||
//
|
||||
// False is returned on error. Use Parser for proper error handling.
|
||||
//
|
||||
// Parser is faster for obtaining multiple fields from JSON.
|
||||
func GetBool(data []byte, keys ...string) bool {
|
||||
p := handyPool.Get()
|
||||
v, err := p.ParseBytes(data)
|
||||
if err != nil {
|
||||
handyPool.Put(p)
|
||||
return false
|
||||
}
|
||||
b := v.GetBool(keys...)
|
||||
handyPool.Put(p)
|
||||
return b
|
||||
}
|
||||
|
||||
// Exists returns true if the field identified by keys path exists in JSON data.
|
||||
//
|
||||
// Array indexes may be represented as decimal numbers in keys.
|
||||
//
|
||||
// False is returned on error. Use Parser for proper error handling.
|
||||
//
|
||||
// Parser is faster when multiple fields must be checked in the JSON.
|
||||
func Exists(data []byte, keys ...string) bool {
|
||||
p := handyPool.Get()
|
||||
v, err := p.ParseBytes(data)
|
||||
if err != nil {
|
||||
handyPool.Put(p)
|
||||
return false
|
||||
}
|
||||
ok := v.Exists(keys...)
|
||||
handyPool.Put(p)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Parse parses json string s.
|
||||
//
|
||||
// The function is slower than the Parser.Parse for re-used Parser.
|
||||
func Parse(s string) (*Value, error) {
|
||||
var p Parser
|
||||
return p.Parse(s)
|
||||
}
|
||||
|
||||
// MustParse parses json string s.
|
||||
//
|
||||
// The function panics if s cannot be parsed.
|
||||
// The function is slower than the Parser.Parse for re-used Parser.
|
||||
func MustParse(s string) *Value {
|
||||
v, err := Parse(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// ParseBytes parses b containing json.
|
||||
//
|
||||
// The function is slower than the Parser.ParseBytes for re-used Parser.
|
||||
func ParseBytes(b []byte) (*Value, error) {
|
||||
var p Parser
|
||||
return p.ParseBytes(b)
|
||||
}
|
||||
|
||||
// MustParseBytes parses b containing json.
|
||||
//
|
||||
// The function panics if b cannot be parsed.
|
||||
// The function is slower than the Parser.ParseBytes for re-used Parser.
|
||||
func MustParseBytes(b []byte) *Value {
|
||||
v, err := ParseBytes(b)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
+976
@@ -0,0 +1,976 @@
|
||||
package fastjson
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/valyala/fastjson/fastfloat"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf16"
|
||||
)
|
||||
|
||||
// Parser parses JSON.
|
||||
//
|
||||
// Parser may be re-used for subsequent parsing.
|
||||
//
|
||||
// Parser cannot be used from concurrent goroutines.
|
||||
// Use per-goroutine parsers or ParserPool instead.
|
||||
type Parser struct {
|
||||
// b contains working copy of the string to be parsed.
|
||||
b []byte
|
||||
|
||||
// c is a cache for json values.
|
||||
c cache
|
||||
}
|
||||
|
||||
// Parse parses s containing JSON.
|
||||
//
|
||||
// The returned value is valid until the next call to Parse*.
|
||||
//
|
||||
// Use Scanner if a stream of JSON values must be parsed.
|
||||
func (p *Parser) Parse(s string) (*Value, error) {
|
||||
s = skipWS(s)
|
||||
p.b = append(p.b[:0], s...)
|
||||
p.c.reset()
|
||||
|
||||
v, tail, err := parseValue(b2s(p.b), &p.c, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse JSON: %s; unparsed tail: %q", err, startEndString(tail))
|
||||
}
|
||||
tail = skipWS(tail)
|
||||
if len(tail) > 0 {
|
||||
return nil, fmt.Errorf("unexpected tail: %q", startEndString(tail))
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// ParseBytes parses b containing JSON.
|
||||
//
|
||||
// The returned Value is valid until the next call to Parse*.
|
||||
//
|
||||
// Use Scanner if a stream of JSON values must be parsed.
|
||||
func (p *Parser) ParseBytes(b []byte) (*Value, error) {
|
||||
return p.Parse(b2s(b))
|
||||
}
|
||||
|
||||
type cache struct {
|
||||
vs []Value
|
||||
}
|
||||
|
||||
func (c *cache) reset() {
|
||||
c.vs = c.vs[:0]
|
||||
}
|
||||
|
||||
func (c *cache) getValue() *Value {
|
||||
if cap(c.vs) > len(c.vs) {
|
||||
c.vs = c.vs[:len(c.vs)+1]
|
||||
} else {
|
||||
c.vs = append(c.vs, Value{})
|
||||
}
|
||||
// Do not reset the value, since the caller must properly init it.
|
||||
return &c.vs[len(c.vs)-1]
|
||||
}
|
||||
|
||||
func skipWS(s string) string {
|
||||
if len(s) == 0 || s[0] > 0x20 {
|
||||
// Fast path.
|
||||
return s
|
||||
}
|
||||
return skipWSSlow(s)
|
||||
}
|
||||
|
||||
func skipWSSlow(s string) string {
|
||||
if len(s) == 0 || s[0] != 0x20 && s[0] != 0x0A && s[0] != 0x09 && s[0] != 0x0D {
|
||||
return s
|
||||
}
|
||||
for i := 1; i < len(s); i++ {
|
||||
if s[i] != 0x20 && s[i] != 0x0A && s[i] != 0x09 && s[i] != 0x0D {
|
||||
return s[i:]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type kv struct {
|
||||
k string
|
||||
v *Value
|
||||
}
|
||||
|
||||
// MaxDepth is the maximum depth for nested JSON.
|
||||
const MaxDepth = 300
|
||||
|
||||
func parseValue(s string, c *cache, depth int) (*Value, string, error) {
|
||||
if len(s) == 0 {
|
||||
return nil, s, fmt.Errorf("cannot parse empty string")
|
||||
}
|
||||
depth++
|
||||
if depth > MaxDepth {
|
||||
return nil, s, fmt.Errorf("too big depth for the nested JSON; it exceeds %d", MaxDepth)
|
||||
}
|
||||
|
||||
if s[0] == '{' {
|
||||
v, tail, err := parseObject(s[1:], c, depth)
|
||||
if err != nil {
|
||||
return nil, tail, fmt.Errorf("cannot parse object: %s", err)
|
||||
}
|
||||
return v, tail, nil
|
||||
}
|
||||
if s[0] == '[' {
|
||||
v, tail, err := parseArray(s[1:], c, depth)
|
||||
if err != nil {
|
||||
return nil, tail, fmt.Errorf("cannot parse array: %s", err)
|
||||
}
|
||||
return v, tail, nil
|
||||
}
|
||||
if s[0] == '"' {
|
||||
ss, tail, err := parseRawString(s[1:])
|
||||
if err != nil {
|
||||
return nil, tail, fmt.Errorf("cannot parse string: %s", err)
|
||||
}
|
||||
v := c.getValue()
|
||||
v.t = typeRawString
|
||||
v.s = ss
|
||||
return v, tail, nil
|
||||
}
|
||||
if s[0] == 't' {
|
||||
if len(s) < len("true") || s[:len("true")] != "true" {
|
||||
return nil, s, fmt.Errorf("unexpected value found: %q", s)
|
||||
}
|
||||
return valueTrue, s[len("true"):], nil
|
||||
}
|
||||
if s[0] == 'f' {
|
||||
if len(s) < len("false") || s[:len("false")] != "false" {
|
||||
return nil, s, fmt.Errorf("unexpected value found: %q", s)
|
||||
}
|
||||
return valueFalse, s[len("false"):], nil
|
||||
}
|
||||
if s[0] == 'n' {
|
||||
if len(s) < len("null") || s[:len("null")] != "null" {
|
||||
// Try parsing NaN
|
||||
if len(s) >= 3 && strings.EqualFold(s[:3], "nan") {
|
||||
v := c.getValue()
|
||||
v.t = TypeNumber
|
||||
v.s = s[:3]
|
||||
return v, s[3:], nil
|
||||
}
|
||||
return nil, s, fmt.Errorf("unexpected value found: %q", s)
|
||||
}
|
||||
return valueNull, s[len("null"):], nil
|
||||
}
|
||||
|
||||
ns, tail, err := parseRawNumber(s)
|
||||
if err != nil {
|
||||
return nil, tail, fmt.Errorf("cannot parse number: %s", err)
|
||||
}
|
||||
v := c.getValue()
|
||||
v.t = TypeNumber
|
||||
v.s = ns
|
||||
return v, tail, nil
|
||||
}
|
||||
|
||||
func parseArray(s string, c *cache, depth int) (*Value, string, error) {
|
||||
s = skipWS(s)
|
||||
if len(s) == 0 {
|
||||
return nil, s, fmt.Errorf("missing ']'")
|
||||
}
|
||||
|
||||
if s[0] == ']' {
|
||||
v := c.getValue()
|
||||
v.t = TypeArray
|
||||
v.a = v.a[:0]
|
||||
return v, s[1:], nil
|
||||
}
|
||||
|
||||
a := c.getValue()
|
||||
a.t = TypeArray
|
||||
a.a = a.a[:0]
|
||||
for {
|
||||
var v *Value
|
||||
var err error
|
||||
|
||||
s = skipWS(s)
|
||||
v, s, err = parseValue(s, c, depth)
|
||||
if err != nil {
|
||||
return nil, s, fmt.Errorf("cannot parse array value: %s", err)
|
||||
}
|
||||
a.a = append(a.a, v)
|
||||
|
||||
s = skipWS(s)
|
||||
if len(s) == 0 {
|
||||
return nil, s, fmt.Errorf("unexpected end of array")
|
||||
}
|
||||
if s[0] == ',' {
|
||||
s = s[1:]
|
||||
continue
|
||||
}
|
||||
if s[0] == ']' {
|
||||
s = s[1:]
|
||||
return a, s, nil
|
||||
}
|
||||
return nil, s, fmt.Errorf("missing ',' after array value")
|
||||
}
|
||||
}
|
||||
|
||||
func parseObject(s string, c *cache, depth int) (*Value, string, error) {
|
||||
s = skipWS(s)
|
||||
if len(s) == 0 {
|
||||
return nil, s, fmt.Errorf("missing '}'")
|
||||
}
|
||||
|
||||
if s[0] == '}' {
|
||||
v := c.getValue()
|
||||
v.t = TypeObject
|
||||
v.o.reset()
|
||||
return v, s[1:], nil
|
||||
}
|
||||
|
||||
o := c.getValue()
|
||||
o.t = TypeObject
|
||||
o.o.reset()
|
||||
for {
|
||||
var err error
|
||||
kv := o.o.getKV()
|
||||
|
||||
// Parse key.
|
||||
s = skipWS(s)
|
||||
if len(s) == 0 || s[0] != '"' {
|
||||
return nil, s, fmt.Errorf(`cannot find opening '"" for object key`)
|
||||
}
|
||||
kv.k, s, err = parseRawKey(s[1:])
|
||||
if err != nil {
|
||||
return nil, s, fmt.Errorf("cannot parse object key: %s", err)
|
||||
}
|
||||
s = skipWS(s)
|
||||
if len(s) == 0 || s[0] != ':' {
|
||||
return nil, s, fmt.Errorf("missing ':' after object key")
|
||||
}
|
||||
s = s[1:]
|
||||
|
||||
// Parse value
|
||||
s = skipWS(s)
|
||||
kv.v, s, err = parseValue(s, c, depth)
|
||||
if err != nil {
|
||||
return nil, s, fmt.Errorf("cannot parse object value: %s", err)
|
||||
}
|
||||
s = skipWS(s)
|
||||
if len(s) == 0 {
|
||||
return nil, s, fmt.Errorf("unexpected end of object")
|
||||
}
|
||||
if s[0] == ',' {
|
||||
s = s[1:]
|
||||
continue
|
||||
}
|
||||
if s[0] == '}' {
|
||||
return o, s[1:], nil
|
||||
}
|
||||
return nil, s, fmt.Errorf("missing ',' after object value")
|
||||
}
|
||||
}
|
||||
|
||||
func escapeString(dst []byte, s string) []byte {
|
||||
if !hasSpecialChars(s) {
|
||||
// Fast path - nothing to escape.
|
||||
dst = append(dst, '"')
|
||||
dst = append(dst, s...)
|
||||
dst = append(dst, '"')
|
||||
return dst
|
||||
}
|
||||
|
||||
// Slow path.
|
||||
return strconv.AppendQuote(dst, s)
|
||||
}
|
||||
|
||||
func hasSpecialChars(s string) bool {
|
||||
if strings.IndexByte(s, '"') >= 0 || strings.IndexByte(s, '\\') >= 0 {
|
||||
return true
|
||||
}
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] < 0x20 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func unescapeStringBestEffort(s string) string {
|
||||
n := strings.IndexByte(s, '\\')
|
||||
if n < 0 {
|
||||
// Fast path - nothing to unescape.
|
||||
return s
|
||||
}
|
||||
|
||||
// Slow path - unescape string.
|
||||
b := s2b(s) // It is safe to do, since s points to a byte slice in Parser.b.
|
||||
b = b[:n]
|
||||
s = s[n+1:]
|
||||
for len(s) > 0 {
|
||||
ch := s[0]
|
||||
s = s[1:]
|
||||
switch ch {
|
||||
case '"':
|
||||
b = append(b, '"')
|
||||
case '\\':
|
||||
b = append(b, '\\')
|
||||
case '/':
|
||||
b = append(b, '/')
|
||||
case 'b':
|
||||
b = append(b, '\b')
|
||||
case 'f':
|
||||
b = append(b, '\f')
|
||||
case 'n':
|
||||
b = append(b, '\n')
|
||||
case 'r':
|
||||
b = append(b, '\r')
|
||||
case 't':
|
||||
b = append(b, '\t')
|
||||
case 'u':
|
||||
if len(s) < 4 {
|
||||
// Too short escape sequence. Just store it unchanged.
|
||||
b = append(b, "\\u"...)
|
||||
break
|
||||
}
|
||||
xs := s[:4]
|
||||
x, err := strconv.ParseUint(xs, 16, 16)
|
||||
if err != nil {
|
||||
// Invalid escape sequence. Just store it unchanged.
|
||||
b = append(b, "\\u"...)
|
||||
break
|
||||
}
|
||||
s = s[4:]
|
||||
if !utf16.IsSurrogate(rune(x)) {
|
||||
b = append(b, string(rune(x))...)
|
||||
break
|
||||
}
|
||||
|
||||
// Surrogate.
|
||||
// See https://en.wikipedia.org/wiki/Universal_Character_Set_characters#Surrogates
|
||||
if len(s) < 6 || s[0] != '\\' || s[1] != 'u' {
|
||||
b = append(b, "\\u"...)
|
||||
b = append(b, xs...)
|
||||
break
|
||||
}
|
||||
x1, err := strconv.ParseUint(s[2:6], 16, 16)
|
||||
if err != nil {
|
||||
b = append(b, "\\u"...)
|
||||
b = append(b, xs...)
|
||||
break
|
||||
}
|
||||
r := utf16.DecodeRune(rune(x), rune(x1))
|
||||
b = append(b, string(r)...)
|
||||
s = s[6:]
|
||||
default:
|
||||
// Unknown escape sequence. Just store it unchanged.
|
||||
b = append(b, '\\', ch)
|
||||
}
|
||||
n = strings.IndexByte(s, '\\')
|
||||
if n < 0 {
|
||||
b = append(b, s...)
|
||||
break
|
||||
}
|
||||
b = append(b, s[:n]...)
|
||||
s = s[n+1:]
|
||||
}
|
||||
return b2s(b)
|
||||
}
|
||||
|
||||
// parseRawKey is similar to parseRawString, but is optimized
|
||||
// for small-sized keys without escape sequences.
|
||||
func parseRawKey(s string) (string, string, error) {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] == '"' {
|
||||
// Fast path.
|
||||
return s[:i], s[i+1:], nil
|
||||
}
|
||||
if s[i] == '\\' {
|
||||
// Slow path.
|
||||
return parseRawString(s)
|
||||
}
|
||||
}
|
||||
return s, "", fmt.Errorf(`missing closing '"'`)
|
||||
}
|
||||
|
||||
func parseRawString(s string) (string, string, error) {
|
||||
n := strings.IndexByte(s, '"')
|
||||
if n < 0 {
|
||||
return s, "", fmt.Errorf(`missing closing '"'`)
|
||||
}
|
||||
if n == 0 || s[n-1] != '\\' {
|
||||
// Fast path. No escaped ".
|
||||
return s[:n], s[n+1:], nil
|
||||
}
|
||||
|
||||
// Slow path - possible escaped " found.
|
||||
ss := s
|
||||
for {
|
||||
i := n - 1
|
||||
for i > 0 && s[i-1] == '\\' {
|
||||
i--
|
||||
}
|
||||
if uint(n-i)%2 == 0 {
|
||||
return ss[:len(ss)-len(s)+n], s[n+1:], nil
|
||||
}
|
||||
s = s[n+1:]
|
||||
|
||||
n = strings.IndexByte(s, '"')
|
||||
if n < 0 {
|
||||
return ss, "", fmt.Errorf(`missing closing '"'`)
|
||||
}
|
||||
if n == 0 || s[n-1] != '\\' {
|
||||
return ss[:len(ss)-len(s)+n], s[n+1:], nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseRawNumber(s string) (string, string, error) {
|
||||
// The caller must ensure len(s) > 0
|
||||
|
||||
// Find the end of the number.
|
||||
for i := 0; i < len(s); i++ {
|
||||
ch := s[i]
|
||||
if (ch >= '0' && ch <= '9') || ch == '.' || ch == '-' || ch == 'e' || ch == 'E' || ch == '+' {
|
||||
continue
|
||||
}
|
||||
if i == 0 || i == 1 && (s[0] == '-' || s[0] == '+') {
|
||||
if len(s[i:]) >= 3 {
|
||||
xs := s[i : i+3]
|
||||
if strings.EqualFold(xs, "inf") || strings.EqualFold(xs, "nan") {
|
||||
return s[:i+3], s[i+3:], nil
|
||||
}
|
||||
}
|
||||
return "", s, fmt.Errorf("unexpected char: %q", s[:1])
|
||||
}
|
||||
ns := s[:i]
|
||||
s = s[i:]
|
||||
return ns, s, nil
|
||||
}
|
||||
return s, "", nil
|
||||
}
|
||||
|
||||
// Object represents JSON object.
|
||||
//
|
||||
// Object cannot be used from concurrent goroutines.
|
||||
// Use per-goroutine parsers or ParserPool instead.
|
||||
type Object struct {
|
||||
kvs []kv
|
||||
keysUnescaped bool
|
||||
}
|
||||
|
||||
func (o *Object) reset() {
|
||||
o.kvs = o.kvs[:0]
|
||||
o.keysUnescaped = false
|
||||
}
|
||||
|
||||
// MarshalTo appends marshaled o to dst and returns the result.
|
||||
func (o *Object) MarshalTo(dst []byte) []byte {
|
||||
dst = append(dst, '{')
|
||||
for i, kv := range o.kvs {
|
||||
if o.keysUnescaped {
|
||||
dst = escapeString(dst, kv.k)
|
||||
} else {
|
||||
dst = append(dst, '"')
|
||||
dst = append(dst, kv.k...)
|
||||
dst = append(dst, '"')
|
||||
}
|
||||
dst = append(dst, ':')
|
||||
dst = kv.v.MarshalTo(dst)
|
||||
if i != len(o.kvs)-1 {
|
||||
dst = append(dst, ',')
|
||||
}
|
||||
}
|
||||
dst = append(dst, '}')
|
||||
return dst
|
||||
}
|
||||
|
||||
// String returns string representation for the o.
|
||||
//
|
||||
// This function is for debugging purposes only. It isn't optimized for speed.
|
||||
// See MarshalTo instead.
|
||||
func (o *Object) String() string {
|
||||
b := o.MarshalTo(nil)
|
||||
// It is safe converting b to string without allocation, since b is no longer
|
||||
// reachable after this line.
|
||||
return b2s(b)
|
||||
}
|
||||
|
||||
func (o *Object) getKV() *kv {
|
||||
if cap(o.kvs) > len(o.kvs) {
|
||||
o.kvs = o.kvs[:len(o.kvs)+1]
|
||||
} else {
|
||||
o.kvs = append(o.kvs, kv{})
|
||||
}
|
||||
return &o.kvs[len(o.kvs)-1]
|
||||
}
|
||||
|
||||
func (o *Object) unescapeKeys() {
|
||||
if o.keysUnescaped {
|
||||
return
|
||||
}
|
||||
kvs := o.kvs
|
||||
for i := range kvs {
|
||||
kv := &kvs[i]
|
||||
kv.k = unescapeStringBestEffort(kv.k)
|
||||
}
|
||||
o.keysUnescaped = true
|
||||
}
|
||||
|
||||
// Len returns the number of items in the o.
|
||||
func (o *Object) Len() int {
|
||||
return len(o.kvs)
|
||||
}
|
||||
|
||||
// Get returns the value for the given key in the o.
|
||||
//
|
||||
// Returns nil if the value for the given key isn't found.
|
||||
//
|
||||
// The returned value is valid until Parse is called on the Parser returned o.
|
||||
func (o *Object) Get(key string) *Value {
|
||||
if !o.keysUnescaped && strings.IndexByte(key, '\\') < 0 {
|
||||
// Fast path - try searching for the key without object keys unescaping.
|
||||
for _, kv := range o.kvs {
|
||||
if kv.k == key {
|
||||
return kv.v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Slow path - unescape object keys.
|
||||
o.unescapeKeys()
|
||||
|
||||
for _, kv := range o.kvs {
|
||||
if kv.k == key {
|
||||
return kv.v
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Visit calls f for each item in the o in the original order
|
||||
// of the parsed JSON.
|
||||
//
|
||||
// f cannot hold key and/or v after returning.
|
||||
func (o *Object) Visit(f func(key []byte, v *Value)) {
|
||||
if o == nil {
|
||||
return
|
||||
}
|
||||
|
||||
o.unescapeKeys()
|
||||
|
||||
for _, kv := range o.kvs {
|
||||
f(s2b(kv.k), kv.v)
|
||||
}
|
||||
}
|
||||
|
||||
// Value represents any JSON value.
|
||||
//
|
||||
// Call Type in order to determine the actual type of the JSON value.
|
||||
//
|
||||
// Value cannot be used from concurrent goroutines.
|
||||
// Use per-goroutine parsers or ParserPool instead.
|
||||
type Value struct {
|
||||
o Object
|
||||
a []*Value
|
||||
s string
|
||||
t Type
|
||||
}
|
||||
|
||||
// MarshalTo appends marshaled v to dst and returns the result.
|
||||
func (v *Value) MarshalTo(dst []byte) []byte {
|
||||
switch v.t {
|
||||
case typeRawString:
|
||||
dst = append(dst, '"')
|
||||
dst = append(dst, v.s...)
|
||||
dst = append(dst, '"')
|
||||
return dst
|
||||
case TypeObject:
|
||||
return v.o.MarshalTo(dst)
|
||||
case TypeArray:
|
||||
dst = append(dst, '[')
|
||||
for i, vv := range v.a {
|
||||
dst = vv.MarshalTo(dst)
|
||||
if i != len(v.a)-1 {
|
||||
dst = append(dst, ',')
|
||||
}
|
||||
}
|
||||
dst = append(dst, ']')
|
||||
return dst
|
||||
case TypeString:
|
||||
return escapeString(dst, v.s)
|
||||
case TypeNumber:
|
||||
return append(dst, v.s...)
|
||||
case TypeTrue:
|
||||
return append(dst, "true"...)
|
||||
case TypeFalse:
|
||||
return append(dst, "false"...)
|
||||
case TypeNull:
|
||||
return append(dst, "null"...)
|
||||
default:
|
||||
panic(fmt.Errorf("BUG: unexpected Value type: %d", v.t))
|
||||
}
|
||||
}
|
||||
|
||||
// String returns string representation of the v.
|
||||
//
|
||||
// The function is for debugging purposes only. It isn't optimized for speed.
|
||||
// See MarshalTo instead.
|
||||
//
|
||||
// Don't confuse this function with StringBytes, which must be called
|
||||
// for obtaining the underlying JSON string for the v.
|
||||
func (v *Value) String() string {
|
||||
b := v.MarshalTo(nil)
|
||||
// It is safe converting b to string without allocation, since b is no longer
|
||||
// reachable after this line.
|
||||
return b2s(b)
|
||||
}
|
||||
|
||||
// Type represents JSON type.
|
||||
type Type int
|
||||
|
||||
const (
|
||||
// TypeNull is JSON null.
|
||||
TypeNull Type = 0
|
||||
|
||||
// TypeObject is JSON object type.
|
||||
TypeObject Type = 1
|
||||
|
||||
// TypeArray is JSON array type.
|
||||
TypeArray Type = 2
|
||||
|
||||
// TypeString is JSON string type.
|
||||
TypeString Type = 3
|
||||
|
||||
// TypeNumber is JSON number type.
|
||||
TypeNumber Type = 4
|
||||
|
||||
// TypeTrue is JSON true.
|
||||
TypeTrue Type = 5
|
||||
|
||||
// TypeFalse is JSON false.
|
||||
TypeFalse Type = 6
|
||||
|
||||
typeRawString Type = 7
|
||||
)
|
||||
|
||||
// String returns string representation of t.
|
||||
func (t Type) String() string {
|
||||
switch t {
|
||||
case TypeObject:
|
||||
return "object"
|
||||
case TypeArray:
|
||||
return "array"
|
||||
case TypeString:
|
||||
return "string"
|
||||
case TypeNumber:
|
||||
return "number"
|
||||
case TypeTrue:
|
||||
return "true"
|
||||
case TypeFalse:
|
||||
return "false"
|
||||
case TypeNull:
|
||||
return "null"
|
||||
|
||||
// typeRawString is skipped intentionally,
|
||||
// since it shouldn't be visible to user.
|
||||
default:
|
||||
panic(fmt.Errorf("BUG: unknown Value type: %d", t))
|
||||
}
|
||||
}
|
||||
|
||||
// Type returns the type of the v.
|
||||
func (v *Value) Type() Type {
|
||||
if v.t == typeRawString {
|
||||
v.s = unescapeStringBestEffort(v.s)
|
||||
v.t = TypeString
|
||||
}
|
||||
return v.t
|
||||
}
|
||||
|
||||
// Exists returns true if the field exists for the given keys path.
|
||||
//
|
||||
// Array indexes may be represented as decimal numbers in keys.
|
||||
func (v *Value) Exists(keys ...string) bool {
|
||||
v = v.Get(keys...)
|
||||
return v != nil
|
||||
}
|
||||
|
||||
// Get returns value by the given keys path.
|
||||
//
|
||||
// Array indexes may be represented as decimal numbers in keys.
|
||||
//
|
||||
// nil is returned for non-existing keys path.
|
||||
//
|
||||
// The returned value is valid until Parse is called on the Parser returned v.
|
||||
func (v *Value) Get(keys ...string) *Value {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
for _, key := range keys {
|
||||
if v.t == TypeObject {
|
||||
v = v.o.Get(key)
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
} else if v.t == TypeArray {
|
||||
n, err := strconv.Atoi(key)
|
||||
if err != nil || n < 0 || n >= len(v.a) {
|
||||
return nil
|
||||
}
|
||||
v = v.a[n]
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// GetObject returns object value by the given keys path.
|
||||
//
|
||||
// Array indexes may be represented as decimal numbers in keys.
|
||||
//
|
||||
// nil is returned for non-existing keys path or for invalid value type.
|
||||
//
|
||||
// The returned object is valid until Parse is called on the Parser returned v.
|
||||
func (v *Value) GetObject(keys ...string) *Object {
|
||||
v = v.Get(keys...)
|
||||
if v == nil || v.t != TypeObject {
|
||||
return nil
|
||||
}
|
||||
return &v.o
|
||||
}
|
||||
|
||||
// GetArray returns array value by the given keys path.
|
||||
//
|
||||
// Array indexes may be represented as decimal numbers in keys.
|
||||
//
|
||||
// nil is returned for non-existing keys path or for invalid value type.
|
||||
//
|
||||
// The returned array is valid until Parse is called on the Parser returned v.
|
||||
func (v *Value) GetArray(keys ...string) []*Value {
|
||||
v = v.Get(keys...)
|
||||
if v == nil || v.t != TypeArray {
|
||||
return nil
|
||||
}
|
||||
return v.a
|
||||
}
|
||||
|
||||
// GetFloat64 returns float64 value by the given keys path.
|
||||
//
|
||||
// Array indexes may be represented as decimal numbers in keys.
|
||||
//
|
||||
// 0 is returned for non-existing keys path or for invalid value type.
|
||||
func (v *Value) GetFloat64(keys ...string) float64 {
|
||||
v = v.Get(keys...)
|
||||
if v == nil || v.Type() != TypeNumber {
|
||||
return 0
|
||||
}
|
||||
return fastfloat.ParseBestEffort(v.s)
|
||||
}
|
||||
|
||||
// GetInt returns int value by the given keys path.
|
||||
//
|
||||
// Array indexes may be represented as decimal numbers in keys.
|
||||
//
|
||||
// 0 is returned for non-existing keys path or for invalid value type.
|
||||
func (v *Value) GetInt(keys ...string) int {
|
||||
v = v.Get(keys...)
|
||||
if v == nil || v.Type() != TypeNumber {
|
||||
return 0
|
||||
}
|
||||
n := fastfloat.ParseInt64BestEffort(v.s)
|
||||
nn := int(n)
|
||||
if int64(nn) != n {
|
||||
return 0
|
||||
}
|
||||
return nn
|
||||
}
|
||||
|
||||
// GetUint returns uint value by the given keys path.
|
||||
//
|
||||
// Array indexes may be represented as decimal numbers in keys.
|
||||
//
|
||||
// 0 is returned for non-existing keys path or for invalid value type.
|
||||
func (v *Value) GetUint(keys ...string) uint {
|
||||
v = v.Get(keys...)
|
||||
if v == nil || v.Type() != TypeNumber {
|
||||
return 0
|
||||
}
|
||||
n := fastfloat.ParseUint64BestEffort(v.s)
|
||||
nn := uint(n)
|
||||
if uint64(nn) != n {
|
||||
return 0
|
||||
}
|
||||
return nn
|
||||
}
|
||||
|
||||
// GetInt64 returns int64 value by the given keys path.
|
||||
//
|
||||
// Array indexes may be represented as decimal numbers in keys.
|
||||
//
|
||||
// 0 is returned for non-existing keys path or for invalid value type.
|
||||
func (v *Value) GetInt64(keys ...string) int64 {
|
||||
v = v.Get(keys...)
|
||||
if v == nil || v.Type() != TypeNumber {
|
||||
return 0
|
||||
}
|
||||
return fastfloat.ParseInt64BestEffort(v.s)
|
||||
}
|
||||
|
||||
// GetUint64 returns uint64 value by the given keys path.
|
||||
//
|
||||
// Array indexes may be represented as decimal numbers in keys.
|
||||
//
|
||||
// 0 is returned for non-existing keys path or for invalid value type.
|
||||
func (v *Value) GetUint64(keys ...string) uint64 {
|
||||
v = v.Get(keys...)
|
||||
if v == nil || v.Type() != TypeNumber {
|
||||
return 0
|
||||
}
|
||||
return fastfloat.ParseUint64BestEffort(v.s)
|
||||
}
|
||||
|
||||
// GetStringBytes returns string value by the given keys path.
|
||||
//
|
||||
// Array indexes may be represented as decimal numbers in keys.
|
||||
//
|
||||
// nil is returned for non-existing keys path or for invalid value type.
|
||||
//
|
||||
// The returned string is valid until Parse is called on the Parser returned v.
|
||||
func (v *Value) GetStringBytes(keys ...string) []byte {
|
||||
v = v.Get(keys...)
|
||||
if v == nil || v.Type() != TypeString {
|
||||
return nil
|
||||
}
|
||||
return s2b(v.s)
|
||||
}
|
||||
|
||||
// GetBool returns bool value by the given keys path.
|
||||
//
|
||||
// Array indexes may be represented as decimal numbers in keys.
|
||||
//
|
||||
// false is returned for non-existing keys path or for invalid value type.
|
||||
func (v *Value) GetBool(keys ...string) bool {
|
||||
v = v.Get(keys...)
|
||||
if v != nil && v.t == TypeTrue {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Object returns the underlying JSON object for the v.
|
||||
//
|
||||
// The returned object is valid until Parse is called on the Parser returned v.
|
||||
//
|
||||
// Use GetObject if you don't need error handling.
|
||||
func (v *Value) Object() (*Object, error) {
|
||||
if v.t != TypeObject {
|
||||
return nil, fmt.Errorf("value doesn't contain object; it contains %s", v.Type())
|
||||
}
|
||||
return &v.o, nil
|
||||
}
|
||||
|
||||
// Array returns the underlying JSON array for the v.
|
||||
//
|
||||
// The returned array is valid until Parse is called on the Parser returned v.
|
||||
//
|
||||
// Use GetArray if you don't need error handling.
|
||||
func (v *Value) Array() ([]*Value, error) {
|
||||
if v.t != TypeArray {
|
||||
return nil, fmt.Errorf("value doesn't contain array; it contains %s", v.Type())
|
||||
}
|
||||
return v.a, nil
|
||||
}
|
||||
|
||||
// StringBytes returns the underlying JSON string for the v.
|
||||
//
|
||||
// The returned string is valid until Parse is called on the Parser returned v.
|
||||
//
|
||||
// Use GetStringBytes if you don't need error handling.
|
||||
func (v *Value) StringBytes() ([]byte, error) {
|
||||
if v.Type() != TypeString {
|
||||
return nil, fmt.Errorf("value doesn't contain string; it contains %s", v.Type())
|
||||
}
|
||||
return s2b(v.s), nil
|
||||
}
|
||||
|
||||
// Float64 returns the underlying JSON number for the v.
|
||||
//
|
||||
// Use GetFloat64 if you don't need error handling.
|
||||
func (v *Value) Float64() (float64, error) {
|
||||
if v.Type() != TypeNumber {
|
||||
return 0, fmt.Errorf("value doesn't contain number; it contains %s", v.Type())
|
||||
}
|
||||
return fastfloat.Parse(v.s)
|
||||
}
|
||||
|
||||
// Int returns the underlying JSON int for the v.
|
||||
//
|
||||
// Use GetInt if you don't need error handling.
|
||||
func (v *Value) Int() (int, error) {
|
||||
if v.Type() != TypeNumber {
|
||||
return 0, fmt.Errorf("value doesn't contain number; it contains %s", v.Type())
|
||||
}
|
||||
n, err := fastfloat.ParseInt64(v.s)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
nn := int(n)
|
||||
if int64(nn) != n {
|
||||
return 0, fmt.Errorf("number %q doesn't fit int", v.s)
|
||||
}
|
||||
return nn, nil
|
||||
}
|
||||
|
||||
// Uint returns the underlying JSON uint for the v.
|
||||
//
|
||||
// Use GetInt if you don't need error handling.
|
||||
func (v *Value) Uint() (uint, error) {
|
||||
if v.Type() != TypeNumber {
|
||||
return 0, fmt.Errorf("value doesn't contain number; it contains %s", v.Type())
|
||||
}
|
||||
n, err := fastfloat.ParseUint64(v.s)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
nn := uint(n)
|
||||
if uint64(nn) != n {
|
||||
return 0, fmt.Errorf("number %q doesn't fit uint", v.s)
|
||||
}
|
||||
return nn, nil
|
||||
}
|
||||
|
||||
// Int64 returns the underlying JSON int64 for the v.
|
||||
//
|
||||
// Use GetInt64 if you don't need error handling.
|
||||
func (v *Value) Int64() (int64, error) {
|
||||
if v.Type() != TypeNumber {
|
||||
return 0, fmt.Errorf("value doesn't contain number; it contains %s", v.Type())
|
||||
}
|
||||
return fastfloat.ParseInt64(v.s)
|
||||
}
|
||||
|
||||
// Uint64 returns the underlying JSON uint64 for the v.
|
||||
//
|
||||
// Use GetInt64 if you don't need error handling.
|
||||
func (v *Value) Uint64() (uint64, error) {
|
||||
if v.Type() != TypeNumber {
|
||||
return 0, fmt.Errorf("value doesn't contain number; it contains %s", v.Type())
|
||||
}
|
||||
return fastfloat.ParseUint64(v.s)
|
||||
}
|
||||
|
||||
// Bool returns the underlying JSON bool for the v.
|
||||
//
|
||||
// Use GetBool if you don't need error handling.
|
||||
func (v *Value) Bool() (bool, error) {
|
||||
if v.t == TypeTrue {
|
||||
return true, nil
|
||||
}
|
||||
if v.t == TypeFalse {
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("value doesn't contain bool; it contains %s", v.Type())
|
||||
}
|
||||
|
||||
var (
|
||||
valueTrue = &Value{t: TypeTrue}
|
||||
valueFalse = &Value{t: TypeFalse}
|
||||
valueNull = &Value{t: TypeNull}
|
||||
)
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package fastjson
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ParserPool may be used for pooling Parsers for similarly typed JSONs.
|
||||
type ParserPool struct {
|
||||
pool sync.Pool
|
||||
}
|
||||
|
||||
// Get returns a Parser from pp.
|
||||
//
|
||||
// The Parser must be Put to pp after use.
|
||||
func (pp *ParserPool) Get() *Parser {
|
||||
v := pp.pool.Get()
|
||||
if v == nil {
|
||||
return &Parser{}
|
||||
}
|
||||
return v.(*Parser)
|
||||
}
|
||||
|
||||
// Put returns p to pp.
|
||||
//
|
||||
// p and objects recursively returned from p cannot be used after p
|
||||
// is put into pp.
|
||||
func (pp *ParserPool) Put(p *Parser) {
|
||||
pp.pool.Put(p)
|
||||
}
|
||||
|
||||
// ArenaPool may be used for pooling Arenas for similarly typed JSONs.
|
||||
type ArenaPool struct {
|
||||
pool sync.Pool
|
||||
}
|
||||
|
||||
// Get returns an Arena from ap.
|
||||
//
|
||||
// The Arena must be Put to ap after use.
|
||||
func (ap *ArenaPool) Get() *Arena {
|
||||
v := ap.pool.Get()
|
||||
if v == nil {
|
||||
return &Arena{}
|
||||
}
|
||||
return v.(*Arena)
|
||||
}
|
||||
|
||||
// Put returns a to ap.
|
||||
//
|
||||
// a and objects created by a cannot be used after a is put into ap.
|
||||
func (ap *ArenaPool) Put(a *Arena) {
|
||||
a.Reset()
|
||||
ap.pool.Put(a)
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
package fastjson
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Scanner scans a series of JSON values. Values may be delimited by whitespace.
|
||||
//
|
||||
// Scanner may parse JSON lines ( http://jsonlines.org/ ).
|
||||
//
|
||||
// Scanner may be re-used for subsequent parsing.
|
||||
//
|
||||
// Scanner cannot be used from concurrent goroutines.
|
||||
//
|
||||
// Use Parser for parsing only a single JSON value.
|
||||
type Scanner struct {
|
||||
// b contains a working copy of json value passed to Init.
|
||||
b []byte
|
||||
|
||||
// s points to the next JSON value to parse.
|
||||
s string
|
||||
|
||||
// err contains the last error.
|
||||
err error
|
||||
|
||||
// v contains the last parsed JSON value.
|
||||
v *Value
|
||||
|
||||
// c is used for caching JSON values.
|
||||
c cache
|
||||
}
|
||||
|
||||
// Init initializes sc with the given s.
|
||||
//
|
||||
// s may contain multiple JSON values, which may be delimited by whitespace.
|
||||
func (sc *Scanner) Init(s string) {
|
||||
sc.b = append(sc.b[:0], s...)
|
||||
sc.s = b2s(sc.b)
|
||||
sc.err = nil
|
||||
sc.v = nil
|
||||
}
|
||||
|
||||
// InitBytes initializes sc with the given b.
|
||||
//
|
||||
// b may contain multiple JSON values, which may be delimited by whitespace.
|
||||
func (sc *Scanner) InitBytes(b []byte) {
|
||||
sc.Init(b2s(b))
|
||||
}
|
||||
|
||||
// Next parses the next JSON value from s passed to Init.
|
||||
//
|
||||
// Returns true on success. The parsed value is available via Value call.
|
||||
//
|
||||
// Returns false either on error or on the end of s.
|
||||
// Call Error in order to determine the cause of the returned false.
|
||||
func (sc *Scanner) Next() bool {
|
||||
if sc.err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
sc.s = skipWS(sc.s)
|
||||
if len(sc.s) == 0 {
|
||||
sc.err = errEOF
|
||||
return false
|
||||
}
|
||||
|
||||
sc.c.reset()
|
||||
v, tail, err := parseValue(sc.s, &sc.c, 0)
|
||||
if err != nil {
|
||||
sc.err = err
|
||||
return false
|
||||
}
|
||||
|
||||
sc.s = tail
|
||||
sc.v = v
|
||||
return true
|
||||
}
|
||||
|
||||
// Error returns the last error.
|
||||
func (sc *Scanner) Error() error {
|
||||
if sc.err == errEOF {
|
||||
return nil
|
||||
}
|
||||
return sc.err
|
||||
}
|
||||
|
||||
// Value returns the last parsed value.
|
||||
//
|
||||
// The value is valid until the Next call.
|
||||
func (sc *Scanner) Value() *Value {
|
||||
return sc.v
|
||||
}
|
||||
|
||||
var errEOF = errors.New("end of s")
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
package fastjson
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Del deletes the entry with the given key from o.
|
||||
func (o *Object) Del(key string) {
|
||||
if o == nil {
|
||||
return
|
||||
}
|
||||
if !o.keysUnescaped && strings.IndexByte(key, '\\') < 0 {
|
||||
// Fast path - try searching for the key without object keys unescaping.
|
||||
for i, kv := range o.kvs {
|
||||
if kv.k == key {
|
||||
o.kvs = append(o.kvs[:i], o.kvs[i+1:]...)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Slow path - unescape object keys before item search.
|
||||
o.unescapeKeys()
|
||||
|
||||
for i, kv := range o.kvs {
|
||||
if kv.k == key {
|
||||
o.kvs = append(o.kvs[:i], o.kvs[i+1:]...)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Del deletes the entry with the given key from array or object v.
|
||||
func (v *Value) Del(key string) {
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
if v.t == TypeObject {
|
||||
v.o.Del(key)
|
||||
return
|
||||
}
|
||||
if v.t == TypeArray {
|
||||
n, err := strconv.Atoi(key)
|
||||
if err != nil || n < 0 || n >= len(v.a) {
|
||||
return
|
||||
}
|
||||
v.a = append(v.a[:n], v.a[n+1:]...)
|
||||
}
|
||||
}
|
||||
|
||||
// Set sets (key, value) entry in the o.
|
||||
//
|
||||
// The value must be unchanged during o lifetime.
|
||||
func (o *Object) Set(key string, value *Value) {
|
||||
if o == nil {
|
||||
return
|
||||
}
|
||||
if value == nil {
|
||||
value = valueNull
|
||||
}
|
||||
o.unescapeKeys()
|
||||
|
||||
// Try substituting already existing entry with the given key.
|
||||
for i := range o.kvs {
|
||||
kv := &o.kvs[i]
|
||||
if kv.k == key {
|
||||
kv.v = value
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Add new entry.
|
||||
kv := o.getKV()
|
||||
kv.k = key
|
||||
kv.v = value
|
||||
}
|
||||
|
||||
// Set sets (key, value) entry in the array or object v.
|
||||
//
|
||||
// The value must be unchanged during v lifetime.
|
||||
func (v *Value) Set(key string, value *Value) {
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
if v.t == TypeObject {
|
||||
v.o.Set(key, value)
|
||||
return
|
||||
}
|
||||
if v.t == TypeArray {
|
||||
idx, err := strconv.Atoi(key)
|
||||
if err != nil || idx < 0 {
|
||||
return
|
||||
}
|
||||
v.SetArrayItem(idx, value)
|
||||
}
|
||||
}
|
||||
|
||||
// SetArrayItem sets the value in the array v at idx position.
|
||||
//
|
||||
// The value must be unchanged during v lifetime.
|
||||
func (v *Value) SetArrayItem(idx int, value *Value) {
|
||||
if v == nil || v.t != TypeArray {
|
||||
return
|
||||
}
|
||||
for idx >= len(v.a) {
|
||||
v.a = append(v.a, valueNull)
|
||||
}
|
||||
if value == nil {
|
||||
value = valueNull
|
||||
}
|
||||
v.a[idx] = value
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package fastjson
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func b2s(b []byte) string {
|
||||
return *(*string)(unsafe.Pointer(&b))
|
||||
}
|
||||
|
||||
func s2b(s string) (b []byte) {
|
||||
strh := (*reflect.StringHeader)(unsafe.Pointer(&s))
|
||||
sh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
|
||||
sh.Data = strh.Data
|
||||
sh.Len = strh.Len
|
||||
sh.Cap = strh.Len
|
||||
return b
|
||||
}
|
||||
|
||||
const maxStartEndStringLen = 80
|
||||
|
||||
func startEndString(s string) string {
|
||||
if len(s) <= maxStartEndStringLen {
|
||||
return s
|
||||
}
|
||||
start := s[:40]
|
||||
end := s[len(s)-40:]
|
||||
return start + "..." + end
|
||||
}
|
||||
+308
@@ -0,0 +1,308 @@
|
||||
package fastjson
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Validate validates JSON s.
|
||||
func Validate(s string) error {
|
||||
s = skipWS(s)
|
||||
|
||||
tail, err := validateValue(s)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot parse JSON: %s; unparsed tail: %q", err, startEndString(tail))
|
||||
}
|
||||
tail = skipWS(tail)
|
||||
if len(tail) > 0 {
|
||||
return fmt.Errorf("unexpected tail: %q", startEndString(tail))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateBytes validates JSON b.
|
||||
func ValidateBytes(b []byte) error {
|
||||
return Validate(b2s(b))
|
||||
}
|
||||
|
||||
func validateValue(s string) (string, error) {
|
||||
if len(s) == 0 {
|
||||
return s, fmt.Errorf("cannot parse empty string")
|
||||
}
|
||||
|
||||
if s[0] == '{' {
|
||||
tail, err := validateObject(s[1:])
|
||||
if err != nil {
|
||||
return tail, fmt.Errorf("cannot parse object: %s", err)
|
||||
}
|
||||
return tail, nil
|
||||
}
|
||||
if s[0] == '[' {
|
||||
tail, err := validateArray(s[1:])
|
||||
if err != nil {
|
||||
return tail, fmt.Errorf("cannot parse array: %s", err)
|
||||
}
|
||||
return tail, nil
|
||||
}
|
||||
if s[0] == '"' {
|
||||
sv, tail, err := validateString(s[1:])
|
||||
if err != nil {
|
||||
return tail, fmt.Errorf("cannot parse string: %s", err)
|
||||
}
|
||||
// Scan the string for control chars.
|
||||
for i := 0; i < len(sv); i++ {
|
||||
if sv[i] < 0x20 {
|
||||
return tail, fmt.Errorf("string cannot contain control char 0x%02X", sv[i])
|
||||
}
|
||||
}
|
||||
return tail, nil
|
||||
}
|
||||
if s[0] == 't' {
|
||||
if len(s) < len("true") || s[:len("true")] != "true" {
|
||||
return s, fmt.Errorf("unexpected value found: %q", s)
|
||||
}
|
||||
return s[len("true"):], nil
|
||||
}
|
||||
if s[0] == 'f' {
|
||||
if len(s) < len("false") || s[:len("false")] != "false" {
|
||||
return s, fmt.Errorf("unexpected value found: %q", s)
|
||||
}
|
||||
return s[len("false"):], nil
|
||||
}
|
||||
if s[0] == 'n' {
|
||||
if len(s) < len("null") || s[:len("null")] != "null" {
|
||||
return s, fmt.Errorf("unexpected value found: %q", s)
|
||||
}
|
||||
return s[len("null"):], nil
|
||||
}
|
||||
|
||||
tail, err := validateNumber(s)
|
||||
if err != nil {
|
||||
return tail, fmt.Errorf("cannot parse number: %s", err)
|
||||
}
|
||||
return tail, nil
|
||||
}
|
||||
|
||||
func validateArray(s string) (string, error) {
|
||||
s = skipWS(s)
|
||||
if len(s) == 0 {
|
||||
return s, fmt.Errorf("missing ']'")
|
||||
}
|
||||
if s[0] == ']' {
|
||||
return s[1:], nil
|
||||
}
|
||||
|
||||
for {
|
||||
var err error
|
||||
|
||||
s = skipWS(s)
|
||||
s, err = validateValue(s)
|
||||
if err != nil {
|
||||
return s, fmt.Errorf("cannot parse array value: %s", err)
|
||||
}
|
||||
|
||||
s = skipWS(s)
|
||||
if len(s) == 0 {
|
||||
return s, fmt.Errorf("unexpected end of array")
|
||||
}
|
||||
if s[0] == ',' {
|
||||
s = s[1:]
|
||||
continue
|
||||
}
|
||||
if s[0] == ']' {
|
||||
s = s[1:]
|
||||
return s, nil
|
||||
}
|
||||
return s, fmt.Errorf("missing ',' after array value")
|
||||
}
|
||||
}
|
||||
|
||||
func validateObject(s string) (string, error) {
|
||||
s = skipWS(s)
|
||||
if len(s) == 0 {
|
||||
return s, fmt.Errorf("missing '}'")
|
||||
}
|
||||
if s[0] == '}' {
|
||||
return s[1:], nil
|
||||
}
|
||||
|
||||
for {
|
||||
var err error
|
||||
|
||||
// Parse key.
|
||||
s = skipWS(s)
|
||||
if len(s) == 0 || s[0] != '"' {
|
||||
return s, fmt.Errorf(`cannot find opening '"" for object key`)
|
||||
}
|
||||
|
||||
var key string
|
||||
key, s, err = validateKey(s[1:])
|
||||
if err != nil {
|
||||
return s, fmt.Errorf("cannot parse object key: %s", err)
|
||||
}
|
||||
// Scan the key for control chars.
|
||||
for i := 0; i < len(key); i++ {
|
||||
if key[i] < 0x20 {
|
||||
return s, fmt.Errorf("object key cannot contain control char 0x%02X", key[i])
|
||||
}
|
||||
}
|
||||
s = skipWS(s)
|
||||
if len(s) == 0 || s[0] != ':' {
|
||||
return s, fmt.Errorf("missing ':' after object key")
|
||||
}
|
||||
s = s[1:]
|
||||
|
||||
// Parse value
|
||||
s = skipWS(s)
|
||||
s, err = validateValue(s)
|
||||
if err != nil {
|
||||
return s, fmt.Errorf("cannot parse object value: %s", err)
|
||||
}
|
||||
s = skipWS(s)
|
||||
if len(s) == 0 {
|
||||
return s, fmt.Errorf("unexpected end of object")
|
||||
}
|
||||
if s[0] == ',' {
|
||||
s = s[1:]
|
||||
continue
|
||||
}
|
||||
if s[0] == '}' {
|
||||
return s[1:], nil
|
||||
}
|
||||
return s, fmt.Errorf("missing ',' after object value")
|
||||
}
|
||||
}
|
||||
|
||||
// validateKey is similar to validateString, but is optimized
|
||||
// for typical object keys, which are quite small and have no escape sequences.
|
||||
func validateKey(s string) (string, string, error) {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] == '"' {
|
||||
// Fast path - the key doesn't contain escape sequences.
|
||||
return s[:i], s[i+1:], nil
|
||||
}
|
||||
if s[i] == '\\' {
|
||||
// Slow path - the key contains escape sequences.
|
||||
return validateString(s)
|
||||
}
|
||||
}
|
||||
return "", s, fmt.Errorf(`missing closing '"'`)
|
||||
}
|
||||
|
||||
func validateString(s string) (string, string, error) {
|
||||
// Try fast path - a string without escape sequences.
|
||||
if n := strings.IndexByte(s, '"'); n >= 0 && strings.IndexByte(s[:n], '\\') < 0 {
|
||||
return s[:n], s[n+1:], nil
|
||||
}
|
||||
|
||||
// Slow path - escape sequences are present.
|
||||
rs, tail, err := parseRawString(s)
|
||||
if err != nil {
|
||||
return rs, tail, err
|
||||
}
|
||||
for {
|
||||
n := strings.IndexByte(rs, '\\')
|
||||
if n < 0 {
|
||||
return rs, tail, nil
|
||||
}
|
||||
n++
|
||||
if n >= len(rs) {
|
||||
return rs, tail, fmt.Errorf("BUG: parseRawString returned invalid string with trailing backslash: %q", rs)
|
||||
}
|
||||
ch := rs[n]
|
||||
rs = rs[n+1:]
|
||||
switch ch {
|
||||
case '"', '\\', '/', 'b', 'f', 'n', 'r', 't':
|
||||
// Valid escape sequences - see http://json.org/
|
||||
break
|
||||
case 'u':
|
||||
if len(rs) < 4 {
|
||||
return rs, tail, fmt.Errorf(`too short escape sequence: \u%s`, rs)
|
||||
}
|
||||
xs := rs[:4]
|
||||
_, err := strconv.ParseUint(xs, 16, 16)
|
||||
if err != nil {
|
||||
return rs, tail, fmt.Errorf(`invalid escape sequence \u%s: %s`, xs, err)
|
||||
}
|
||||
rs = rs[4:]
|
||||
default:
|
||||
return rs, tail, fmt.Errorf(`unknown escape sequence \%c`, ch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func validateNumber(s string) (string, error) {
|
||||
if len(s) == 0 {
|
||||
return s, fmt.Errorf("zero-length number")
|
||||
}
|
||||
if s[0] == '-' {
|
||||
s = s[1:]
|
||||
if len(s) == 0 {
|
||||
return s, fmt.Errorf("missing number after minus")
|
||||
}
|
||||
}
|
||||
i := 0
|
||||
for i < len(s) {
|
||||
if s[i] < '0' || s[i] > '9' {
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
if i <= 0 {
|
||||
return s, fmt.Errorf("expecting 0..9 digit, got %c", s[0])
|
||||
}
|
||||
if s[0] == '0' && i != 1 {
|
||||
return s, fmt.Errorf("unexpected number starting from 0")
|
||||
}
|
||||
if i >= len(s) {
|
||||
return "", nil
|
||||
}
|
||||
if s[i] == '.' {
|
||||
// Validate fractional part
|
||||
s = s[i+1:]
|
||||
if len(s) == 0 {
|
||||
return s, fmt.Errorf("missing fractional part")
|
||||
}
|
||||
i = 0
|
||||
for i < len(s) {
|
||||
if s[i] < '0' || s[i] > '9' {
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
if i == 0 {
|
||||
return s, fmt.Errorf("expecting 0..9 digit in fractional part, got %c", s[0])
|
||||
}
|
||||
if i >= len(s) {
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
if s[i] == 'e' || s[i] == 'E' {
|
||||
// Validate exponent part
|
||||
s = s[i+1:]
|
||||
if len(s) == 0 {
|
||||
return s, fmt.Errorf("missing exponent part")
|
||||
}
|
||||
if s[0] == '-' || s[0] == '+' {
|
||||
s = s[1:]
|
||||
if len(s) == 0 {
|
||||
return s, fmt.Errorf("missing exponent part")
|
||||
}
|
||||
}
|
||||
i = 0
|
||||
for i < len(s) {
|
||||
if s[i] < '0' || s[i] > '9' {
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
if i == 0 {
|
||||
return s, fmt.Errorf("expecting 0..9 digit in exponent part, got %c", s[0])
|
||||
}
|
||||
if i >= len(s) {
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
return s[i:], nil
|
||||
}
|
||||
Reference in New Issue
Block a user