94 lines
2.3 KiB
Go
94 lines
2.3 KiB
Go
package mujica
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"regexp"
|
|
"strconv"
|
|
)
|
|
|
|
// jsonNode is one value of a parsed document, in document order.
|
|
type jsonNode struct {
|
|
key, path string
|
|
// value is a scalar's JSON text, or "{}" / "[]" for containers.
|
|
value string
|
|
depth int
|
|
parent int
|
|
// end is the index after this node's subtree; count is its child count.
|
|
end, count int
|
|
}
|
|
|
|
func (n jsonNode) container() bool { return n.value == "{}" || n.value == "[]" }
|
|
|
|
func (n jsonNode) label() string {
|
|
if n.key == "" {
|
|
return n.value
|
|
}
|
|
return n.key + ": " + n.value
|
|
}
|
|
|
|
// jsonTree is a document flattened depth-first.
|
|
type jsonTree struct{ nodes []jsonNode }
|
|
|
|
var jsonIdent = regexp.MustCompile(`^[A-Za-z_$][A-Za-z0-9_$]*$`)
|
|
|
|
// parseJSONTree flattens data keeping key order; it fails on invalid or trailing JSON.
|
|
func parseJSONTree(data string) (jsonTree, error) {
|
|
dec := json.NewDecoder(bytes.NewReader([]byte(data)))
|
|
dec.UseNumber()
|
|
var t jsonTree
|
|
if err := jsonParseValue(dec, &t, "", "$", 0, -1); err != nil {
|
|
return jsonTree{}, err
|
|
}
|
|
if _, err := dec.Token(); err != io.EOF {
|
|
return jsonTree{}, fmt.Errorf("unexpected data after the value")
|
|
}
|
|
return t, nil
|
|
}
|
|
|
|
func jsonParseValue(dec *json.Decoder, t *jsonTree, key, path string, depth, parent int) error {
|
|
tok, err := dec.Token()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
at := len(t.nodes)
|
|
t.nodes = append(t.nodes, jsonNode{key: key, path: path, depth: depth, parent: parent})
|
|
switch v := tok.(type) {
|
|
case json.Delim:
|
|
t.nodes[at].value = map[json.Delim]string{'{': "{}", '[': "[]"}[v]
|
|
for i := 0; dec.More(); i++ {
|
|
childKey, childPath := "", fmt.Sprintf("%s[%d]", path, i)
|
|
if v == '{' {
|
|
kt, err := dec.Token()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
childKey = kt.(string)
|
|
childPath = path + "." + childKey
|
|
if !jsonIdent.MatchString(childKey) {
|
|
childPath = path + "[" + strconv.Quote(childKey) + "]"
|
|
}
|
|
}
|
|
if err := jsonParseValue(dec, t, childKey, childPath, depth+1, at); err != nil {
|
|
return err
|
|
}
|
|
t.nodes[at].count++
|
|
}
|
|
if _, err := dec.Token(); err != nil { // the closing delimiter
|
|
return err
|
|
}
|
|
case string:
|
|
t.nodes[at].value = strconv.Quote(v)
|
|
case json.Number:
|
|
t.nodes[at].value = v.String()
|
|
case bool:
|
|
t.nodes[at].value = strconv.FormatBool(v)
|
|
case nil:
|
|
t.nodes[at].value = "null"
|
|
}
|
|
t.nodes[at].end = len(t.nodes)
|
|
return nil
|
|
}
|