diff options
Diffstat (limited to 'main/json.go')
-rw-r--r-- | main/json.go | 299 |
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") +} |