pax_global_header00006660000000000000000000000064136553072000014513gustar00rootroot0000000000000052 comment=b8565c15b0757d4d4fbbeddba11d627a1ba08e62 jsonquery-1.1.4/000077500000000000000000000000001365530720000135555ustar00rootroot00000000000000jsonquery-1.1.4/.gitignore000066400000000000000000000004621365530720000155470ustar00rootroot00000000000000# 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.1.4/.travis.yml000066400000000000000000000003521365530720000156660ustar00rootroot00000000000000language: go go: - 1.9.x - 1.12.x - 1.13.x install: - go get github.com/antchfx/xpath - go get github.com/mattn/goveralls - go get github.com/golang/groupcache script: - $HOME/gopath/bin/goveralls -service=travis-cijsonquery-1.1.4/LICENSE000066400000000000000000000017761365530720000145750ustar00rootroot00000000000000Permission 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.1.4/README.md000066400000000000000000000077131365530720000150440ustar00rootroot00000000000000jsonquery ==== [![Build Status](https://travis-ci.org/antchfx/jsonquery.svg?branch=master)](https://travis-ci.org/antchfx/jsonquery) [![Coverage Status](https://coveralls.io/repos/github/antchfx/jsonquery/badge.svg?branch=master)](https://coveralls.io/github/antchfx/jsonquery?branch=master) [![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 is an XPath query package for JSON document, lets you extract data from JSON documents through an XPath expression. Built-in XPath expression cache avoid re-compile XPath expression each query. Getting Started === ### Install Package ``` go get github.com/antchfx/jsonquery ``` #### Load JSON document from URL. ```go doc, err := jsonquery.LoadURL("http://www.example.com/feed?json") ``` #### Load JSON document from string. ```go s :=`{ "name":"John", "age":31, "city":"New York" }` doc, err := jsonquery.Parse(strings.NewReader(s)) ``` #### Load JSON document from io.Reader. ```go f, err := os.Open("./books.json") doc, err := jsonquery.Parse(f) ``` #### Find authors of all books in the store. ```go list := jsonquery.Find(doc, "store/book/*/author") // or equal to list := jsonquery.Find(doc, "//author") // or by QueryAll() nodes, err := jsonquery.QueryAll(doc, "//a") ``` #### Find the third book. ```go book := jsonquery.Find(doc, "//book/*[3]") ``` #### Find the last book. ```go book := jsonquery.Find(doc, "//book/*[last()]") ``` #### Find all books that have an isbn number. ```go list := jsonquery.Find(doc, "//book/*[isbn]") ``` #### Find all books priced less than 10. ```go list := jsonquery.Find(doc, "//book/*[price<10]") ``` Examples === ```go func main() { s := `{ "name": "John", "age" : 26, "address" : { "streetAddress": "naist street", "city" : "Nara", "postalCode" : "630-0192" }, "phoneNumbers": [ { "type" : "iPhone", "number": "0123-4567-8888" }, { "type" : "home", "number": "0123-4567-8910" } ] }` doc, err := jsonquery.Parse(strings.NewReader(s)) if err != nil { panic(err) } name := jsonquery.FindOne(doc, "name") fmt.Printf("name: %s\n", name.InnerText()) var a []string for _, n := range jsonquery.Find(doc, "phoneNumbers/*/number") { a = append(a, n.InnerText()) } fmt.Printf("phone number: %s\n", strings.Join(a, ",")) if n := jsonquery.FindOne(doc, "address/streetAddress"); n != nil { fmt.Printf("address: %s\n", n.InnerText()) } } ``` Implement Principle === If you are familiar with XPath and XML, you can easily figure out how to write your XPath expression. ```json { "name":"John", "age":30, "cars": [ { "name":"Ford", "models":[ "Fiesta", "Focus", "Mustang" ] }, { "name":"BMW", "models":[ "320", "X3", "X5" ] }, { "name":"Fiat", "models":[ "500", "Panda" ] } ] } ``` The above JSON document will be convert to similar to XML document by the *JSONQuery*, like below: ```XML John 30 Ford Fiesta Focus Mustang BMW 320 X3 X5 Fiat 500 Panda ``` Notes: `element` is empty element that have no any name. List of XPath query packages === |Name |Description | |--------------------------|----------------| |[htmlquery](https://github.com/antchfx/htmlquery) | XPath query package for the HTML document| |[xmlquery](https://github.com/antchfx/xmlquery) | XPath query package for the XML document| |[jsonquery](https://github.com/antchfx/jsonquery) | XPath query package for the JSON document| jsonquery-1.1.4/books.json000066400000000000000000000017141365530720000155700ustar00rootroot00000000000000{ "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 } } }jsonquery-1.1.4/cache.go000066400000000000000000000016231365530720000151510ustar00rootroot00000000000000package 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.1.4/go.mod000066400000000000000000000002341365530720000146620ustar00rootroot00000000000000module github.com/antchfx/jsonquery go 1.14 require ( github.com/antchfx/xpath v1.1.7 github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e ) jsonquery-1.1.4/go.sum000066400000000000000000000010651365530720000147120ustar00rootroot00000000000000github.com/antchfx/xpath v1.1.6 h1:6sVh6hB5T6phw1pFpHRQ+C4bd8sNI+O58flqtg7h0R0= github.com/antchfx/xpath v1.1.6/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk= github.com/antchfx/xpath v1.1.7 h1:RgnAdTaRzF4bBiTqdDA7ZQ7IU8ivc72KSTf3/XCA/ic= github.com/antchfx/xpath v1.1.7/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= jsonquery-1.1.4/node.go000066400000000000000000000100461365530720000150320ustar00rootroot00000000000000package jsonquery import ( "bytes" "encoding/json" "io" "io/ioutil" "net/http" "sort" "strconv" ) // 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 } // 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 gets the value of the node and all its child nodes. func (n *Node) InnerText() string { var output func(*bytes.Buffer, *Node) output = func(buf *bytes.Buffer, n *Node) { if n.Type == TextNode { buf.WriteString(n.Data) return } for child := n.FirstChild; child != nil; child = child.NextSibling { output(buf, child) } } var buf bytes.Buffer output(&buf, n) return buf.String() } func outputXML(buf *bytes.Buffer, n *Node) { switch n.Type { case ElementNode: if n.Data == "" { buf.WriteString("") } else { buf.WriteString("<" + n.Data + ">") } case TextNode: buf.WriteString(n.Data) return } for child := n.FirstChild; child != nil; child = child.NextSibling { outputXML(buf, child) } if n.Data == "" { buf.WriteString("") } else { buf.WriteString("") } } // OutputXML prints the XML string. func (n *Node) OutputXML() string { var buf bytes.Buffer buf.WriteString(``) for n := n.FirstChild; n != nil; n = n.NextSibling { outputXML(&buf, n) } return buf.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{}: for _, vv := range v { n := &Node{Type: ElementNode, level: level} addNode(n) parseValue(vv, n, level+1) } case map[string]interface{}: // 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} addNode(n) parseValue(v[key], n, level+1) } case string: n := &Node{Data: v, Type: TextNode, level: level} addNode(n) case float64: s := strconv.FormatFloat(v, 'f', -1, 64) n := &Node{Data: s, Type: TextNode, level: level} addNode(n) case bool: s := strconv.FormatBool(v) n := &Node{Data: s, Type: TextNode, level: level} 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.1.4/node_test.go000066400000000000000000000072211365530720000160720ustar00rootroot00000000000000package jsonquery import ( "sort" "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) } // output like below: // 1 // 2 // ... // 6 if e, g := 6, len(doc.ChildNodes()); e != g { t.Fatalf("excepted %v but got %v", e, g) } var v []string for _, n := range doc.ChildNodes() { v = append(v, n.InnerText()) } if got, expected := strings.Join(v, ","), "1,2,3,4,5,6"; got != expected { t.Fatalf("got %v but expected %v", got, expected) } } func TestParseJsonObject(t *testing.T) { s := `{ "name":"John", "age":31, "city":"New York" }` doc, err := parseString(s) if err != nil { t.Fatal(err) } // output like below: // John // 31 // New York m := make(map[string]string) for _, n := range doc.ChildNodes() { m[n.Data] = n.InnerText() } expected := []struct { name, value string }{ {"name", "John"}, {"age", "31"}, {"city", "New York"}, } for _, v := range expected { if e, g := v.value, m[v.name]; e != g { t.Fatalf("expected %v=%v,but %v=%v", v.name, e, v.name, g) } } } func TestParseJsonObjectArray(t *testing.T) { s := `[ { "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) } /** Ford Fiesta Focus Mustang BMW 320 X3 X5 .... */ if e, g := 3, len(doc.ChildNodes()); e != g { t.Fatalf("expected %v, but %v", e, g) } m := make(map[string][]string) for _, n := range doc.ChildNodes() { // Go to the next of the element list. var name string var models []string for _, e := range n.ChildNodes() { if e.Data == "name" { // a name node. name = e.InnerText() } else { // a models node. for _, k := range e.ChildNodes() { models = append(models, k.InnerText()) } } } // Sort models list. sort.Strings(models) m[name] = models } expected := []struct { name, value string }{ {"Ford", "Fiesta,Focus,Mustang"}, {"BMW", "320,X3,X5"}, {"Fiat", "500,Panda"}, } for _, v := range expected { if e, g := v.value, strings.Join(m[v.name], ","); e != g { t.Fatalf("expected %v=%v,but %v=%v", v.name, e, v.name, g) } } } 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.InnerText() != "365823929453" { t.Fatalf("expected %v but %v", "365823929453", n.InnerText()) } } jsonquery-1.1.4/query.go000066400000000000000000000071401365530720000152530ustar00rootroot00000000000000package jsonquery import ( "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 { switch a.cur.Type { case ElementNode: return a.cur.InnerText() case TextNode: return a.cur.Data } return "" } 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.1.4/query_test.go000066400000000000000000000060161365530720000163130ustar00rootroot00000000000000package jsonquery import ( "strings" "testing" "github.com/antchfx/xpath" ) 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, _ := parseString(s) /** 30 ... Ford ... BMW ... Fiat John */ nav := CreateXPathNavigator(doc) nav.MoveToRoot() if nav.NodeType() != xpath.RootNode { t.Fatal("node type is not RootNode") } // Move to first child(age). if e, g := true, nav.MoveToChild(); e != g { t.Fatalf("expected %v but %v", e, g) } if e, g := "age", nav.Current().Data; e != g { t.Fatalf("expected %v but %v", e, g) } if e, g := "30", nav.Value(); e != g { t.Fatalf("expected %v but %v", e, g) } // Move to next sibling node(cars). if e, g := true, nav.MoveToNext(); e != g { t.Fatalf("expected %v but %v", e, g) } if e, g := "cars", nav.Current().Data; e != g { t.Fatalf("expected %v but %v", e, g) } 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() 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 } expected := []struct { name, value string }{ {"Ford", "Fiesta,Focus,Mustang"}, {"BMW", "320,X3,X5"}, {"Fiat", "500,Panda"}, } for _, v := range expected { if e, g := v.value, strings.Join(m[v.name], ","); e != g { t.Fatalf("expected %v=%v,but %v=%v", v.name, e, v.name, g) } } nav.MoveTo(cur) // move to name. if e, g := true, nav.MoveToNext(); e != g { t.Fatalf("expected %v but %v", e, g) } // move to cars nav.MoveToPrevious() if e, g := "cars", nav.Current().Data; e != g { t.Fatalf("expected %v but %v", e, g) } // move to age. nav.MoveToFirst() if e, g := "age", nav.Current().Data; e != g { t.Fatalf("expected %v but %v", e, g) } nav.MoveToParent() if g := nav.Current().Type; g != DocumentNode { t.Fatalf("node type is not DocumentNode") } }