forked from mirror/gjson
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
This commit is contained in:
parent
5fe9078c47
commit
a2f35b522e
38
README.md
38
README.md
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
GJSON is a Go package that provides a [fast](#performance) and [simple](#get-a-value) way to get values from a json document.
|
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).
|
||||||
|
For a command-line tool that uses the GJSON syntax check out [JJ](https://github.com/tidwall/jj).
|
||||||
|
|
||||||
Getting Started
|
Getting Started
|
||||||
===============
|
===============
|
||||||
|
@ -95,6 +96,43 @@ friends.#[age>45]#.last >> ["Craig","Murphy"]
|
||||||
friends.#[first%"D*"].last >> "Murphy"
|
friends.#[first%"D*"].last >> "Murphy"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## JSON Lines
|
||||||
|
|
||||||
|
There also 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
|
||||||
|
```
|
||||||
|
|
||||||
|
The `ForEachLines` function will iterate through lines.
|
||||||
|
|
||||||
|
```go
|
||||||
|
gjson.ForEachLine(json, func(line gjson.Result) bool{
|
||||||
|
println(line.String())
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
// Outputs:
|
||||||
|
// {"name": "Gilbert", "age": 61}
|
||||||
|
// {"name": "Alexa", "age": 34}
|
||||||
|
// {"name": "May", "age": 57}
|
||||||
|
// {"name": "Deloise", "age": 44}
|
||||||
|
```
|
||||||
|
|
||||||
## Result Type
|
## Result Type
|
||||||
|
|
||||||
GJSON supports the json types `string`, `number`, `bool`, and `null`.
|
GJSON supports the json types `string`, `number`, `bool`, and `null`.
|
||||||
|
|
73
gjson.go
73
gjson.go
|
@ -1128,7 +1128,7 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
|
||||||
partidx = int(n)
|
partidx = int(n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i < len(c.json) {
|
for i < len(c.json)+1 {
|
||||||
if !rp.arrch {
|
if !rp.arrch {
|
||||||
pmatch = partidx == h
|
pmatch = partidx == h
|
||||||
hit = pmatch && !rp.more
|
hit = pmatch && !rp.more
|
||||||
|
@ -1137,8 +1137,16 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
|
||||||
if rp.alogok {
|
if rp.alogok {
|
||||||
alog = append(alog, i)
|
alog = append(alog, i)
|
||||||
}
|
}
|
||||||
for ; i < len(c.json); i++ {
|
for ; ; i++ {
|
||||||
switch c.json[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:
|
default:
|
||||||
continue
|
continue
|
||||||
case '"':
|
case '"':
|
||||||
|
@ -1252,14 +1260,18 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
|
||||||
if rp.alogok {
|
if rp.alogok {
|
||||||
var jsons = make([]byte, 0, 64)
|
var jsons = make([]byte, 0, 64)
|
||||||
jsons = append(jsons, '[')
|
jsons = append(jsons, '[')
|
||||||
|
|
||||||
for j, k := 0, 0; j < len(alog); j++ {
|
for j, k := 0, 0; j < len(alog); j++ {
|
||||||
res := Get(c.json[alog[j]:], rp.alogkey)
|
_, res, ok := parseAny(c.json, alog[j], true)
|
||||||
if res.Exists() {
|
if ok {
|
||||||
if k > 0 {
|
res := res.Get(rp.alogkey)
|
||||||
jsons = append(jsons, ',')
|
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, ']')
|
jsons = append(jsons, ']')
|
||||||
|
@ -1290,10 +1302,28 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
|
||||||
return i, false
|
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 {
|
type parseContext struct {
|
||||||
json string
|
json string
|
||||||
value Result
|
value Result
|
||||||
calcd bool
|
calcd bool
|
||||||
|
lines bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get searches json for the specified path.
|
// Get searches json for the specified path.
|
||||||
|
@ -1329,16 +1359,21 @@ type parseContext struct {
|
||||||
func Get(json, path string) Result {
|
func Get(json, path string) Result {
|
||||||
var i int
|
var i int
|
||||||
var c = &parseContext{json: json}
|
var c = &parseContext{json: json}
|
||||||
for ; i < len(c.json); i++ {
|
if len(path) >= 2 && path[0] == '.' && path[1] == '.' {
|
||||||
if c.json[i] == '{' {
|
c.lines = true
|
||||||
i++
|
parseArray(c, 0, path[2:])
|
||||||
parseObject(c, i, path)
|
} else {
|
||||||
break
|
for ; i < len(c.json); i++ {
|
||||||
}
|
if c.json[i] == '{' {
|
||||||
if c.json[i] == '[' {
|
i++
|
||||||
i++
|
parseObject(c, i, path)
|
||||||
parseArray(c, i, path)
|
break
|
||||||
break
|
}
|
||||||
|
if c.json[i] == '[' {
|
||||||
|
i++
|
||||||
|
parseArray(c, i, path)
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(c.value.Raw) > 0 && !c.calcd {
|
if len(c.value.Raw) > 0 && !c.calcd {
|
||||||
|
|
|
@ -1295,3 +1295,60 @@ func TestIssue58(t *testing.T) {
|
||||||
t.Fatalf("expected '%v', got '%v'", `{"uid": 1}`, res)
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue