<- Back to shtanton's homepage
aboutsummaryrefslogtreecommitdiff
path: root/main/json.go
diff options
context:
space:
mode:
Diffstat (limited to 'main/json.go')
-rw-r--r--main/json.go299
1 files changed, 299 insertions, 0 deletions
diff --git a/main/json.go b/main/json.go
new file mode 100644
index 0000000..66ca5d5
--- /dev/null
+++ b/main/json.go
@@ -0,0 +1,299 @@
+package main
+
+import (
+ "io"
+ "encoding/json"
+ "fmt"
+)
+
+type WalkItem struct {
+ value WalkValue
+ path Path
+}
+
+type WalkItemStream struct {
+ channel chan WalkItem
+ rewinds []WalkItem
+}
+
+func (stream *WalkItemStream) next() (WalkItem, bool) {
+ if len(stream.rewinds) == 0 {
+ item, hasItem := <- stream.channel
+ return item, hasItem
+ }
+ item := stream.rewinds[len(stream.rewinds)-1]
+ stream.rewinds = stream.rewinds[0:len(stream.rewinds)-1]
+ return item, true
+}
+
+func (stream *WalkItemStream) rewind(item WalkItem) {
+ stream.rewinds = append(stream.rewinds, item)
+}
+
+func (stream *WalkItemStream) peek() (WalkItem, bool) {
+ item, hasItem := stream.next()
+ if !hasItem {
+ return item, false
+ }
+ stream.rewind(item)
+ return item, true
+}
+
+func tokenToValue(token json.Token) WalkValue {
+ switch token.(type) {
+ case nil:
+ return ValueNull {}
+ case bool:
+ return ValueBool(token.(bool))
+ case float64:
+ return ValueNumber(token.(float64))
+ case string:
+ return ValueString(token.(string))
+ default:
+ panic("Can't convert JSON token to value")
+ }
+}
+
+func readValue(dec *json.Decoder, path Path, out chan WalkItem) bool {
+ if !dec.More() {
+ return true
+ }
+ t, err := dec.Token()
+ if err == io.EOF {
+ return true
+ } else if err != nil {
+ panic("Invalid JSON")
+ }
+ switch t.(type) {
+ case nil, string, float64, bool:
+ v := tokenToValue(t)
+ out <- WalkItem {v, path}
+ return false
+ case json.Delim:
+ switch rune(t.(json.Delim)) {
+ case '[':
+ out <- WalkItem {ArrayBegin, path}
+ index := 0
+ for dec.More() {
+ empty := readValue(dec, append(path, index), out)
+ if empty {
+ break
+ }
+ index += 1
+ }
+ t, err := dec.Token()
+ if err != nil {
+ panic("Invalid JSON")
+ }
+ delim, isDelim := t.(json.Delim)
+ if !isDelim || delim != ']' {
+ panic("Expected ] in JSON")
+ }
+ out <- WalkItem{ArrayEnd, path}
+ return false
+ case '{':
+ out <- WalkItem {MapBegin, path}
+ for dec.More() {
+ t, _ := dec.Token()
+ key, keyIsString := t.(string)
+ if !keyIsString {
+ panic("Invalid JSON")
+ }
+ empty := readValue(dec, append(path, key), out)
+ if empty {
+ panic("Invalid JSON")
+ }
+ }
+ t, err := dec.Token()
+ if err != nil {
+ panic("Invalid JSON")
+ }
+ delim, isDelim := t.(json.Delim)
+ if !isDelim || delim != '}' {
+ panic("Expected } in JSON")
+ }
+ out <- WalkItem {MapEnd, path}
+ return false
+ default:
+ panic("Error parsing JSON")
+ }
+ default:
+ panic("Invalid JSON token")
+ }
+}
+
+func startWalk(dec *json.Decoder, out chan WalkItem) {
+ isEmpty := readValue(dec, nil, out)
+ if isEmpty {
+ panic("Missing JSON input")
+ }
+ close(out)
+}
+
+func Json(r io.Reader) chan WalkItem {
+ dec := json.NewDecoder(r)
+ out := make(chan WalkItem)
+ go startWalk(dec, out)
+ return out
+}
+
+func printIndent(indent int) {
+ for i := 0; i < indent; i += 1 {
+ fmt.Print("\t")
+ }
+}
+
+func jsonOutArray(in *WalkItemStream, indent int) {
+ fmt.Println("[")
+ token, hasToken := in.next()
+ if !hasToken {
+ panic("Missing ] in output JSON")
+ }
+ terminal, isTerminal := token.value.(TerminalValue)
+ if isTerminal && terminal == ArrayEnd {
+ fmt.Print("\n")
+ printIndent(indent)
+ fmt.Print("]")
+ return
+ }
+ in.rewind(token)
+ for {
+ valueToken := jsonOutValue(in, indent + 1, true)
+ if valueToken != nil {
+ panic("Missing value in output JSON array")
+ }
+ token, hasToken := in.next()
+ if !hasToken {
+ panic("Missing ] in output JSON")
+ }
+ terminal, isTerminal := token.value.(TerminalValue)
+ if isTerminal && terminal == ArrayEnd {
+ fmt.Print("\n")
+ printIndent(indent)
+ fmt.Print("]")
+ return
+ }
+ in.rewind(token)
+ fmt.Println(",")
+ }
+}
+
+func jsonOutMap(in *WalkItemStream, indent int) {
+ fmt.Println("{")
+ token, hasToken := in.next()
+ if !hasToken {
+ panic("Missing } in output JSON")
+ }
+ terminal, isTerminal := token.value.(TerminalValue)
+ if isTerminal && terminal == MapEnd {
+ fmt.Print("\n")
+ printIndent(indent)
+ fmt.Print("}")
+ return
+ }
+ in.rewind(token)
+ for {
+ keyToken, hasKeyToken := in.peek()
+ if !hasKeyToken {
+ panic("Missing map element")
+ }
+ printIndent(indent + 1)
+ if len(keyToken.path) == 0 {
+ panic("Map element missing key")
+ }
+ key := keyToken.path[len(keyToken.path)-1]
+ switch key.(type) {
+ case int:
+ fmt.Print(key.(int))
+ case string:
+ fmt.Printf("%q", key.(string))
+ default:
+ panic("Invalid path segment")
+ }
+ fmt.Print(": ")
+ valueToken := jsonOutValue(in, indent + 1, false)
+ if valueToken != nil {
+ panic("Missing value int output JSON map")
+ }
+ token, hasToken := in.next()
+ if !hasToken {
+ panic("Missing } in output JSON")
+ }
+ terminal, isTerminal := token.value.(TerminalValue)
+ if isTerminal && terminal == MapEnd {
+ fmt.Print("\n")
+ printIndent(indent)
+ fmt.Print("}")
+ return
+ }
+ in.rewind(token)
+ fmt.Println(",")
+ }
+}
+
+func jsonOutValue(in *WalkItemStream, indent int, doIndent bool) WalkValue {
+ token, hasToken := in.next()
+ if !hasToken {
+ panic("Missing JSON token in output")
+ }
+ switch token.value.(type) {
+ case ValueNull:
+ if doIndent {
+ printIndent(indent)
+ }
+ fmt.Printf("null")
+ return nil
+ case ValueBool:
+ if doIndent {
+ printIndent(indent)
+ }
+ if token.value.(ValueBool) {
+ fmt.Print("true")
+ } else {
+ fmt.Print("false")
+ }
+ return nil
+ case ValueNumber:
+ if doIndent {
+ printIndent(indent)
+ }
+ fmt.Printf("%v", token.value)
+ return nil
+ case ValueString:
+ if doIndent {
+ printIndent(indent)
+ }
+ fmt.Printf("%q", token.value)
+ return nil
+ case TerminalValue:
+ switch token.value.(TerminalValue) {
+ case ArrayBegin:
+ if doIndent {
+ printIndent(indent)
+ }
+ jsonOutArray(in, indent)
+ return nil
+ case MapBegin:
+ if doIndent {
+ printIndent(indent)
+ }
+ jsonOutMap(in, indent)
+ return nil
+ default:
+ return token
+ }
+ default:
+ panic("Invalid WalkValue")
+ }
+}
+
+func JsonOut(in chan WalkItem) {
+ stream := WalkItemStream {
+ channel: in,
+ rewinds: nil,
+ }
+ if jsonOutValue(&stream, 0, true) != nil {
+ panic("Invalid output JSON")
+ }
+ fmt.Print("\n")
+}