From 095b24f53a6ea1fb07e169b48ad6b4e1f9139a6e Mon Sep 17 00:00:00 2001 From: adrianbrad Date: Tue, 14 Oct 2025 19:23:25 +0300 Subject: [PATCH] fix(deepobject): support nested objects in deepObject arrays --- deepobject.go | 18 +++++++--------- deepobject_test.go | 54 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 10 deletions(-) diff --git a/deepobject.go b/deepobject.go index 588465c0..394f5e4f 100644 --- a/deepobject.go +++ b/deepobject.go @@ -335,26 +335,24 @@ func assignPathValues(dst interface{}, pathValues fieldOrValue) error { } func assignSlice(dst reflect.Value, pathValues fieldOrValue) error { - // Gather up the values nValues := len(pathValues.fields) - values := make([]string, nValues) - // We expect to have consecutive array indices in the map + + // Process each array element by index for i := 0; i < nValues; i++ { indexStr := strconv.Itoa(i) fv, found := pathValues.fields[indexStr] if !found { return errors.New("array deepObjects must have consecutive indices") } - values[i] = fv.value - } - // This could be cleaner, but we can call into assignPathValues to - // avoid recreating this logic. - for i := 0; i < nValues; i++ { + // Get the destination element dstElem := dst.Index(i).Addr() - err := assignPathValues(dstElem.Interface(), fieldOrValue{value: values[i]}) + + // assignPathValues handles both simple values (via fv.value) and + // nested objects (via fv.fields) automatically + err := assignPathValues(dstElem.Interface(), fv) if err != nil { - return fmt.Errorf("error binding array: %w", err) + return fmt.Errorf("error binding array element %d: %w", i, err) } } diff --git a/deepobject_test.go b/deepobject_test.go index 237673ad..7490203f 100644 --- a/deepobject_test.go +++ b/deepobject_test.go @@ -84,3 +84,57 @@ func TestDeepObject(t *testing.T) { require.NoError(t, err) assert.EqualValues(t, srcObj, dstObj) } + +// Item represents an item object for testing array of objects +type Item struct { + Name string `json:"name"` + Value string `json:"value"` +} + +func TestDeepObject_ArrayOfObjects(t *testing.T) { + // Test case for: + // name: items + // style: deepObject + // required: false + // explode: true + // schema: + // type: array + // minItems: 1 + // items: + // type: object + + srcArray := []Item{ + {Name: "first", Value: "value1"}, + {Name: "second", Value: "value2"}, + } + + // Marshal the array to deepObject format + marshaled, err := MarshalDeepObject(srcArray, "items") + require.NoError(t, err) + t.Log("Marshaled:", marshaled) + + // Expected format for array of objects with explode:true should be: + // items[0][name]=first&items[0][value]=value1&items[1][name]=second&items[1][value]=value2 + + // Parse the marshaled string into url.Values + params := make(url.Values) + marshaledParts := strings.Split(marshaled, "&") + for _, p := range marshaledParts { + parts := strings.Split(p, "=") + require.Equal(t, 2, len(parts)) + params.Set(parts[0], parts[1]) + } + + // Unmarshal back to the destination array + var dstArray []Item + err = UnmarshalDeepObject(&dstArray, "items", params) + require.NoError(t, err) + + // Verify the result matches the source + assert.EqualValues(t, srcArray, dstArray) + assert.Len(t, dstArray, 2) + assert.Equal(t, "first", dstArray[0].Name) + assert.Equal(t, "value1", dstArray[0].Value) + assert.Equal(t, "second", dstArray[1].Name) + assert.Equal(t, "value2", dstArray[1].Value) +}