Skip to content

Commit 84021fd

Browse files
authored
JSON: Ensure all fields types work as expected when they are the first field in a frame (#1080)
1 parent 74f1b74 commit 84021fd

File tree

3 files changed

+125
-7
lines changed

3 files changed

+125
-7
lines changed

data/field_type.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,9 @@ const (
8181

8282
// FieldTypeJSON indicates the underlying primitive is a []json.RawMessage.
8383
FieldTypeJSON
84+
8485
// FieldTypeNullableJSON indicates the underlying primitive is a []*json.RawMessage.
86+
// Deprecated: Use FieldTypeJSON, an array can be null anyway
8587
FieldTypeNullableJSON
8688

8789
// FieldTypeEnum indicates the underlying primitive is a []data.EnumItemIndex, with field mapping metadata

data/frame_json.go

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,8 @@ func int64FromJSON(v interface{}) (int64, error) {
420420
return 0, fmt.Errorf("unable to convert int64 in json [%T]", v)
421421
}
422422

423+
// in this path, we do not yet know the length and must discover it from the array
424+
// nolint:gocyclo
423425
func jsonValuesToVector(iter *jsoniter.Iterator, ft FieldType) (vector, error) {
424426
itere := sdkjsoniter.NewIterator(iter)
425427
// we handle Uint64 differently because the regular method for unmarshalling to []any does not work for uint64 correctly
@@ -435,6 +437,7 @@ func jsonValuesToVector(iter *jsoniter.Iterator, ft FieldType) (vector, error) {
435437
return nil, err
436438
}
437439
return newUint64VectorWithValues(u), nil
440+
438441
case FieldTypeNullableUint64:
439442
parseUint64 := func(s string) (*uint64, error) {
440443
u, err := strconv.ParseUint(s, 0, 64)
@@ -448,9 +451,57 @@ func jsonValuesToVector(iter *jsoniter.Iterator, ft FieldType) (vector, error) {
448451
return nil, err
449452
}
450453
return newNullableUint64VectorWithValues(u), nil
454+
455+
case FieldTypeInt64:
456+
vals := newInt64Vector(0)
457+
for iter.ReadArray() {
458+
v := iter.ReadInt64()
459+
vals.Append(v)
460+
}
461+
return vals, nil
462+
463+
case FieldTypeNullableInt64:
464+
vals := newNullableInt64Vector(0)
465+
for iter.ReadArray() {
466+
t := iter.WhatIsNext()
467+
if t == sdkjsoniter.NilValue {
468+
iter.ReadNil()
469+
vals.Append(nil)
470+
} else {
471+
v := iter.ReadInt64()
472+
vals.Append(&v)
473+
}
474+
}
475+
return vals, nil
476+
477+
case FieldTypeJSON, FieldTypeNullableJSON:
478+
vals := newJsonRawMessageVector(0)
479+
for iter.ReadArray() {
480+
var v json.RawMessage
481+
t := iter.WhatIsNext()
482+
if t == sdkjsoniter.NilValue {
483+
iter.ReadNil()
484+
} else {
485+
iter.ReadVal(&v)
486+
}
487+
vals.Append(v)
488+
}
489+
490+
// Convert this to the pointer flavor
491+
if ft == FieldTypeNullableJSON {
492+
size := vals.Len()
493+
nullable := newNullableJsonRawMessageVector(size)
494+
for i := 0; i < size; i++ {
495+
v := vals.At(i).(json.RawMessage)
496+
nullable.Set(i, &v)
497+
}
498+
return nullable, nil
499+
}
500+
501+
return vals, nil
451502
}
452-
// if it's not uint64 field, handle the array the old way
453503

504+
// if it's not uint64 field, handle the array the old way
454505
convert := func(v interface{}) (interface{}, error) {
455506
return v, nil
456507
}
@@ -506,11 +557,6 @@ func jsonValuesToVector(iter *jsoniter.Iterator, ft FieldType) (vector, error) {
506557
return int32(iV), err
507558
}
508559

509-
case FieldTypeInt64:
510-
convert = func(v interface{}) (interface{}, error) {
511-
return int64FromJSON(v)
512-
}
513-
514560
case FieldTypeFloat32:
515561
convert = func(v interface{}) (interface{}, error) {
516562
fV, err := float64FromJSON(v)
@@ -1293,7 +1339,7 @@ func readTimeVectorJSON(iter *jsoniter.Iterator, nullable bool, size int) (vecto
12931339
} else {
12941340
ms := iter.ReadInt64()
12951341

1296-
tv := time.Unix(ms/int64(1e+3), (ms%int64(1e+3))*int64(1e+6))
1342+
tv := time.Unix(ms/int64(1e+3), (ms%int64(1e+3))*int64(1e+6)).UTC()
12971343
arr.SetConcrete(i, tv)
12981344
}
12991345
}

data/frame_json_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,76 @@ func TestGoldenFrameJSON(t *testing.T) {
6565
if diff := cmp.Diff(f, out, data.FrameTestCompareOptions()...); diff != "" {
6666
t.Errorf("Result mismatch (-want +got):\n%s", diff)
6767
}
68+
69+
// Now try each field as the only field in the frame
70+
for idx, field := range f.Fields {
71+
expected := out.Fields[idx]
72+
size := expected.Len()
73+
74+
frame := data.NewFrame("test-"+field.Name, field)
75+
b, err := data.FrameToJSON(frame, data.IncludeAll) // json.Marshal(f2)
76+
require.NoError(t, err)
77+
78+
f2 := &data.Frame{}
79+
err = json.Unmarshal(b, f2)
80+
require.NoError(t, err)
81+
82+
found := f2.Fields[0]
83+
require.Equal(t, size, found.Len())
84+
for i := 0; i < size; i++ {
85+
// Lots of NaN flavors that are really the same
86+
if expected.Type().Numeric() {
87+
fA, _ := expected.NullableFloatAt(i)
88+
fB, _ := found.NullableFloatAt(i)
89+
if fA != nil && fB != nil {
90+
if math.IsNaN(*fA) && math.IsNaN(*fB) {
91+
continue // many flavors of NaN!
92+
}
93+
}
94+
}
95+
96+
switch expected.Type() {
97+
case data.FieldTypeJSON, data.FieldTypeNullableJSON:
98+
msgA, okA := expected.ConcreteAt(i)
99+
msgB, okB := found.ConcreteAt(i)
100+
if okA && okB {
101+
a := string(msgA.(json.RawMessage))
102+
b := string(msgB.(json.RawMessage))
103+
require.JSONEq(t, a, b, field.Name) // pretty print does not matter
104+
continue
105+
} else if expected.Type() == data.FieldTypeNullableJSON {
106+
// The pointer conversions get too weird for this to be reasonable
107+
continue
108+
}
109+
}
110+
111+
assert.EqualValues(t, expected.At(i), found.At(i), "field: %s, row: %d", field.Name, i)
112+
}
113+
}
114+
}
115+
116+
func TestMarshalFieldTypeJSON(t *testing.T) {
117+
testJSON := func(ft data.FieldType) {
118+
msg := json.RawMessage([]byte(`{"l1":"v1"}`))
119+
f1 := data.NewFieldFromFieldType(ft, 1)
120+
f1.SetConcrete(0, msg)
121+
122+
f := data.NewFrame("frame", f1)
123+
124+
fbytes, err := json.Marshal(f)
125+
require.NoError(t, err)
126+
127+
// fmt.Println("frame in json:", string(fbytes))
128+
129+
f2 := &data.Frame{}
130+
err = json.Unmarshal(fbytes, f2)
131+
require.NoError(t, err)
132+
val, ok := f2.Fields[0].ConcreteAt(0)
133+
require.True(t, ok)
134+
require.Equal(t, msg, val)
135+
}
136+
testJSON(data.FieldTypeJSON)
137+
testJSON(data.FieldTypeNullableJSON) // this version is silly
68138
}
69139

70140
type simpleTestObj struct {

0 commit comments

Comments
 (0)