19 Commits

Author SHA1 Message Date
Josh Baker
01f00f1296 Merge pull request #66 from Hexilee/master
Result.Bool() can parse 'false' as false
2018-02-23 08:48:56 -07:00
Hexilee
9fa9086994 Result.Bool() can parse 'false' as false 2018-02-23 20:25:48 +08:00
Josh Baker
aff9dcea3c uncomment function 2018-02-18 10:09:03 -07:00
Josh Baker
c2e370e1b3 comment on Valid 2018-02-18 10:05:23 -07:00
Josh Baker
df0b0cce42 section on json validation 2018-02-18 09:49:01 -07:00
Josh Baker
46c712f1ce update playground badge 2018-02-10 11:31:49 -07:00
Josh Baker
54d114d487 minor updates 2018-02-10 09:30:30 -07:00
Josh Baker
6d43584b26 mention related projects 2018-02-10 09:10:40 -07:00
Josh Baker
4b8623b9d6 minor edits 2018-02-09 19:51:56 -07:00
Josh Baker
749b396bb9 Update README.md 2018-02-09 16:09:45 -07:00
Josh Baker
077965694f changed badge color 2018-02-09 16:08:32 -07:00
Josh Baker
b9953e2865 Update README.md 2018-02-09 16:04:47 -07:00
Josh Baker
a2f35b522e Added support for JSON Lines
Added support for JSON Lines (http://jsonlines.org) using the `..` prefix.
Which when specified, treats the multi-lined document as an array.

For example:

```
{"name": "Gilbert", "age": 61}
{"name": "Alexa", "age": 34}
{"name": "May", "age": 57}
{"name": "Deloise", "age": 44}
```

```
..#                   >> 4
..1                   >> {"name": "Alexa", "age": 34}
..3                   >> {"name": "Deloise", "age": 44}
..#.name              >> ["Gilbert","Alexa","May","Deloise"]
..#[name="May"].age   >> 57
```

Closes #60
2018-02-09 15:42:42 -07:00
Josh Baker
5fe9078c47 mention large integer support 2018-02-08 12:21:27 -07:00
Josh Baker
bff3f07fe7 mention large integer support 2018-02-08 12:21:08 -07:00
Josh Baker
87033efcae array query mismatch, fixes #58 2018-01-23 05:45:05 -07:00
Josh Baker
5cd723d566 simplify getmanybytes 2017-12-22 07:19:48 -07:00
Josh Baker
62ee2064df remove commented line 2017-12-22 07:17:19 -07:00
Josh Baker
09641abb33 update tagline 2017-12-22 07:16:29 -07:00
3 changed files with 195 additions and 34 deletions

View File

@@ -5,15 +5,17 @@
<br>
<a href="https://travis-ci.org/tidwall/gjson"><img src="https://img.shields.io/travis/tidwall/gjson.svg?style=flat-square" alt="Build Status"></a>
<a href="https://godoc.org/github.com/tidwall/gjson"><img src="https://img.shields.io/badge/api-reference-blue.svg?style=flat-square" alt="GoDoc"></a>
<a href="http://tidwall.com/gjson-play"><img src="https://img.shields.io/badge/play-ground-orange.svg?style=flat-square" alt="GJSON Playground"></a>
<a href="http://tidwall.com/gjson-play"><img src="https://img.shields.io/badge/%F0%9F%8F%90-playground-9900cc.svg?style=flat-square" alt="GJSON Playground"></a>
</p>
<p align="center">get a json value quickly</a></p>
<p align="center">get json values quickly</a></p>
GJSON is a Go package that provides a [fast](#performance) and [simple](#get-a-value) way to get values from a json document.
It has features such as [one line retrieval](#get-a-value), [dot notation paths](#path-syntax), [iteration](#iterate-through-an-object-or-array).
It has features such as [one line retrieval](#get-a-value), [dot notation paths](#path-syntax), [iteration](#iterate-through-an-object-or-array), and [parsing json lines](#json-lines).
Also check out [SJSON](https://github.com/tidwall/sjson) for modifying json, and the [JJ](https://github.com/tidwall/jj) command line tool.
Getting Started
===============
@@ -29,7 +31,7 @@ $ go get -u github.com/tidwall/gjson
This will retrieve the library.
## Get a value
Get searches json for the specified path. A path is in dot syntax, such as "name.last" or "age". This function expects that the json is well-formed. Bad json will not panic, but it may return back unexpected results. When the value is found it's returned immediately.
Get searches json for the specified path. A path is in dot syntax, such as "name.last" or "age". When the value is found it's returned immediately.
```go
package main
@@ -95,6 +97,36 @@ friends.#[age>45]#.last >> ["Craig","Murphy"]
friends.#[first%"D*"].last >> "Murphy"
```
## JSON Lines
There's support for [JSON Lines](http://jsonlines.org/) using the `..` prefix, which treats a multilined document as an array.
For example:
```
{"name": "Gilbert", "age": 61}
{"name": "Alexa", "age": 34}
{"name": "May", "age": 57}
{"name": "Deloise", "age": 44}
```
```
..# >> 4
..1 >> {"name": "Alexa", "age": 34}
..3 >> {"name": "Deloise", "age": 44}
..#.name >> ["Gilbert","Alexa","May","Deloise"]
..#[name="May"].age >> 57
```
The `ForEachLines` function will iterate through JSON lines.
```go
gjson.ForEachLine(json, func(line gjson.Result) bool{
println(line.String())
return true
})
```
## Result Type
GJSON supports the json types `string`, `number`, `bool`, and `null`.
@@ -152,6 +184,15 @@ array >> []interface{}
object >> map[string]interface{}
```
### 64-bit integers
The `result.Int()` and `result.Uint()` calls are capable of reading all 64 bits, allowing for large JSON integers.
```go
result.Int() int64 // -9223372036854775808 to 9223372036854775807
result.Uint() int64 // 0 to 18446744073709551615
```
## Get nested array values
Suppose you want all the last names from the following json:
@@ -234,6 +275,19 @@ if gjson.Get(json, "name.last").Exists() {
}
```
## Validate JSON
The `Get*` and `Parse*` functions expects that the json is well-formed. Bad json will not panic, but it may return back unexpected results.
If you are consuming JSON from an unpredictable source then you may want to validate prior to using GJSON.
```go
if !gjson.Valid(json) {
return errors.New("invalid json")
}
value := gjson.Get(json, "name.last")
```
## Unmarshal to a map
To unmarshal to a `map[string]interface{}`:

100
gjson.go
View File

@@ -96,7 +96,7 @@ func (t Result) Bool() bool {
case True:
return true
case String:
return t.Str != "" && t.Str != "0"
return t.Str != "" && t.Str != "0" && t.Str != "false"
case Number:
return t.Num != 0
}
@@ -390,6 +390,11 @@ end:
}
// Parse parses the json and returns a result.
//
// This function expects that the json is well-formed, and does not validate.
// Invalid json will not panic, but it may return back unexpected results.
// If you are consuming JSON from an unpredictable source then you may want to
// use the Valid function first.
func Parse(json string) Result {
var value Result
for i := 0; i < len(json); i++ {
@@ -1077,7 +1082,7 @@ func queryMatches(rp *arrayPathResult, value Result) bool {
case "=":
return value.Num == rpvn
case "!=":
return value.Num == rpvn
return value.Num != rpvn
case "<":
return value.Num < rpvn
case "<=":
@@ -1128,7 +1133,7 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
partidx = int(n)
}
}
for i < len(c.json) {
for i < len(c.json)+1 {
if !rp.arrch {
pmatch = partidx == h
hit = pmatch && !rp.more
@@ -1137,8 +1142,16 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
if rp.alogok {
alog = append(alog, i)
}
for ; i < len(c.json); i++ {
switch c.json[i] {
for ; ; i++ {
var ch byte
if i > len(c.json) {
break
} else if i == len(c.json) {
ch = ']'
} else {
ch = c.json[i]
}
switch ch {
default:
continue
case '"':
@@ -1252,14 +1265,18 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
if rp.alogok {
var jsons = make([]byte, 0, 64)
jsons = append(jsons, '[')
for j, k := 0, 0; j < len(alog); j++ {
res := Get(c.json[alog[j]:], rp.alogkey)
if res.Exists() {
if k > 0 {
jsons = append(jsons, ',')
_, res, ok := parseAny(c.json, alog[j], true)
if ok {
res := res.Get(rp.alogkey)
if res.Exists() {
if k > 0 {
jsons = append(jsons, ',')
}
jsons = append(jsons, []byte(res.Raw)...)
k++
}
jsons = append(jsons, []byte(res.Raw)...)
k++
}
}
jsons = append(jsons, ']')
@@ -1290,16 +1307,32 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
return i, false
}
// ForEachLine iterates through lines of JSON as specified by the JSON Lines
// format (http://jsonlines.org/).
// Each line is returned as a GJSON Result.
func ForEachLine(json string, iterator func(line Result) bool) {
var res Result
var i int
for {
i, res, _ = parseAny(json, i, true)
if !res.Exists() {
break
}
if !iterator(res) {
return
}
}
}
type parseContext struct {
json string
value Result
calcd bool
lines bool
}
// Get searches json for the specified path.
// A path is in dot syntax, such as "name.last" or "age".
// This function expects that the json is well-formed, and does not validate.
// Invalid json will not panic, but it may return back unexpected results.
// When the value is found it's returned immediately.
//
// A path is a series of keys searated by a dot.
@@ -1326,19 +1359,28 @@ type parseContext struct {
// "c?ildren.0" >> "Sara"
// "friends.#.first" >> ["James","Roger"]
//
// This function expects that the json is well-formed, and does not validate.
// Invalid json will not panic, but it may return back unexpected results.
// If you are consuming JSON from an unpredictable source then you may want to
// use the Valid function first.
func Get(json, path string) Result {
var i int
var c = &parseContext{json: json}
for ; i < len(c.json); i++ {
if c.json[i] == '{' {
i++
parseObject(c, i, path)
break
}
if c.json[i] == '[' {
i++
parseArray(c, i, path)
break
if len(path) >= 2 && path[0] == '.' && path[1] == '.' {
c.lines = true
parseArray(c, 0, path[2:])
} else {
for ; i < len(c.json); i++ {
if c.json[i] == '{' {
i++
parseObject(c, i, path)
break
}
if c.json[i] == '[' {
i++
parseArray(c, i, path)
break
}
}
}
if len(c.value.Raw) > 0 && !c.calcd {
@@ -1610,11 +1652,7 @@ func GetMany(json string, path ...string) []Result {
// The return value is a Result array where the number of items
// will be equal to the number of input paths.
func GetManyBytes(json []byte, path ...string) []Result {
res := make([]Result, len(path))
for i, path := range path {
res[i] = GetBytes(json, path)
}
return res
return GetMany(string(json), path...)
}
var fieldsmu sync.RWMutex
@@ -2007,6 +2045,12 @@ func validnull(data []byte, i int) (outi int, ok bool) {
}
// Valid returns true if the input is valid json.
//
// if !gjson.Valid(json) {
// return errors.New("invalid json")
// }
// value := gjson.Get(json, "name.last")
//
func Valid(json string) bool {
_, ok := validpayload([]byte(json), 0)
return ok

View File

@@ -148,7 +148,7 @@ func TestTimeResult(t *testing.T) {
func TestParseAny(t *testing.T) {
assert(t, Parse("100").Float() == 100)
assert(t, Parse("true").Bool())
assert(t, Parse("valse").Bool() == false)
assert(t, Parse("false").Bool() == false)
}
func TestManyVariousPathCounts(t *testing.T) {
@@ -1275,7 +1275,6 @@ func randomObjectOrArray(keys []string, prefix string, array bool, depth int) (s
}
func randomJSON() (json string, keys []string) {
//rand.Seed(time.Now().UnixNano())
return randomObjectOrArray(nil, "", false, 0)
}
@@ -1289,3 +1288,67 @@ func TestIssue55(t *testing.T) {
}
}
}
func TestIssue58(t *testing.T) {
json := `{"data":[{"uid": 1},{"uid": 2}]}`
res := Get(json, `data.#[uid!=1]`).Raw
if res != `{"uid": 2}` {
t.Fatalf("expected '%v', got '%v'", `{"uid": 1}`, res)
}
}
func TestObjectGrouping(t *testing.T) {
json := `
[
true,
{"name":"tom"},
false,
{"name":"janet"},
null
]
`
res := Get(json, "#.name")
if res.String() != `["tom","janet"]` {
t.Fatalf("expected '%v', got '%v'", `["tom","janet"]`, res.String())
}
}
func TestJSONLines(t *testing.T) {
json := `
true
false
{"name":"tom"}
[1,2,3,4,5]
{"name":"janet"}
null
12930.1203
`
paths := []string{"..#", "..0", "..2.name", "..#.name", "..6", "..7"}
ress := []string{"7", "true", "tom", `["tom","janet"]`, "12930.1203", ""}
for i, path := range paths {
res := Get(json, path)
if res.String() != ress[i] {
t.Fatalf("expected '%v', got '%v'", ress[i], res.String())
}
}
json = `
{"name": "Gilbert", "wins": [["straight", "7♣"], ["one pair", "10♥"]]}
{"name": "Alexa", "wins": [["two pair", "4♠"], ["two pair", "9♠"]]}
{"name": "May", "wins": []}
{"name": "Deloise", "wins": [["three of a kind", "5♣"]]}
`
var i int
lines := strings.Split(strings.TrimSpace(json), "\n")
ForEachLine(json, func(line Result) bool {
if line.Raw != lines[i] {
t.Fatalf("expected '%v', got '%v'", lines[i], line.Raw)
}
i++
return true
})
if i != 4 {
t.Fatalf("expected '%v', got '%v'", 4, i)
}
}