summaryrefslogtreecommitdiff
path: root/json
diff options
context:
space:
mode:
authorFelix Hanley <felix@userspace.com.au>2018-11-16 06:28:50 +0000
committerFelix Hanley <felix@userspace.com.au>2018-11-16 06:28:50 +0000
commitd6882bd9403c588415c1906a7015d16e92aa1ad3 (patch)
tree86f8c25049454e9d199c8f114f8e9dc52b19c2ba /json
parentdc0a50a47d1ad749efa298dcdcd732daa33388c9 (diff)
downloadquery-d6882bd9403c588415c1906a7015d16e92aa1ad3.tar.gz
query-d6882bd9403c588415c1906a7015d16e92aa1ad3.tar.bz2
Add node data types and tests
Diffstat (limited to 'json')
-rw-r--r--json/node.go40
-rw-r--r--json/node_test.go177
2 files changed, 198 insertions, 19 deletions
diff --git a/json/node.go b/json/node.go
index f219436..eb544dc 100644
--- a/json/node.go
+++ b/json/node.go
@@ -4,7 +4,6 @@ import (
"bytes"
"encoding/json"
"io"
- "io/ioutil"
"sort"
"strconv"
)
@@ -21,13 +20,20 @@ const (
TextNode
)
+var NodeNames = map[NodeType]string{
+ DocumentNode: "DocumentNode",
+ ElementNode: "ElementNode",
+ TextNode: "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
+ Type NodeType
+ Data string
+ DataType string
level int
}
@@ -91,10 +97,13 @@ func parseValue(x interface{}, top *Node, level int) {
}
}
}
+
+ // TODO check for null
+
switch v := x.(type) {
case []interface{}:
for _, vv := range v {
- n := &Node{Type: ElementNode, level: level}
+ n := &Node{Type: ElementNode, level: level, DataType: "array"}
addNode(n)
parseValue(vv, n, level+1)
}
@@ -107,39 +116,32 @@ func parseValue(x interface{}, top *Node, level int) {
}
sort.Strings(keys)
for _, key := range keys {
- n := &Node{Data: key, Type: ElementNode, level: level}
+ n := &Node{Data: key, Type: ElementNode, level: level, DataType: "object"}
addNode(n)
parseValue(v[key], n, level+1)
}
case string:
- n := &Node{Data: v, Type: TextNode, level: level}
+ n := &Node{Data: v, Type: TextNode, level: level, DataType: "string"}
addNode(n)
case float64:
s := strconv.FormatFloat(v, 'f', -1, 64)
- n := &Node{Data: s, Type: TextNode, level: level}
+ n := &Node{Data: s, Type: TextNode, level: level, DataType: "number"}
addNode(n)
case bool:
s := strconv.FormatBool(v)
- n := &Node{Data: s, Type: TextNode, level: level}
+ n := &Node{Data: s, Type: TextNode, level: level, DataType: "boolean"}
addNode(n)
}
}
-func parse(b []byte) (*Node, error) {
+// Parse JSON document.
+func Parse(r io.Reader) (*Node, error) {
var v interface{}
- if err := json.Unmarshal(b, &v); err != nil {
+ decoder := json.NewDecoder(r)
+ if err := decoder.Decode(&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)
-}
diff --git a/json/node_test.go b/json/node_test.go
new file mode 100644
index 0000000..1fe4902
--- /dev/null
+++ b/json/node_test.go
@@ -0,0 +1,177 @@
+package json
+
+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:
+ // <element>1</element>
+ // <element>2</element>
+ // ...
+ // <element>6</element>
+ 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:
+ // <name>John</name>
+ // <age>31</age>
+ // <city>New York</city>
+ 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)
+ }
+ /**
+ <element>
+ <name>Ford</name>
+ <models>
+ <element>Fiesta</element>
+ <element>Focus</element>
+ <element>Mustang</element>
+ </models>
+ </element>
+ <element>
+ <name>BMW</name>
+ <models>
+ <element>320</element>
+ <element>X3</element>
+ <element>X5</element>
+ </models>
+ </element>
+ ....
+ */
+ 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())
+ }
+}