diff --git a/sqliteclidriver/orderedkv.go b/sqliteclidriver/orderedkv.go new file mode 100644 index 0000000..b6e90ef --- /dev/null +++ b/sqliteclidriver/orderedkv.go @@ -0,0 +1,85 @@ +package sqliteclidriver + +import ( + "bytes" + "encoding/json" + "fmt" +) + +type Pair struct { + Key string + Value any +} + +type OrderedKV []Pair + +func (o *OrderedKV) UnmarshalJSON(data []byte) error { + // Rough estimate malloc size based on number of `:` + // This is a lower bound since there might be nested elements + var elCount int64 + for _, c := range data { + if c == ':' { + elCount += 1 + } + } + + *o = make([]Pair, 0, elCount) + + // Parse the initial opening { delimiter from the JSON stream + + reader := bytes.NewReader(data) + dec := json.NewDecoder(reader) + + tok, err := dec.Token() + if err != nil { + return fmt.Errorf("expected '{': %w", err) + } + if d, ok := tok.(json.Delim); !ok || (rune(d) != '{') { + return fmt.Errorf("expected '{', got %v", tok) + } + + // Read remaining content + + for dec.More() { + + var p Pair + + // Parse key: either string or Delim('}') + tok, err := dec.Token() + if err != nil { + return fmt.Errorf("expected '{': %w", err) + } + switch tok := tok.(type) { + case json.Delim: + if rune(tok) == '}' { + // Finished + break + } + // Something else + return fmt.Errorf("expected string or }, got %v", tok) + + case string: + // Valid key + p.Key = tok + + default: + return fmt.Errorf("expected string or }, got %v", tok) + } + + // Parse value (any) + err = dec.Decode(&p.Value) + if err != nil { + return err + } + + *o = append(*o, p) + } + + // Assert that there is no remaining content + if reader.Len() != 0 { + return fmt.Errorf("Unexpected trailing data (%d bytes remaining)", reader.Len()) + } + + // Done + return nil +} diff --git a/sqliteclidriver/orderedkv_test.go b/sqliteclidriver/orderedkv_test.go new file mode 100644 index 0000000..5b50109 --- /dev/null +++ b/sqliteclidriver/orderedkv_test.go @@ -0,0 +1,30 @@ +package sqliteclidriver + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestOrderedKV(t *testing.T) { + + input := ` +{ + "zzz": "foo", + "aaa": "bar" +} +` + var got OrderedKV + err := json.Unmarshal([]byte(input), &got) + if err != nil { + t.Fatal(err) + } + + expect := OrderedKV{ + Pair{Key: "zzz", Value: "foo"}, + Pair{Key: "aaa", Value: "bar"}, + } + + require.EqualValues(t, expect, got) +}