pax_global_header00006660000000000000000000000064146361664010014521gustar00rootroot0000000000000052 comment=08a718232b5e380bef963ea80f483158e8e6a3cd jsonquery-1.3.5/000077500000000000000000000000001463616640100135665ustar00rootroot00000000000000jsonquery-1.3.5/.github/000077500000000000000000000000001463616640100151265ustar00rootroot00000000000000jsonquery-1.3.5/.github/workflows/000077500000000000000000000000001463616640100171635ustar00rootroot00000000000000jsonquery-1.3.5/.github/workflows/testing.yml000066400000000000000000000007561463616640100213730ustar00rootroot00000000000000name: Testing on: [push, pull_request] jobs: test: strategy: matrix: go-version: ["1.20", 1.21.x, 1.22.x] os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: - name: Setup Go uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} - name: Checkout code uses: actions/checkout@v4 - name: Test run: | go version go test . -v -cover jsonquery-1.3.5/.gitignore000066400000000000000000000004621463616640100155600ustar00rootroot00000000000000# vscode .vscode debug *.test ./build # Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.profjsonquery-1.3.5/LICENSE000066400000000000000000000017761463616640100146060ustar00rootroot00000000000000Permission 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.jsonquery-1.3.5/README.md000066400000000000000000000231771463616640100150570ustar00rootroot00000000000000# jsonquery [![Build Status](https://github.com/antchfx/jsonquery/actions/workflows/testing.yml/badge.svg)](https://github.com/antchfx/jsonquery/actions/workflows/testing.yml) [![GoDoc](https://godoc.org/github.com/antchfx/jsonquery?status.svg)](https://godoc.org/github.com/antchfx/jsonquery) [![Go Report Card](https://goreportcard.com/badge/github.com/antchfx/jsonquery)](https://goreportcard.com/report/github.com/antchfx/jsonquery) # Overview [jsonquery](https://github.com/antchfx/jsonquery) is XPath query package for JSON document depended on [xpath](https://github.com/antchfx/xpath) package, writing in go. jsonquery helps you easy to extract any data from JSON using XPath query without using pre-defined object structure to unmarshal in go, saving your time. - [htmlquery](https://github.com/antchfx/htmlquery) - XPath query package for HTML document - [xmlquery](https://github.com/antchfx/xmlquery) - XPath query package for XML document. ### Install Package ``` go get github.com/antchfx/jsonquery ``` ## Get Started The below code may be help your understand what it does. We don't need pre-defined structure or using regexp to extract some data in JSON file, gets any data is easy and fast in jsonquery now. Using an xpath like syntax to access specific fields of a json structure. ```go // https://go.dev/play/p/vqoD_jWryKY package main import ( "fmt" "strings" "github.com/antchfx/jsonquery" ) func main() { s := `{ "person":{ "name":"John", "age":31, "female":false, "city":null, "hobbies":[ "coding", "eating", "football" ] } }` doc, err := jsonquery.Parse(strings.NewReader(s)) if err != nil { panic(err) } // xpath query age := jsonquery.FindOne(doc, "age") // or age = jsonquery.FindOne(doc, "person/age") fmt.Printf("%#v[%T]\n", age.Value(), age.Value()) // prints 31[float64] hobbies := jsonquery.FindOne(doc, "//hobbies") fmt.Printf("%#v\n", hobbies.Value()) // prints []interface {}{"coding", "eating", "football"} firstHobby := jsonquery.FindOne(doc, "//hobbies/*[1]") fmt.Printf("%#v\n", firstHobby.Value()) // "coding" } ``` Iterating over a json structure. ```go // https://go.dev/play/p/vwXQKTCLdVK package main import ( "fmt" "strings" "github.com/antchfx/jsonquery" ) func main() { s := `{ "name":"John", "age":31, "female":false, "city":null }` doc, err := jsonquery.Parse(strings.NewReader(s)) if err != nil { panic(err) } // iterate all json objects from child ndoes. for _, n := range doc.ChildNodes() { fmt.Printf("%s: %v[%T]\n", n.Data, n.Value(), n.Value()) } } ``` Output: ``` name: John[string] age: 31[float64] female: false[bool] city: [] ``` The default Json types and Go types are: | JSON | jsonquery(go) | | ------- | ------------- | | object | interface{} | | string | string | | number | float64 | | boolean | bool | | array | []interface{} | | null | nil | For more information about JSON & Go see the https://go.dev/blog/json ## Getting Started #### Load JSON from URL. ```go doc, err := jsonquery.LoadURL("http://www.example.com/feed?json") ``` #### Load JSON from string. ```go s :=`{ "name":"John", "age":31, "city":"New York" }` doc, err := jsonquery.Parse(strings.NewReader(s)) ``` #### Load JSON from io.Reader. ```go f, err := os.Open("./books.json") doc, err := jsonquery.Parse(f) ``` #### Parse JSON array ```go s := `[1,2,3,4,5,6]` doc, _ := jsonquery.Parse(strings.NewReader(s)) list := jsonquery.Find(doc, "*") for _, n := range list { fmt.Print(n.Value().(float64)) } ``` // Output: `1,2,3,4,5,6` #### Convert JSON object to XML file ```go s := `[{"name":"John", "age":31, "female":false, "city":null}]` doc, _ := jsonquery.Parse(strings.NewReader(s)) fmt.Println(doc.OutputXML()) ``` ### Methods #### FindOne() ```go n := jsonquery.FindOne(doc,"//a") ``` #### Find() ```go list := jsonquery.Find(doc,"//a") ``` #### QuerySelector() ```go n := jsonquery.QuerySelector(doc, xpath.MustCompile("//a")) ``` #### QuerySelectorAll() ```go list :=jsonquery.QuerySelectorAll(doc, xpath.MustCompile("//a")) ``` #### Query() ```go n, err := jsonquery.Query(doc, "*") ``` #### QueryAll() ```go list, err := jsonquery.QueryAll(doc, "*") ``` #### Query() vs FindOne() - `Query()` will return an error if give xpath query expr is not valid. - `FindOne` will panic error and interrupt your program if give xpath query expr is not valid. #### OutputXML() Convert current JSON object to XML format. ## Example of how to convert JSON object to XML file ```json { "store": { "book": [ { "id": 1, "category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95 }, { "id": 2, "category": "fiction", "author": "Evelyn Waugh", "title": "Sword of Honour", "price": 12.99 }, { "id": 3, "category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "isbn": "0-553-21311-3", "price": 8.99 }, { "id": 4, "category": "fiction", "author": "J. R. R. Tolkien", "title": "The Lord of the Rings", "isbn": "0-395-19395-8", "price": 22.99 } ], "bicycle": { "color": "red", "price": 19.95 } }, "expensive": 10 } ``` ```go doc, err := jsonquery.Parse(strings.NewReader(s)) if err != nil { panic(err) } fmt.Println(doc.OutputXML()) ``` Output the below XML: ```xml 10 red 19.95 Nigel Rees reference 1 8.95 Sayings of the Century Evelyn Waugh fiction 2 12.99 Sword of Honour Herman Melville fiction 3 0-553-21311-3 8.99 Moby Dick J. R. R. Tolkien fiction 4 0-395-19395-8 22.99 The Lord of the Rings ``` ## XPath Tests | Query | Matched | Native Value Types | Native Values | | ----------------------------------- | ------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------- | | `//book` | 1 | []interface{} | `{"book": [{"id":1,... }, {"id":2,... }, {"id":3,... }, {"id":4,... }]}` | | `//book/*` | 4 | [map[string]interface{}] | `{"id":1,... }`, `{"id":2,... }`, `{"id":3,... }`, `{"id":4,... }` | | `//*[price<12.99]` | 2 | [map[string]interface{}] | `{"id":1,...}`, `{"id":3,...}` | | `//book/*/author` | 4 | []string | `{"author": "Nigel Rees"}`, `{"author": "Evelyn Waugh"}`, `{"author": "Herman Melville"}`, `{"author": "J. R. R. Tolkien"}` | | `//book/*[last()]` | 1 | map[string]interface {} | `{"id":4,...}` | | `//book/*[2]` | 1 | map[string]interface{} | `{"id":2,...}` | | `//*[isbn]` | 2 | [map[string]interface{}] | `{"id":3,"isbn":"0-553-21311-3",...}`,`{"id":4,"isbn":"0-395-19395-8",...}` | | `//*[isbn='0-553-21311-3']` | 1 | map[string]interface{} | `{"id":3,"isbn":"0-553-21311-3",...}` | | `//bicycle` | 1 | map[string]interface {} | `{"bicycle":{"color":...,}}` | | `//bicycle/color[text()='red']` | 1 | map[string]interface {} | `{"color":"red"}` | | `//*/category[contains(.,'refer')]` | 1 | string | `{"category": "reference"}` | | `//price[.=22.99]` | 1 | float64 | `{"price": 22.99}` | | `//expensive/text()` | 1 | string | `10` | For more supports XPath feature and function see https://github.com/antchfx/xpath jsonquery-1.3.5/cache.go000066400000000000000000000016231463616640100151620ustar00rootroot00000000000000package jsonquery import ( "sync" "github.com/golang/groupcache/lru" "github.com/antchfx/xpath" ) // DisableSelectorCache will disable caching for the query selector if value is true. var DisableSelectorCache = false // SelectorCacheMaxEntries allows how many selector object can be caching. Default is 50. // Will disable caching if SelectorCacheMaxEntries <= 0. var SelectorCacheMaxEntries = 50 var ( cacheOnce sync.Once cache *lru.Cache cacheMutex sync.Mutex ) func getQuery(expr string) (*xpath.Expr, error) { if DisableSelectorCache || SelectorCacheMaxEntries <= 0 { return xpath.Compile(expr) } cacheOnce.Do(func() { cache = lru.New(SelectorCacheMaxEntries) }) cacheMutex.Lock() defer cacheMutex.Unlock() if v, ok := cache.Get(expr); ok { return v.(*xpath.Expr), nil } v, err := xpath.Compile(expr) if err != nil { return nil, err } cache.Add(expr, v) return v, nil } jsonquery-1.3.5/go.mod000066400000000000000000000003001463616640100146650ustar00rootroot00000000000000module github.com/antchfx/jsonquery go 1.14 require ( github.com/antchfx/xpath v1.3.1 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da github.com/stretchr/testify v1.8.4 ) jsonquery-1.3.5/go.sum000066400000000000000000000035741463616640100147320ustar00rootroot00000000000000github.com/antchfx/xpath v1.3.1 h1:PNbFuUqHwWl0xRjvUPjJ95Agbmdj2uzzIwmQKgu4oCk= github.com/antchfx/xpath v1.3.1/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= jsonquery-1.3.5/node.go000066400000000000000000000112021463616640100150360ustar00rootroot00000000000000package jsonquery import ( "encoding/json" "fmt" "io" "io/ioutil" "net/http" "reflect" "sort" "strings" ) // A NodeType is the type of a Node. type NodeType uint const ( // DocumentNode is a document object that, as the root of the document tree, // provides access to the entire XML document. DocumentNode NodeType = iota // ElementNode is an element. ElementNode // TextNode is the text content of a node. TextNode ) // A Node consists of a NodeType and some Data (tag name for // element nodes, content for text) and are part of a tree of Nodes. type Node struct { Parent, PrevSibling, NextSibling, FirstChild, LastChild *Node Type NodeType Data string level int value interface{} } // Gets the JSON object value. func (n *Node) Value() interface{} { return n.value } // ChildNodes gets all child nodes of the node. func (n *Node) ChildNodes() []*Node { var a []*Node for nn := n.FirstChild; nn != nil; nn = nn.NextSibling { a = append(a, nn) } return a } // InnerText will gets the value of the node and all its child nodes. // // Deprecated: Use Value() to get JSON object value. func (n *Node) InnerText() string { var output func(*strings.Builder, *Node) output = func(b *strings.Builder, n *Node) { if n.Type == TextNode { b.WriteString(fmt.Sprintf("%v", n.value)) return } for child := n.FirstChild; child != nil; child = child.NextSibling { output(b, child) } } var b strings.Builder output(&b, n) return b.String() } func outputXML(b *strings.Builder, n *Node, level int, skip bool) { level++ if n.Type == TextNode { b.WriteString(fmt.Sprintf("%v", n.value)) return } if v := reflect.ValueOf(n.value); v.Kind() == reflect.Slice { for child := n.FirstChild; child != nil; child = child.NextSibling { b.WriteString("<" + n.Data + ">") outputXML(b, child, level, true) b.WriteString("") } } else { d := n.Data if !skip { if d == "" { if v := reflect.ValueOf(n.value); v.Kind() == reflect.Map { d = "element" } else { d = fmt.Sprintf("%v", n.value) } } b.WriteString("<" + d + ">") } if reflect.TypeOf(n.value) != nil { for child := n.FirstChild; child != nil; child = child.NextSibling { outputXML(b, child, level, false) } } if !skip { b.WriteString("") } } } // OutputXML prints the XML string. func (n *Node) OutputXML() string { var b strings.Builder b.WriteString(``) b.WriteString("") level := 0 for n := n.FirstChild; n != nil; n = n.NextSibling { outputXML(&b, n, level, false) } b.WriteString("") return b.String() } // SelectElement finds the first of child elements with the // specified name. func (n *Node) SelectElement(name string) *Node { for nn := n.FirstChild; nn != nil; nn = nn.NextSibling { if nn.Data == name { return nn } } return nil } // LoadURL loads the JSON document from the specified URL. func LoadURL(url string) (*Node, error) { resp, err := http.Get(url) if err != nil { return nil, err } defer resp.Body.Close() return Parse(resp.Body) } func parseValue(x interface{}, top *Node, level int) { addNode := func(n *Node) { if n.level == top.level { top.NextSibling = n n.PrevSibling = top n.Parent = top.Parent if top.Parent != nil { top.Parent.LastChild = n } } else if n.level > top.level { n.Parent = top if top.FirstChild == nil { top.FirstChild = n top.LastChild = n } else { t := top.LastChild t.NextSibling = n n.PrevSibling = t top.LastChild = n } } } switch v := x.(type) { case []interface{}: // JSON array for _, vv := range v { n := &Node{Type: ElementNode, level: level, value: vv} addNode(n) parseValue(vv, n, level+1) } case map[string]interface{}: // JSON object // The Go’s map iteration order is random. // (https://blog.golang.org/go-maps-in-action#Iteration-order) var keys []string for key := range v { keys = append(keys, key) } sort.Strings(keys) for _, key := range keys { n := &Node{Data: key, Type: ElementNode, level: level, value: v[key]} addNode(n) parseValue(v[key], n, level+1) } default: // JSON types: string, number, boolean n := &Node{Data: fmt.Sprintf("%v", v), Type: TextNode, level: level, value: fmt.Sprintf("%v", v)} addNode(n) } } func parse(b []byte) (*Node, error) { var v interface{} if err := json.Unmarshal(b, &v); err != nil { return nil, err } doc := &Node{Type: DocumentNode} parseValue(v, doc, 1) return doc, nil } // Parse JSON document. func Parse(r io.Reader) (*Node, error) { b, err := ioutil.ReadAll(r) if err != nil { return nil, err } return parse(b) } jsonquery-1.3.5/node_test.go000066400000000000000000000054311463616640100161040ustar00rootroot00000000000000package jsonquery import ( "reflect" "strings" "testing" ) func parseString(s string) (*Node, error) { return Parse(strings.NewReader(s)) } func TestParseJsonNumberArray(t *testing.T) { s := `[1,2,3,4,5,6]` doc, err := parseString(s) if err != nil { t.Fatal(err) } var values []float64 for _, n := range doc.ChildNodes() { values = append(values, n.Value().(float64)) } expected := []float64{1, 2, 3, 4, 5, 6} if p1, p2 := len(values), len(expected); p1 != p2 { t.Fatalf("got %d elements but expected %d", p1, p2) } if !reflect.DeepEqual(values, expected) { t.Fatalf("got %v but expected %v", values, expected) } } func TestParseJsonObject(t *testing.T) { s := `{ "name":"John", "age":31, "female":false }` doc, err := parseString(s) if err != nil { t.Fatal(err) } m := make(map[string]interface{}) for _, n := range doc.ChildNodes() { m[n.Data] = n.Value() } expected := []struct { name string value interface{} }{ {"name", "John"}, {"age", float64(31)}, {"female", false}, } for _, v := range expected { if e, g := v.value, m[v.name]; e != g { t.Fatalf("expected %s = %v(%T),but %s = %v(%t)", v.name, e, e, v.name, g, g) } } } func TestParseJsonObjectArray(t *testing.T) { s := `[ {"models":[ "Fiesta", "Focus", "Mustang" ] } ]` doc, err := parseString(s) if err != nil { t.Fatal(err) } first := doc.FirstChild models := first.SelectElement("models") if expected := reflect.ValueOf(models.Value()).Kind(); expected != reflect.Slice { t.Fatalf("expected models is slice(Array) but got %v", expected) } expected := []string{"Fiesta", "Focus", "Mustang"} var values []string for _, v := range models.Value().([]interface{}) { values = append(values, v.(string)) } if !reflect.DeepEqual(expected, values) { t.Fatalf("expected %v but got %v", expected, values) } } func TestParseJson(t *testing.T) { s := `{ "name":"John", "age":30, "cars": [ { "name":"Ford", "models":[ "Fiesta", "Focus", "Mustang" ] }, { "name":"BMW", "models":[ "320", "X3", "X5" ] }, { "name":"Fiat", "models":[ "500", "Panda" ] } ] }` doc, err := parseString(s) if err != nil { t.Fatal(err) } n := doc.SelectElement("name") if n == nil { t.Fatal("n is nil") } if n.NextSibling != nil { t.Fatal("next sibling shoud be nil") } if e, g := "John", n.InnerText(); e != g { t.Fatalf("expected %v but %v", e, g) } cars := doc.SelectElement("cars") if e, g := 3, len(cars.ChildNodes()); e != g { t.Fatalf("expected %v but %v", e, g) } } func TestLargeFloat(t *testing.T) { s := `{ "large_number": 365823929453 }` doc, err := parseString(s) if err != nil { t.Fatal(err) } n := doc.SelectElement("large_number") if n.Value() != float64(365823929453) { t.Fatalf("expected %v but %v", "365823929453", n.InnerText()) } } jsonquery-1.3.5/query.go000066400000000000000000000074111463616640100152650ustar00rootroot00000000000000package jsonquery import ( "encoding/json" "fmt" "github.com/antchfx/xpath" ) var _ xpath.NodeNavigator = &NodeNavigator{} // CreateXPathNavigator creates a new xpath.NodeNavigator for the specified html.Node. func CreateXPathNavigator(top *Node) *NodeNavigator { return &NodeNavigator{cur: top, root: top} } // Find is like QueryAll but will panics if `expr` cannot be parsed. func Find(top *Node, expr string) []*Node { nodes, err := QueryAll(top, expr) if err != nil { panic(err) } return nodes } // FindOne is like Query but will panics if `expr` cannot be parsed. func FindOne(top *Node, expr string) *Node { node, err := Query(top, expr) if err != nil { panic(err) } return node } // QueryAll searches the Node that matches by the specified XPath expr. // Return an error if the expression `expr` cannot be parsed. func QueryAll(top *Node, expr string) ([]*Node, error) { exp, err := getQuery(expr) if err != nil { return nil, err } return QuerySelectorAll(top, exp), nil } // Query searches the Node that matches by the specified XPath expr, // and returns first element of matched. func Query(top *Node, expr string) (*Node, error) { exp, err := getQuery(expr) if err != nil { return nil, err } return QuerySelector(top, exp), nil } // QuerySelectorAll searches all of the Node that matches the specified XPath selectors. func QuerySelectorAll(top *Node, selector *xpath.Expr) []*Node { t := selector.Select(CreateXPathNavigator(top)) var elems []*Node for t.MoveNext() { elems = append(elems, (t.Current().(*NodeNavigator)).cur) } return elems } // QuerySelector returns the first matched XML Node by the specified XPath selector. func QuerySelector(top *Node, selector *xpath.Expr) *Node { t := selector.Select(CreateXPathNavigator(top)) if t.MoveNext() { return (t.Current().(*NodeNavigator)).cur } return nil } // NodeNavigator is for navigating JSON document. type NodeNavigator struct { root, cur *Node } func (a *NodeNavigator) Current() *Node { return a.cur } func (a *NodeNavigator) NodeType() xpath.NodeType { switch a.cur.Type { case TextNode: return xpath.TextNode case DocumentNode: return xpath.RootNode case ElementNode: return xpath.ElementNode default: panic(fmt.Sprintf("unknown node type %v", a.cur.Type)) } } func (a *NodeNavigator) LocalName() string { return a.cur.Data } func (a *NodeNavigator) Prefix() string { return "" } func (a *NodeNavigator) Value() string { if v, ok := a.cur.value.(string); ok { return v } buf, err := json.Marshal(a.cur.value) if err != nil { // Keep the raw value return fmt.Sprintf("%v", a.cur.value) } return string(buf) } func (a *NodeNavigator) GetValue() interface{} { return a.cur.value } func (a *NodeNavigator) Copy() xpath.NodeNavigator { n := *a return &n } func (a *NodeNavigator) MoveToRoot() { a.cur = a.root } func (a *NodeNavigator) MoveToParent() bool { if n := a.cur.Parent; n != nil { a.cur = n return true } return false } func (x *NodeNavigator) MoveToNextAttribute() bool { return false } func (a *NodeNavigator) MoveToChild() bool { if n := a.cur.FirstChild; n != nil { a.cur = n return true } return false } func (a *NodeNavigator) MoveToFirst() bool { for n := a.cur.PrevSibling; n != nil; n = n.PrevSibling { a.cur = n } return true } func (a *NodeNavigator) String() string { return a.Value() } func (a *NodeNavigator) MoveToNext() bool { if n := a.cur.NextSibling; n != nil { a.cur = n return true } return false } func (a *NodeNavigator) MoveToPrevious() bool { if n := a.cur.PrevSibling; n != nil { a.cur = n return true } return false } func (a *NodeNavigator) MoveTo(other xpath.NodeNavigator) bool { node, ok := other.(*NodeNavigator) if !ok || node.root != a.root { return false } a.cur = node.cur return true } jsonquery-1.3.5/query_test.go000066400000000000000000000131121463616640100163170ustar00rootroot00000000000000package jsonquery import ( "strings" "testing" "github.com/antchfx/xpath" "github.com/stretchr/testify/require" ) func BenchmarkSelectorCache(b *testing.B) { DisableSelectorCache = false for i := 0; i < b.N; i++ { getQuery("/AAA/BBB/DDD/CCC/EEE/ancestor::*") } } func BenchmarkDisableSelectorCache(b *testing.B) { DisableSelectorCache = true for i := 0; i < b.N; i++ { getQuery("/AAA/BBB/DDD/CCC/EEE/ancestor::*") } } func TestNavigator(t *testing.T) { s := `{ "name":"John", "age":30, "cars": [ { "name":"Ford", "models":[ "Fiesta", "Focus", "Mustang" ] }, { "name":"BMW", "models":[ "320", "X3", "X5" ] }, { "name":"Fiat", "models":[ "500", "Panda" ] } ] }` doc, err := parseString(s) require.NoError(t, err) nav := CreateXPathNavigator(doc) nav.MoveToRoot() require.Equal(t, xpath.RootNode, nav.NodeType(), "node type is not RootNode") // Move to first child(age). require.True(t, nav.MoveToChild()) require.Equal(t, "age", nav.Current().Data) require.Equal(t, float64(30), nav.GetValue()) // Move to next sibling node(cars). require.True(t, nav.MoveToNext()) require.Equal(t, "cars", nav.Current().Data) m := make(map[string][]string) // Move to cars child node. cur := nav.Copy() for ok := nav.MoveToChild(); ok; ok = nav.MoveToNext() { // Move to node. // ...Ford cur1 := nav.Copy() var name string var models []string // name || models for ok := nav.MoveToChild(); ok; ok = nav.MoveToNext() { cur2 := nav.Copy() n := nav.Current() require.NotNil(t, n) if n.Data == "name" { name = n.InnerText() } else { for ok := nav.MoveToChild(); ok; ok = nav.MoveToNext() { cur3 := nav.Copy() models = append(models, nav.Value()) nav.MoveTo(cur3) } } nav.MoveTo(cur2) } nav.MoveTo(cur1) m[name] = models } nav.MoveTo(cur) // move to name. require.True(t, nav.MoveToNext()) // move to cars require.True(t, nav.MoveToPrevious()) require.Equal(t, "cars", nav.Current().Data) // move to age. require.True(t, nav.MoveToFirst()) require.Equal(t, "age", nav.Current().Data) nav.MoveToParent() require.Equal(t, DocumentNode, nav.Current().Type) } func TestToXML(t *testing.T) { s := `{ "name":"John", "age":31, "female":false }` doc, err := Parse(strings.NewReader(s)) require.NoError(t, err) expected := `31falseJohn` require.Equal(t, expected, doc.OutputXML()) } func TestArrayToXML(t *testing.T) { s := `[1,2,3,4]` doc, err := Parse(strings.NewReader(s)) require.NoError(t, err) expected := `<1>1<2>2<3>3<4>4` require.Equal(t, expected, doc.OutputXML()) } func TestNestToArray(t *testing.T) { s := `{ "address": { "city": "Nara", "postalCode": "630-0192", "streetAddress": "naist street" }, "age": 26, "name": "John", "phoneNumbers": [ { "number": "0123-4567-8888", "type": "iPhone" }, { "number": "0123-4567-8910", "type": "home" } ] }` doc, err := Parse(strings.NewReader(s)) require.NoError(t, err) expected := `
Nara630-0192naist street
26John0123-4567-8888iPhone0123-4567-8910home
` require.Equal(t, expected, doc.OutputXML()) } func TestQuery(t *testing.T) { doc, err := Parse(strings.NewReader(BooksExample)) require.NoError(t, err) q := "/store/bicycle" n := FindOne(doc, q) require.NotNil(t, n) q = "/store/bicycle/color" n = FindOne(doc, q) require.NotNil(t, n) require.Equal(t, "color", n.Data) } func TestQueryWhere(t *testing.T) { doc, err := Parse(strings.NewReader(BooksExample)) require.NoError(t, err) // for number q := "//*[price<=12.99]" list := Find(doc, q) require.Len(t, list, 3) // for string q = "//*/isbn[text()='0-553-21311-3']" n := FindOne(doc, q) require.NotNil(t, n) require.Equal(t, "isbn", n.Data) } func TestStringRepresentation(t *testing.T) { s := `{ "a": "a string", "b": 3.1415, "c": true, "d": { "d1": 1, "d2": "foo", "d3": true, "d4": null }, "e": ["master", 42, true], "f": 1690193829 }` doc, err := Parse(strings.NewReader(s)) require.NoError(t, err) expected := map[string]string{ "a": "a string", "b": "3.1415", "c": "true", "d": `{"d1":1,"d2":"foo","d3":true,"d4":null}`, "e": `["master",42,true]`, "f": "1690193829", } nn := CreateXPathNavigator(doc) hasData := nn.MoveToChild() require.True(t, hasData) for hasData { require.NotNil(t, nn.Current()) name := nn.Current().Data require.Equalf(t, expected[name], nn.Value(), "mismatch for node %q", name) hasData = nn.MoveToNext() } } var BooksExample string = `{ "store": { "book": [ { "category": "reference", "author": "Nigel Rees", "title": "Sayings of the Century", "price": 8.95 }, { "category": "fiction", "author": "Evelyn Waugh", "title": "Sword of Honour", "price": 12.99 }, { "category": "fiction", "author": "Herman Melville", "title": "Moby Dick", "isbn": "0-553-21311-3", "price": 8.99 }, { "category": "fiction", "author": "J. R. R. Tolkien", "title": "The Lord of the Rings", "isbn": "0-395-19395-8", "price": 22.99 } ], "bicycle": { "color": "red", "price": 19.95 } }, "expensive": 10 } `