From 3bd45dc49a35b82dcc4ae93796c3e152d799bc0b Mon Sep 17 00:00:00 2001 From: Charlie Stanton Date: Sun, 19 Feb 2023 09:27:55 +0000 Subject: Move JSON serialising, deserialising and walking code into a separate package --- main/json.go | 293 ----------------------------------------------------------- 1 file changed, 293 deletions(-) (limited to 'main/json.go') diff --git a/main/json.go b/main/json.go index 77c3733..06ab7d0 100644 --- a/main/json.go +++ b/main/json.go @@ -1,294 +1 @@ package main - -import ( - "io" - "encoding/json" - "fmt" -) - -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") -} -- cgit v1.2.3