package walk import ( "io" "encoding/json" "fmt" ) type PathSegment interface {} type Path []PathSegment type TerminalValue int const ( ArrayBegin TerminalValue = iota ArrayEnd MapBegin MapEnd ) func (value TerminalValue) Pieces(out chan<- Datum) { out<-value } type ValueNull struct {} func (value ValueNull) Pieces(out chan<- Datum) { out<-value } type ValueBool bool func (value ValueBool) Pieces(out chan<- Datum) { out<-value } type ValueNumber float64 func (value ValueNumber) Pieces(out chan<- Datum) { out<-value } type StartString struct {} type EndString struct {} type ValueString string func (value ValueString) Pieces(out chan<- Datum) { out<-StartString{} for _, char := range value { out<-char } out<-EndString{} } type Datum interface {} type WalkValue interface { Pieces(out chan<- Datum) } 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.Value } 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") }