Skip to content

Commit

Permalink
add additional append/merge helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
devsergiy committed Aug 22, 2024
1 parent 074634c commit c1cca91
Show file tree
Hide file tree
Showing 4 changed files with 252 additions and 0 deletions.
8 changes: 8 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
module github.com/wundergraph/astjson

go 1.21

require github.com/stretchr/testify v1.9.0

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
156 changes: 156 additions & 0 deletions util.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package astjson

import (
"bytes"
"unsafe"
)

Expand All @@ -22,3 +23,158 @@ func startEndString(s string) string {
end := s[len(s)-40:]
return start + "..." + end
}

var (
NullValue = MustParse(`null`)
)

func MergeValues(a, b *Value) (*Value, bool) {
if a == nil {
return b, true
}
if b == nil {
return a, false
}
if a.Type() != b.Type() {
return a, false
}
switch a.Type() {
case TypeObject:
ao, _ := a.Object()
bo, _ := b.Object()
ao.Visit(func(key []byte, l *Value) {
sKey := b2s(key)
r := bo.Get(sKey)
if r == nil {
return
}
merged, changed := MergeValues(l, r)
if changed {
ao.Set(b2s(key), merged)
}
})
bo.Visit(func(key []byte, r *Value) {
sKey := b2s(key)
if ao.Get(sKey) != nil {
return
}
ao.Set(sKey, r)
})
return a, false
case TypeArray:
aa, _ := a.Array()
ba, _ := b.Array()
for i := 0; i < len(ba); i++ {
a.SetArrayItem(len(aa)+i, ba[i])
}
return a, false
case TypeFalse:
if b.Type() == TypeTrue {
return b, true
}
return a, false
case TypeTrue:
if b.Type() == TypeFalse {
return b, true
}
return a, false
case TypeNull:
if b.Type() != TypeNull {
return b, true
}
return a, false
case TypeNumber:
af, _ := a.Float64()
bf, _ := b.Float64()
if af != bf {
return b, true
}
return a, false
case TypeString:
as, _ := a.StringBytes()
bs, _ := b.StringBytes()
if !bytes.Equal(as, bs) {
return b, true
}
return a, false
default:
return b, true
}
}

func MergeValuesWithPath(a, b *Value, path ...string) (*Value, bool) {
if len(path) == 0 {
return MergeValues(a, b)
}
root := MustParseBytes([]byte(`{}`))
current := root
for i := 0; i < len(path)-1; i++ {
current.Set(path[i], MustParseBytes([]byte(`{}`)))
current = current.Get(path[i])
}
current.Set(path[len(path)-1], b)
return MergeValues(a, root)
}

func AppendToArray(array, value *Value) {
if array.Type() != TypeArray {
return
}
items, _ := array.Array()
array.SetArrayItem(len(items), value)
}

func SetValue(v *Value, value *Value, path ...string) {
for i := 0; i < len(path)-1; i++ {
parent := v
v = v.Get(path[i])
if v == nil {
child := MustParse(`{}`)
parent.Set(path[i], child)
v = child
}
}
v.Set(path[len(path)-1], value)
}

func SetNull(v *Value, path ...string) {
SetValue(v, MustParse(`null`), path...)
}

func ValueIsNonNull(v *Value) bool {
if v == nil {
return false
}
if v.Type() == TypeNull {
return false
}
return true
}

func ValueIsNull(v *Value) bool {
return !ValueIsNonNull(v)
}

