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) +		} +	}  } | 
