diff options
author | Charlie Stanton <charlie@shtanton.xyz> | 2024-03-24 19:18:58 +0000 |
---|---|---|
committer | Charlie Stanton <charlie@shtanton.xyz> | 2024-03-24 19:18:58 +0000 |
commit | 81dcb87b2158f625ca10a20df5a93a42bbcaf26b (patch) | |
tree | 24d80db1428e5a61d2d96d9c929eaa82b1e40b92 /json | |
parent | 0e4179fabbd0a66826f1375dae86ca7f681fb29d (diff) | |
download | stred-go-81dcb87b2158f625ca10a20df5a93a42bbcaf26b.tar |
Implements helper function navigateTo in json/write.go
Diffstat (limited to 'json')
-rw-r--r-- | json/read.go | 4 | ||||
-rw-r--r-- | json/write.go | 303 | ||||
-rw-r--r-- | json/write_test.go | 254 |
3 files changed, 348 insertions, 213 deletions
diff --git a/json/read.go b/json/read.go index 8ed7e96..f3a0a65 100644 --- a/json/read.go +++ b/json/read.go @@ -42,10 +42,8 @@ func NewJSONReader(reader *bufio.Reader) *JSONReader { } } -type PathSegment interface {} - type JSONReader struct { - path []PathSegment + path []walk.PathSegment structure []JSONReaderStructure state JSONReaderState reader *bufio.Reader diff --git a/json/write.go b/json/write.go index 9e349be..97b3f4e 100644 --- a/json/write.go +++ b/json/write.go @@ -4,25 +4,26 @@ import ( "bufio" "fmt" "main/walk" + // "text/scanner" ) -func isNumber(value walk.Value) bool { - _, isFloat := value.(walk.NumberScalar) - return isFloat +func isInt(segment walk.PathSegment) bool { + _, isInt := segment.(int) + return isInt } -func isString(value walk.Value) bool { - _, isString := value.(walk.StringStructure) +func isString(segment walk.PathSegment) bool { + _, isString := segment.(string) return isString } -func segmentEqual(left walk.Value, right walk.Value) bool { +func segmentEqual(left walk.PathSegment, right walk.PathSegment) bool { switch left := left.(type) { - case walk.NumberScalar: - _, isNumber := right.(walk.NumberScalar) - return isNumber - case walk.StringStructure: - right, isString := right.(walk.StringStructure) + case int: + _, isInt := right.(int) + return isInt + case string: + right, isString := right.(string) return isString && left == right default: panic("Invalid path segment type") @@ -32,7 +33,7 @@ func segmentEqual(left walk.Value, right walk.Value) bool { type JSONWriterState int const ( JSONWriterStateBeforeValue JSONWriterState = iota - JSONWriterStateAfterValue JSONWriterState = iota + JSONWriterStateAfterValue JSONWriterStateInArray JSONWriterStateInMap ) @@ -46,29 +47,243 @@ func NewJSONWriter(writer *bufio.Writer) *JSONWriter { } type JSONWriter struct { - path []walk.Value + path []walk.PathSegment writer *bufio.Writer state JSONWriterState } -func (writer *JSONWriter) Write(item walk.WalkItem) error { - path := item.Path - for _, value := range item.Value { - err := writer.write(path, value) - if err != nil { - return err +func (writer *JSONWriter) navigateTo(keepLen int, path []walk.PathSegment, state JSONWriterState) { + for { + if keepLen > len(writer.path) { + panic("keepLen > len(writer.path)") + } else if len(writer.path) == keepLen { + if len(path) == 0 { + switch writer.state { + case JSONWriterStateBeforeValue: + switch state { + case JSONWriterStateBeforeValue: + return + case JSONWriterStateAfterValue: + panic("Cannot go from BeforeValue to AfterValue in navigateTo") + case JSONWriterStateInArray: + writer.writer.WriteRune('[') + writer.state = JSONWriterStateInArray + case JSONWriterStateInMap: + writer.writer.WriteRune('{') + writer.state = JSONWriterStateInMap + } + case JSONWriterStateAfterValue: + if state == JSONWriterStateAfterValue { + return + } else { + if keepLen == 0 { + writer.writer.WriteRune('\n') + writer.state = JSONWriterStateBeforeValue + } else { + writer.writer.WriteRune(',') + path = writer.path[len(writer.path) - 1:] + writer.path = writer.path[:len(writer.path) - 1] + keepLen -= 1 + switch path[0].(type) { + case string: + writer.state = JSONWriterStateInMap + case int: + writer.state = JSONWriterStateInArray + } + } + } + case JSONWriterStateInArray: + if state == JSONWriterStateInArray { + return + } else { + writer.writer.WriteRune(']') + writer.state = JSONWriterStateAfterValue + } + case JSONWriterStateInMap: + if state == JSONWriterStateInMap { + return + } else { + writer.writer.WriteRune('}') + writer.state = JSONWriterStateAfterValue + } + } + } else { + // len(path) > 0 + switch writer.state { + case JSONWriterStateBeforeValue: + switch path[0].(type) { + case string: + writer.writer.WriteRune('{') + writer.state = JSONWriterStateInMap + case int: + writer.writer.WriteRune('[') + writer.state = JSONWriterStateInArray + } + case JSONWriterStateAfterValue: + if keepLen == 0 { + writer.writer.WriteRune('\n') + writer.state = JSONWriterStateBeforeValue + } else { + writer.writer.WriteRune(',') + path = append(writer.path[len(writer.path) - 1:], path...) + writer.path = writer.path[:len(writer.path) - 1] + keepLen -= 1 + switch path[0].(type) { + case string: + writer.state = JSONWriterStateInMap + case int: + writer.state = JSONWriterStateInArray + } + } + case JSONWriterStateInArray: + switch path[0].(type) { + case string: + writer.writer.WriteRune(']') + writer.state = JSONWriterStateAfterValue + case int: + writer.path = append(writer.path, path[0]) + path = path[1:] + keepLen += 1 + writer.state = JSONWriterStateBeforeValue + } + case JSONWriterStateInMap: + switch p := path[0].(type) { + case string: + fmt.Fprintf(writer.writer, "%q:", p) + writer.path = append(writer.path, p) + path = path[1:] + keepLen += 1 + writer.state = JSONWriterStateBeforeValue + case int: + writer.writer.WriteRune('}') + writer.state = JSONWriterStateAfterValue + } + } + } + } else { + switch writer.state { + case JSONWriterStateBeforeValue: + panic("Cannot close structures from BeforeValue in navigateTo") + case JSONWriterStateAfterValue: + if len(writer.path) == keepLen + 1 { + if len(path) == 0 { + switch writer.path[len(writer.path) - 1].(type) { + case string: + if state == JSONWriterStateInMap { + writer.writer.WriteRune(',') + writer.path = writer.path[:len(writer.path) - 1] + writer.state = JSONWriterStateInMap + } else { + writer.writer.WriteRune('}') + writer.path = writer.path[:len(writer.path) - 1] + } + case int: + if state == JSONWriterStateInArray { + writer.writer.WriteRune(',') + writer.path = writer.path[:len(writer.path) - 1] + writer.state = JSONWriterStateInArray + } else { + writer.writer.WriteRune(']') + writer.path = writer.path[:len(writer.path) - 1] + } + } + } else { + switch writer.path[len(writer.path) - 1].(type) { + case string: + switch path[0].(type) { + case string: + writer.writer.WriteRune(',') + writer.path = writer.path[:len(writer.path) - 1] + writer.state = JSONWriterStateInMap + case int: + writer.writer.WriteRune('}') + writer.path = writer.path[:len(writer.path) - 1] + } + case int: + switch path[0].(type) { + case string: + writer.writer.WriteRune(']') + writer.path = writer.path[:len(writer.path) - 1] + case int: + writer.writer.WriteRune(',') + writer.path = writer.path[:len(writer.path) - 1] + writer.state = JSONWriterStateInArray + } + } + } + } else { + switch writer.path[len(writer.path) - 1].(type) { + case string: + writer.writer.WriteRune('}') + writer.path = writer.path[:len(writer.path) - 1] + case int: + writer.writer.WriteRune(']') + writer.path = writer.path[:len(writer.path) - 1] + } + } + case JSONWriterStateInArray: + writer.writer.WriteRune(']') + writer.state = JSONWriterStateAfterValue + case JSONWriterStateInMap: + writer.writer.WriteRune('}') + writer.state = JSONWriterStateAfterValue + } } } +} + +func (writer *JSONWriter) Write(value walk.Value) error { return nil } +func (writer *JSONWriter) pathWrite(value walk.Value, path []walk.PathSegment) error { + switch value := value.(type) { + case walk.NullValue: + return writer.write(path, value) + case walk.BoolValue: + return writer.write(path, value) + case walk.NumberValue: + return writer.write(path, value) + case walk.StringValue: + return writer.write(path, value) + case walk.ArrayValue: + if len(value) == 0 { + return writer.write(path, value) + } else { + for _, element := range value { + err := writer.pathWrite(element.Value, append(path, element.Index)) + if err != nil { + return err + } + } + return nil + } + case walk.MapValue: + if len(value) == 0 { + return writer.write(path, value) + } else { + for _, element := range value { + err := writer.pathWrite(element.Value, append(path, element.Key)) + if err != nil { + return err + } + } + return nil + } + case walk.RuneValue: + panic("Cannot write rune value") + default: + panic("Unrecognised value type") + } +} + func (writer *JSONWriter) indent(level int) { for i := 0; i < level; i += 1 { writer.writer.WriteRune('\t') } } -func (writer *JSONWriter) write(targetPath []walk.Value, value walk.Value) error { +func (writer *JSONWriter) write(targetPath []walk.PathSegment, value walk.Value) error { diversionPoint := 0 for diversionPoint < len(writer.path) && diversionPoint < len(targetPath) && segmentEqual(writer.path[diversionPoint], targetPath[diversionPoint]) { diversionPoint += 1 @@ -92,10 +307,10 @@ func (writer *JSONWriter) write(targetPath []walk.Value, value walk.Value) error if diversionPoint < len(targetPath) { segment := targetPath[diversionPoint] switch segment.(type) { - case walk.NumberScalar: + case int: writer.writer.WriteString("[\n") goto inArray - case walk.StringStructure: + case string: writer.writer.WriteString("{\n") goto inMap default: @@ -104,11 +319,11 @@ func (writer *JSONWriter) write(targetPath []walk.Value, value walk.Value) error } switch value := value.(type) { - case walk.NullScalar: + case walk.NullValue: writer.writer.WriteString("null") writer.state = JSONWriterStateAfterValue return nil - case walk.BoolScalar: + case walk.BoolValue: if value { writer.writer.WriteString("true") } else { @@ -116,20 +331,20 @@ func (writer *JSONWriter) write(targetPath []walk.Value, value walk.Value) error } writer.state = JSONWriterStateAfterValue return nil - case walk.NumberScalar: + case walk.NumberValue: writer.writer.WriteString(fmt.Sprintf("%v", value)) writer.state = JSONWriterStateAfterValue return nil - case walk.StringStructure: + case walk.StringValue: writer.writer.WriteString(fmt.Sprintf("%q", value)) writer.state = JSONWriterStateAfterValue return nil - case walk.ArrayStructure: + case walk.ArrayValue: // TODO: write the contents of the structures writer.writer.WriteString("[\n") writer.state = JSONWriterStateInArray return nil - case walk.MapStructure: + case walk.MapValue: writer.writer.WriteString("{\n") writer.state = JSONWriterStateInMap return nil @@ -143,15 +358,15 @@ func (writer *JSONWriter) write(targetPath []walk.Value, value walk.Value) error if diversionPoint == len(writer.path) - 1 && diversionPoint < len(targetPath) { segment := writer.path[diversionPoint] switch segment.(type) { - case walk.NumberScalar: - _, isNumber := targetPath[diversionPoint].(walk.NumberScalar) + case int: + _, isNumber := targetPath[diversionPoint].(walk.NumberValue) if isNumber { writer.writer.WriteString(",\n") writer.path = writer.path[:diversionPoint] goto inArray } - case walk.StringStructure: - _, isString := targetPath[diversionPoint].(walk.StringStructure) + case string: + _, isString := targetPath[diversionPoint].(walk.StringValue) if isString { writer.writer.WriteString(",\n") writer.path = writer.path[:diversionPoint] @@ -164,10 +379,10 @@ func (writer *JSONWriter) write(targetPath []walk.Value, value walk.Value) error writer.writer.WriteString("\n") switch writer.path[len(writer.path) - 1].(type) { - case walk.NumberScalar: + case int: writer.path = writer.path[:len(writer.path) - 1] goto inArray - case walk.StringStructure: + case string: writer.path = writer.path[:len(writer.path) - 1] goto inMap default: @@ -186,9 +401,9 @@ func (writer *JSONWriter) write(targetPath []walk.Value, value walk.Value) error diversionPoint-- writer.path = writer.path[:diversionPoint] switch segment.(type) { - case walk.NumberScalar: + case int: goto inArray - case walk.StringStructure: + case string: goto inMap default: panic("Invalid segment type") @@ -204,9 +419,9 @@ func (writer *JSONWriter) write(targetPath []walk.Value, value walk.Value) error diversionPoint-- writer.path = writer.path[:diversionPoint] switch segment.(type) { - case walk.NumberScalar: + case int: goto inArray - case walk.StringStructure: + case string: goto inMap default: panic("Invalid segment type") @@ -222,12 +437,12 @@ func (writer *JSONWriter) write(targetPath []walk.Value, value walk.Value) error if diversionPoint < len(targetPath) { switch s := targetPath[diversionPoint].(type) { - case walk.NumberScalar: + case int: writer.path = append(writer.path, s) diversionPoint++ writer.indent(len(writer.path)) goto beforeValue - case walk.StringStructure: + case string: writer.indent(len(writer.path)) writer.writer.WriteString("]") goto afterValue @@ -250,11 +465,11 @@ func (writer *JSONWriter) write(targetPath []walk.Value, value walk.Value) error if diversionPoint < len(targetPath) { switch s := targetPath[diversionPoint].(type) { - case walk.NumberScalar: + case int: writer.indent(len(writer.path)) writer.writer.WriteString("}") goto afterValue - case walk.StringStructure: + case string: writer.path = append(writer.path, s) diversionPoint++ writer.indent(len(writer.path)) @@ -278,11 +493,11 @@ func (writer *JSONWriter) AssertDone() { } for i := len(writer.path) - 1; i >= 0; i -= 1 { switch writer.path[i].(type) { - case walk.NumberScalar: + case int: writer.writer.WriteString("\n") writer.indent(i) writer.writer.WriteString("]") - case walk.StringStructure: + case string: writer.writer.WriteString("\n") writer.indent(i) writer.writer.WriteString("}") diff --git a/json/write_test.go b/json/write_test.go index 60ad609..508ccfa 100644 --- a/json/write_test.go +++ b/json/write_test.go @@ -5,179 +5,101 @@ import ( "main/walk" "strings" "testing" - "encoding/json" ) -type writeTester struct { - writer *JSONWriter - output *strings.Builder - t *testing.T -} - -func (t writeTester) write(value walk.Value, path ...interface{}) writeTester { - var pathValues []walk.Value - for _, segment := range path { - switch s := segment.(type) { - case int: - pathValues = append(pathValues, walk.NumberScalar(s)) - case string: - pathValues = append(pathValues, walk.StringStructure(s)) - default: - panic("Invalid path segment type") - } - } - - t.writer.Write(walk.WalkItem { - Value: []walk.Value{value}, - Path: pathValues, - }) - - return t -} - -func (t writeTester) expect(expected interface{}) writeTester { - t.writer.AssertDone() - output := t.output.String() - var actual interface{} - err := json.Unmarshal([]byte(output), &actual) - if err != nil { - t.t.Log("Produced invalid JSON:") - t.t.Log(output) - t.t.FailNow() - } - - expectedBytes, err1 := json.Marshal(expected) - actualBytes, err2 := json.Marshal(actual) - - if err1 != nil || err2 != nil { - panic("Error marshalling") - } - - expectedString := string(expectedBytes) - actualString := string(actualBytes) - - if expectedString != actualString { - t.t.Log("Expected:") - t.t.Log(expectedString) - t.t.Log("Found:") - t.t.Log(actualString) - t.t.FailNow() +func TestNavigateTo(t *testing.T) { + type testCase struct { + fromPath []walk.PathSegment + fromState JSONWriterState + toKeep int + toPath []walk.PathSegment + toState JSONWriterState + expected string } - return t -} - -func tester(t *testing.T) writeTester { - var output strings.Builder - return writeTester { - writer: NewJSONWriter(bufio.NewWriter(&output)), - output: &output, - t: t, - } -} - -func TestImplicitStructures(t *testing.T) { - tester(t).write( - walk.NullScalar{}, - 0, "test", 0, - ).expect( - []interface{}{ - map[string]interface{}{ - "test": []interface{}{ - nil, - }, - }, + tests := []testCase { + { + fromPath: []walk.PathSegment{}, + fromState: JSONWriterStateBeforeValue, + toKeep: 0, + toPath: []walk.PathSegment{"a", "b", "c"}, + toState: JSONWriterStateBeforeValue, + expected: `{"a":{"b":{"c":`, }, - ) -} - -func TestExplicitMap(t *testing.T) { - tester(t).write( - make(walk.MapStructure), - ).write( - walk.NullScalar{}, - "test", - ).expect( - map[string]interface{}{ - "test": nil, + { + fromPath: []walk.PathSegment{}, + fromState: JSONWriterStateBeforeValue, + toKeep: 0, + toPath: []walk.PathSegment{0, "a", "a", 0, 1}, + toState: JSONWriterStateInMap, + expected: `[{"a":{"a":[[{`, }, - ) -} - -func TestExplicitNested(t *testing.T) { - tester(t).write( - make(walk.MapStructure), - ).write( - make(walk.MapStructure), - "first", - ).write( - make(walk.MapStructure), - "first", "second", - ).write( - walk.StringStructure("test"), - "first", "second", "third", - ).expect( - map[string]interface{}{ - "first": map[string]interface{}{ - "second": map[string]interface{}{ - "third": "test", - }, - }, + { + fromPath: []walk.PathSegment{}, + fromState: JSONWriterStateInMap, + toKeep: 0, + toPath: []walk.PathSegment{}, + toState: JSONWriterStateInArray, + expected: "}\n[", }, - ) -} - -func TestArrayOfMaps(t *testing.T) { - tester(t).write( - walk.ArrayStructure{}, - ).write( - make(walk.MapStructure), - 0, - ).write( - walk.NumberScalar(0), - 0, "number", - ).write( - make(walk.MapStructure), - 1, - ).write( - walk.NumberScalar(1), - 1, "nested", "number", - ).write( - make(walk.MapStructure), - 2, - ).write( - walk.NumberScalar(2), - 2, "number", - ).expect( - []interface{}{ - map[string]interface{}{ - "number": 0, - }, - map[string]interface{}{ - "nested": map[string]interface{}{ - "number": 1, - }, - }, - map[string]interface{}{ - "number": 2, - }, + { + fromPath: []walk.PathSegment{0, 0}, + fromState: JSONWriterStateBeforeValue, + toKeep: 2, + toPath: []walk.PathSegment{"a"}, + toState: JSONWriterStateInArray, + expected: `{"a":[`, }, - ) -} - -func TestStructures1(t *testing.T) { - tester(t).write( - make(walk.MapStructure), - ).write( - make(walk.MapStructure), - "map", - ).write( - walk.ArrayStructure{}, - "array", - ).expect( - map[string]interface{}{ - "map": map[string]interface{}{}, - "array": []interface{}{}, + { + fromPath: []walk.PathSegment{"a", "b"}, + fromState: JSONWriterStateAfterValue, + toKeep: 1, + toPath: []walk.PathSegment{"c"}, + toState: JSONWriterStateBeforeValue, + expected: `,"c":`, }, - ) + { + fromPath: []walk.PathSegment{0, "a"}, + fromState: JSONWriterStateInArray, + toKeep: 0, + toPath: []walk.PathSegment{"b", 1}, + toState: JSONWriterStateInMap, + expected: `]}]` + "\n" + `{"b":[{`, + }, + { + fromPath: []walk.PathSegment{"a", "b", "c", "d", "e"}, + fromState: JSONWriterStateAfterValue, + toKeep: 2, + toPath: []walk.PathSegment{"f", "g", "h"}, + toState: JSONWriterStateBeforeValue, + expected: `}},"f":{"g":{"h":`, + }, + { + fromPath: []walk.PathSegment{"a", 0, "b"}, + fromState: JSONWriterStateAfterValue, + toKeep: 2, + toPath: []walk.PathSegment{0}, + toState: JSONWriterStateBeforeValue, + expected: `},[`, + }, + } + + for i, test := range tests { + var writer strings.Builder + jsonWriter := &JSONWriter { + path: test.fromPath, + writer: bufio.NewWriter(&writer), + state: test.fromState, + } + jsonWriter.navigateTo( + test.toKeep, + test.toPath, + test.toState, + ) + jsonWriter.writer.Flush() + res := writer.String() + if res != test.expected { + t.Errorf(`Test %d: Expected '%s' found '%s'`, i, test.expected, res) + } + } } |