Skip to content

Commit b70cbfb

Browse files
authored
add support for marshaling zng.Value fields (#2693)
This commit adds support for marshaling and unmarshaling zng Values that are embedded in Go structs. This allows us to encode arbitrary zng.Values as part of native Go data structures, e.g., to encode the upper/lower range boundary as zng.Values in the metadata struct of an arbitrary Zed lake data object. While here, we also improved the error message when trying to decode a non-record Zed value into a Go struct.
1 parent 8a46395 commit b70cbfb

File tree

3 files changed

+90
-5
lines changed

3 files changed

+90
-5
lines changed

zson/marshal.go

+26-5
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ func (m *MarshalZNGContext) NamedBindings(bindings []Binding) error {
275275
}
276276

277277
var nanoTsType = reflect.TypeOf(nano.Ts(0))
278+
var zngValueType = reflect.TypeOf(zng.Value{})
278279

279280
func (m *MarshalZNGContext) encodeValue(v reflect.Value) (zng.Type, error) {
280281
typ, err := m.encodeAny(v)
@@ -292,8 +293,10 @@ func (m *MarshalZNGContext) encodeValue(v reflect.Value) (zng.Type, error) {
292293
kind := v.Kind().String()
293294
if name != "" && name != kind {
294295
// We do not want to further decorate nano.Ts as
295-
// it's already been converted to a Zed time.
296-
if v.Type() == nanoTsType {
296+
// it's already been converted to a Zed time;
297+
// likewise for zng.Value, which gets encoded as
298+
// itself and its own alias type if it has one.
299+
if t := v.Type(); t == nanoTsType || t == zngValueType {
297300
return typ, nil
298301
}
299302
path := v.Type().PkgPath()
@@ -320,10 +323,22 @@ func (m *MarshalZNGContext) encodeAny(v reflect.Value) (zng.Type, error) {
320323
if v.Type().Implements(marshalerTypeZNG) {
321324
return v.Interface().(ZNGMarshaler).MarshalZNG(m)
322325
}
323-
if v, ok := v.Interface().(nano.Ts); ok {
324-
m.Builder.AppendPrimitive(zng.EncodeTime(v))
326+
if ts, ok := v.Interface().(nano.Ts); ok {
327+
m.Builder.AppendPrimitive(zng.EncodeTime(ts))
325328
return zng.TypeTime, nil
326329
}
330+
if zv, ok := v.Interface().(zng.Value); ok {
331+
typ, err := m.TranslateType(zv.Type)
332+
if err != nil {
333+
return nil, err
334+
}
335+
if zng.IsContainerType(typ) {
336+
m.Builder.AppendContainer(zv.Bytes)
337+
} else {
338+
m.Builder.AppendPrimitive(zv.Bytes)
339+
}
340+
return typ, nil
341+
}
327342
switch v.Kind() {
328343
case reflect.Array:
329344
if v.Type().Elem().Kind() == reflect.Uint8 {
@@ -609,6 +624,12 @@ func (u *UnmarshalZNGContext) decodeAny(zv zng.Value, v reflect.Value) error {
609624
v.Set(reflect.ValueOf(x))
610625
return err
611626
}
627+
if _, ok := v.Interface().(zng.Value); ok {
628+
// For zng.Values we simply set the reflect value to the
629+
// zng.Value that has been decoded.
630+
v.Set(reflect.ValueOf(zv))
631+
return nil
632+
}
612633
switch v.Kind() {
613634
case reflect.Array:
614635
return u.decodeArray(zv, v)
@@ -750,7 +771,7 @@ func (u *UnmarshalZNGContext) decodeIP(zv zng.Value, v reflect.Value) error {
750771
func (u *UnmarshalZNGContext) decodeRecord(zv zng.Value, sval reflect.Value) error {
751772
recType, ok := zng.AliasOf(zv.Type).(*zng.TypeRecord)
752773
if !ok {
753-
return errors.New("not a record")
774+
return fmt.Errorf("cannot unmarshal Zed type %q into Go struct", FormatType(zv.Type))
754775
}
755776
nameToField := make(map[string]int)
756777
stype := sval.Type()

zson/marshal_test.go

+47
Original file line numberDiff line numberDiff line change
@@ -235,3 +235,50 @@ func TestUnexported(t *testing.T) {
235235
_, err := m.Marshal(f)
236236
require.NoError(t, err)
237237
}
238+
239+
type ZNGValueField struct {
240+
Name string
241+
Field zng.Value `zng:"field"`
242+
}
243+
244+
func TestZNGValueField(t *testing.T) {
245+
// Include a Zed int64 inside a Go struct as a zng.Value field.
246+
zngValueField := &ZNGValueField{
247+
Name: "test1",
248+
Field: zng.Value{zng.TypeInt64, zng.EncodeInt(123)},
249+
}
250+
m := zson.NewZNGMarshaler()
251+
m.Decorate(zson.StyleSimple)
252+
zv, err := m.Marshal(zngValueField)
253+
require.NoError(t, err)
254+
expected := `{Name:"test1",field:123} (=ZNGValueField)`
255+
actual, err := zson.FormatValue(zv)
256+
require.NoError(t, err)
257+
assert.Equal(t, trim(expected), trim(actual))
258+
u := zson.NewZNGUnmarshaler()
259+
var out ZNGValueField
260+
err = u.Unmarshal(zv, &out)
261+
require.NoError(t, err)
262+
assert.Equal(t, *zngValueField, out)
263+
// Include a Zed record inside a Go struct in a zng.Value field.
264+
z := `{s:"foo",a:[1,2,3]}`
265+
zv2, err := zson.ParseValue(zson.NewContext(), z)
266+
require.NoError(t, err)
267+
zngValueField2 := &ZNGValueField{
268+
Name: "test2",
269+
Field: zv2,
270+
}
271+
m2 := zson.NewZNGMarshaler()
272+
m2.Decorate(zson.StyleSimple)
273+
zv3, err := m2.Marshal(zngValueField2)
274+
require.NoError(t, err)
275+
expected2 := `{Name:"test2",field:{s:"foo",a:[1,2,3]}} (=ZNGValueField)`
276+
actual2, err := zson.FormatValue(zv3)
277+
require.NoError(t, err)
278+
assert.Equal(t, trim(expected2), trim(actual2))
279+
u2 := zson.NewZNGUnmarshaler()
280+
var out2 ZNGValueField
281+
err = u2.Unmarshal(zv3, &out2)
282+
require.NoError(t, err)
283+
assert.Equal(t, *zngValueField2, out2)
284+
}

zson/zson.go

+17
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"strings"
1111

1212
"github.com/brimdata/zed/compiler/ast/zed"
13+
"github.com/brimdata/zed/zcode"
1314
"github.com/brimdata/zed/zng"
1415
)
1516

@@ -68,6 +69,22 @@ func ParseType(zctx *Context, zson string) (zng.Type, error) {
6869
return NewAnalyzer().convertType(zctx, ast)
6970
}
7071

72+
func ParseValue(zctx *Context, zson string) (zng.Value, error) {
73+
zp, err := NewParser(strings.NewReader(zson))
74+
if err != nil {
75+
return zng.Value{}, err
76+
}
77+
ast, err := zp.ParseValue()
78+
if err != nil {
79+
return zng.Value{}, err
80+
}
81+
val, err := NewAnalyzer().ConvertValue(zctx, ast)
82+
if err != nil {
83+
return zng.Value{}, err
84+
}
85+
return Build(zcode.NewBuilder(), val)
86+
}
87+
7188
func TranslateType(zctx *Context, astType zed.Type) (zng.Type, error) {
7289
return NewAnalyzer().convertType(zctx, astType)
7390
}

0 commit comments

Comments
 (0)