This is an automated email from the ASF dual-hosted git repository.

zeroshade pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/arrow.git


The following commit(s) were added to refs/heads/master by this push:
     new ccfae42b66 ARROW-16502: [Go] Accept missing optional fields when 
unmarshalling JSON in StructBuilder
ccfae42b66 is described below

commit ccfae42b66bce8f342eedc2f1c274e45331defc6
Author: Przemysław Kowolik <[email protected]>
AuthorDate: Thu May 12 13:47:56 2022 -0400

    ARROW-16502: [Go] Accept missing optional fields when unmarshalling JSON in 
StructBuilder
    
    When calling array.StructBuilder.UnmarshalJSON with a JSON object that has 
missing optional fields, it fails to decode the JSON object properly and will 
panic - but overall it's a common behavior to drop empty/null fields from the 
JSON
    
    Fix this by filling all missing optional fields with null values to prevent 
builder from panic
    
    Closes #13097 from Kowol/bufix/struct-unmarshal-optional-fields
    
    Authored-by: Przemysław Kowolik <[email protected]>
    Signed-off-by: Matthew Topol <[email protected]>
---
 go/arrow/array/struct.go      | 12 +++++++
 go/arrow/array/struct_test.go | 81 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 93 insertions(+)

diff --git a/go/arrow/array/struct.go b/go/arrow/array/struct.go
index 8abf9d0af8..052fabb7fd 100644
--- a/go/arrow/array/struct.go
+++ b/go/arrow/array/struct.go
@@ -334,6 +334,18 @@ func (b *StructBuilder) unmarshalOne(dec *json.Decoder) 
error {
                                return err
                        }
                }
+
+               // Append null values to all optional fields that were not 
presented in the json input
+               for _, field := range b.dtype.(*arrow.StructType).Fields() {
+                       if !field.Nullable {
+                               continue
+                       }
+                       idx, _ := 
b.dtype.(*arrow.StructType).FieldIdx(field.Name)
+                       if _, hasKey := keylist[field.Name]; !hasKey {
+                               b.fields[idx].AppendNull()
+                       }
+               }
+
                // consume '}'
                _, err := dec.Token()
                return err
diff --git a/go/arrow/array/struct_test.go b/go/arrow/array/struct_test.go
index 70c18a5b18..d029da1f28 100644
--- a/go/arrow/array/struct_test.go
+++ b/go/arrow/array/struct_test.go
@@ -404,3 +404,84 @@ func TestStructArrayNullBitmap(t *testing.T) {
                t.Fatalf("invalid string representation:\ngot = %q\nwant= %q", 
got, want)
        }
 }
+
+func TestStructArrayUnmarshalJSONMissingFields(t *testing.T) {
+       pool := memory.NewGoAllocator()
+
+       var (
+               fields = []arrow.Field{
+                       {Name: "f1", Type: arrow.PrimitiveTypes.Float64, 
Nullable: true},
+                       {Name: "f2", Type: arrow.PrimitiveTypes.Int32},
+                       {
+                               Name: "f3", Type: arrow.StructOf(
+                                       []arrow.Field{
+                                               {Name: "f3_1", Type: 
arrow.BinaryTypes.String, Nullable: true},
+                                               {Name: "f3_2", Type: 
arrow.BinaryTypes.String, Nullable: true},
+                                               {Name: "f3_3", Type: 
arrow.BinaryTypes.String, Nullable: false},
+                                       }...,
+                               ),
+                       },
+               }
+               dtype = arrow.StructOf(fields...)
+       )
+
+       sb := array.NewStructBuilder(pool, dtype)
+       defer sb.Release()
+
+       tests := map[string]struct {
+               jsonInput string
+               want      string
+               panic     bool
+       }{
+               "missing optional fields": {
+                       jsonInput: `[{"f2": 3, "f3": {"f3_3": "test"}}]`,
+                       panic:     false,
+                       want:      `{[(null)] [3] {[(null)] [(null)] 
["test"]}}`,
+               },
+               "missing required field": {
+                       jsonInput: `[{"f2": 3, "f3": {"f3_1": "test"}}]`,
+                       panic:     true,
+                       want:      "",
+               },
+       }
+
+       for name, tc := range tests {
+               t.Run(
+                       name, func(t *testing.T) {
+
+                               var val bool
+
+                               if tc.panic {
+                                       defer func() {
+                                               e := recover()
+                                               if e == nil {
+                                                       t.Fatalf("this should 
have panicked, but did not; slice value %v", val)
+                                               }
+                                               if got, want := e.(string), 
"arrow/array: index out of range"; got != want {
+                                                       t.Fatalf("invalid 
error. got=%q, want=%q", got, want)
+                                               }
+                                       }()
+                               } else {
+                                       defer func() {
+                                               if e := recover(); e != nil {
+                                                       t.Fatalf("unexpected 
panic: %v", e)
+                                               }
+                                       }()
+                               }
+
+                               err := sb.UnmarshalJSON([]byte(tc.jsonInput))
+                               if err != nil {
+                                       t.Fatal(err)
+                               }
+
+                               arr := sb.NewArray().(*array.Struct)
+                               defer arr.Release()
+
+                               got := arr.String()
+                               if got != tc.want {
+                                       t.Fatalf("invalid string 
representation:\ngot = %q\nwant= %q", got, tc.want)
+                               }
+                       },
+               )
+       }
+}

Reply via email to