orderedkv: initial commit
This commit is contained in:
parent
4735c391bd
commit
8d051a14e5
85
sqliteclidriver/orderedkv.go
Normal file
85
sqliteclidriver/orderedkv.go
Normal file
@ -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
|
||||||
|
}
|
30
sqliteclidriver/orderedkv_test.go
Normal file
30
sqliteclidriver/orderedkv_test.go
Normal file
@ -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)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user