func DeduplicateObjectKeysRecursively(v *Value) {
if v.Type() == TypeArray {
a := v.GetArray()
for _, e := range a {
DeduplicateObjectKeysRecursively(e)
}
}
if v.Type() != TypeObject {
return
}
o, _ := v.Object()
seen := make(map[string]struct{})
o.Visit(func(k []byte, v *Value) {
key := string(k)
if _, ok := seen[key]; ok {
o.Del(key)
return
} else {
seen[key] = struct{}{}
}
DeduplicateObjectKeysRecursively(v)
})
}
78 changes: 78 additions & 0 deletions util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package astjson

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestStartEndString(t *testing.T) {
Expand All @@ -28,3 +30,79 @@ func TestStartEndString(t *testing.T) {
f(getString(maxStartEndStringLen+1), "abcdefghijklmnopqrstuvwxyzabcdefghijklmn...pqrstuvwxyzabcdefghijklmnopqrstuvwxyzabc")
f(getString(100*maxStartEndStringLen), "abcdefghijklmnopqrstuvwxyzabcdefghijklmn...efghijklmnopqrstuvwxyzabcdefghijklmnopqr")
}

func TestMergeValues(t *testing.T) {
a, b := MustParse(`{"a":1}`), MustParse(`{"b":2}`)
merged, changed := MergeValues(a, b)
require.Equal(t, false, changed)
out := merged.MarshalTo(nil)
require.Equal(t, `{"a":1,"b":2}`, string(out))
out = merged.Get("b").MarshalTo(out[:0])
require.Equal(t, `2`, string(out))
}

func TestMergeValuesArray(t *testing.T) {
a, b := MustParse(`[1,2]`), MustParse(`[3,4]`)
merged, changed := MergeValues(a, b)
require.Equal(t, false, changed)
out := merged.MarshalTo(nil)
require.Equal(t, `[1,2,3,4]`, string(out))
}

func TestMergeValuesNestedObjects(t *testing.T) {
a, b := MustParse(`{"a":{"b":1}}`), MustParse(`{"a":{"c":2}}`)
merged, changed := MergeValues(a, b)
require.Equal(t, false, changed)
out := merged.MarshalTo(nil)
require.Equal(t, `{"a":{"b":1,"c":2}}`, string(out))
}

func TestMergeValuesWithPath(t *testing.T) {
a, b := MustParse(`{"a":{"b":1}}`), MustParse(`{"c":2}`)
merged, changed := MergeValuesWithPath(a, b, "a")
require.Equal(t, false, changed)
out := merged.MarshalTo(nil)
require.Equal(t, `{"a":{"b":1,"c":2}}`, string(out))
e := MustParse(`{"e":true}`)
merged, changed = MergeValuesWithPath(merged, e, "a", "d")
require.Equal(t, false, changed)
out = merged.MarshalTo(out[:0])
require.Equal(t, `{"a":{"b":1,"c":2,"d":{"e":true}}}`, string(out))
}

func TestGetArray(t *testing.T) {
a := MustParse(`[{"name":"Jens"},{"name":"Jannik"}]`)
arr, err := a.Array()
require.NoError(t, err)
require.Equal(t, 2, len(arr))
jens := arr[0].MarshalTo(nil)
require.Equal(t, `{"name":"Jens"}`, string(jens))
jannik := arr[1].MarshalTo(nil)
require.Equal(t, `{"name":"Jannik"}`, string(jannik))
}

func TestSetNull(t *testing.T) {
a := MustParse(`{"name":"Jens"}`)
SetNull(a, "name")
out := a.MarshalTo(nil)
require.Equal(t, `{"name":null}`, string(out))

b := MustParse(`{"person":{"name":"Jens"}}`)
SetNull(b, "person", "name")
out = b.MarshalTo(nil)
require.Equal(t, `{"person":{"name":null}}`, string(out))
}

func TestSetWithNonExistingPath(t *testing.T) {
a := MustParse(`{}`)
SetValue(a, MustParse(`1`), "a", "b")
out := a.MarshalTo(nil)
require.Equal(t, `{"a":{"b":1}}`, string(out))
}

func TestAppendToArray(t *testing.T) {
a := MustParse(`[1,2]`)
AppendToArray(a, MustParse(`3`))
out := a.MarshalTo(nil)
require.Equal(t, `[1,2,3]`, string(out))
}

0 comments on commit c1cca91

Please sign in to comment.