diff options
| author | Felix Hanley <felix@userspace.com.au> | 2018-11-16 06:28:50 +0000 |
|---|---|---|
| committer | Felix Hanley <felix@userspace.com.au> | 2018-11-16 06:28:50 +0000 |
| commit | d6882bd9403c588415c1906a7015d16e92aa1ad3 (patch) | |
| tree | 86f8c25049454e9d199c8f114f8e9dc52b19c2ba /json | |
| parent | dc0a50a47d1ad749efa298dcdcd732daa33388c9 (diff) | |
| download | query-d6882bd9403c588415c1906a7015d16e92aa1ad3.tar.gz query-d6882bd9403c588415c1906a7015d16e92aa1ad3.tar.bz2 | |
Add node data types and tests
Diffstat (limited to 'json')
| -rw-r--r-- | json/node.go | 40 | ||||
| -rw-r--r-- | json/node_test.go | 177 |
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()) + } +} |
