mirror of https://github.com/tidwall/gjson.git
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.
|
||||
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
|
||||
===============
|
||||
|
@ -95,6 +96,43 @@ friends.#[age>45]#.last >> ["Craig","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
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
for i < len(c.json) {
|
||||
for i < len(c.json)+1 {
|
||||
if !rp.arrch {
|
||||
pmatch = partidx == h
|
||||
hit = pmatch && !rp.more
|
||||
|
@ -1137,8 +1137,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 +1260,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,10 +1302,28 @@ 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.
|
||||
|
@ -1329,16 +1359,21 @@ type parseContext struct {
|
|||
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 {
|
||||
|
|
|
@ -1295,3 +1295,60 @@ func TestIssue58(t *testing.T) {
|
|||
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