Skip to content

Commit 73aa53b

Browse files
feat: allow find or set value in JSON using path like a.b.c.
1 parent a55d5d1 commit 73aa53b

File tree

3 files changed

+88
-1
lines changed

3 files changed

+88
-1
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ A [Tinybird](https://www.tinybird.co/) module for Go. Why need this module? It p
77
- Lightweight and fast.
88
- Native Go implementation. No C-bindings, just pure Go
99
- Connection pooling for HTTP.
10+
- Read and write values in nested JSON structures using path-based access.
1011
- Test your code with mocks.
1112
- Allow JSON, [NDJSON](http://ndjson.org/) and CSV between tinybird and this module.
1213
- Parallelize HTTP requests.

data.go

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package tinybird
22

3-
import "encoding/json"
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"strings"
7+
)
48

59
// Row is a generic map-based structure that can hold fields of any type.
610
// Each key corresponds to a field name, and each value can be any Go type.
@@ -41,3 +45,63 @@ func (d Data) ToString() (out string) {
4145

4246
return string(tmp)
4347
}
48+
49+
func (d Data) Get(in string) (out any) {
50+
if len(d) == 0 {
51+
return nil
52+
}
53+
54+
parts := strings.Split(in, ".")
55+
out = d[0]
56+
57+
for _, part := range parts {
58+
row, ok := out.(Row)
59+
if !ok {
60+
return nil
61+
}
62+
63+
out, ok = row[part]
64+
if !ok {
65+
return nil
66+
}
67+
}
68+
69+
return out
70+
}
71+
72+
func (d *Data) Set(in string, value any) error {
73+
if d == nil || len(*d) == 0 {
74+
return errors.New("empty Data")
75+
}
76+
77+
parts := strings.Split(in, ".")
78+
if len(parts) == 0 {
79+
return errors.New("empty path")
80+
}
81+
82+
current := (*d)[0]
83+
84+
for i, p := range parts {
85+
if i == len(parts)-1 {
86+
current[p] = value
87+
return nil
88+
}
89+
90+
next, exists := current[p]
91+
if !exists {
92+
child := Row{}
93+
current[p] = child
94+
current = child
95+
continue
96+
}
97+
98+
child, ok := next.(Row)
99+
if !ok {
100+
return errors.New("path collision at " + p)
101+
}
102+
103+
current = child
104+
}
105+
106+
return nil
107+
}

data_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,25 @@ func TestToString_OnNilAndEmpty(t *testing.T) {
7070
assert.Equal(t, "null", dNil.ToString())
7171
assert.Equal(t, "[]", dEmpty.ToString())
7272
}
73+
74+
func TestGet(t *testing.T) {
75+
d := tinybird.Data{
76+
{"a": tinybird.Row{"b": 1}},
77+
{"b": "ok"},
78+
}
79+
80+
assert.Nil(t, d.Get(""))
81+
assert.Nil(t, d.Get("ab"))
82+
assert.Equal(t, tinybird.Row(tinybird.Row{"b": 1}), d.Get("a"))
83+
assert.Equal(t, 1, d.Get("a.b"))
84+
}
85+
86+
func TestSet(t *testing.T) {
87+
d := tinybird.Data{
88+
{"a": tinybird.Row{"b": 1}},
89+
{"b": "ok"},
90+
}
91+
92+
assert.Nil(t, d.Set("a.b", 2))
93+
assert.Equal(t, 2, d.Get("a.b"))
94+
}

0 commit comments

Comments
 (0)