Skip to content

fix(gconv): route map-slice elements through MapToMap in Structs (#4787) - #4815

Open
waterWang wants to merge 1 commit into
gogf:masterfrom
waterWang:fix/map-slice-structs
Open

fix(gconv): route map-slice elements through MapToMap in Structs (#4787)#4815
waterWang wants to merge 1 commit into
gogf:masterfrom
waterWang:fix/map-slice-structs

Conversation

@waterWang

Copy link
Copy Markdown
Contributor

Fix: Result.Structs / Model.Scan into *[]map[string]interface{} silently returns empty maps

Description

When db.Model().Scan(&rows) or Result.Structs(&rows) is called with *[]map[string]interface{}, it returns a slice of the correct length but every element is an empty map, with no error returned. Switching to a typed struct slice, or using Result.List() / gconv.Scan(), works correctly.

Root Cause

In converter.Structs (converter_structs.go), the destination element type is checked:

if itemTypeKind == reflect.Pointer {
    // Pointer element — handles correctly
} else {
    // "Struct element" — taken for reflect.Map too
    ...
    c.Struct(paramsList[i], tempReflectValue, ...)  // expects a struct!
}

c.Struct calls GetCachedStructInfo() which returns nil for non-struct types (like reflect.Map), causing a silent no-op. The pre-allocated slice (correct length) is returned with each map left at its zero value.

By contrast, gconv.Scanconverter.ScandoScanForComplicatedTypes does have the map-slice branch (sliceElemKind == reflect.MapMapToMaps), which is why gconv.Scan and Result.List() work correctly.

Fix

Added a reflect.Map branch in converter.Structs that routes map-typed elements through c.MapToMap instead of c.Struct:

} else if itemTypeKind == reflect.Map {
    // Map element — use MapToMap instead of Struct.
    for i := 0; i < len(paramsList); i++ {
        ...
        c.MapToMap(paramsList[i], tempReflectValue, nil, MapOption{...})
        ...
    }
} else {
    // Struct element (unchanged)
    ...
}

This matches the existing behavior of gconv.Scan's doScanForComplicatedTypes method, which already handles map-slice conversion via MapToMaps.

Testing

The reproduction case from the issue now produces correct output:

res := gdb.Result{
    gdb.Record{"regionId": gvar.New(1)},
    gdb.Record{"regionId": gvar.New(2)},
    gdb.Record{"regionId": gvar.New(3)},
}
var viaStructs []map[string]interface{}
_ = res.Structs(&viaStructs)
// Before: len=3, first=map[]   (empty maps, no error)
// After:  len=3, first=map[regionId:1]  (correct data)

Fixes #4787

…f#4787)

When Result.Structs / Model.Scan is called with a *[]map[string]interface{} pointer,
the destination element type is reflect.Map, but converter.Structs routes it through
c.Struct which expects a struct type. This causes GetCachedStructInfo to return nil
for non-struct types, silently producing empty maps.

Fix: add a reflect.Map branch that calls c.MapToMap for each element instead of c.Struct.
This matches the behavior of gconv.Scan which already handles map-slice conversion via
MapToMaps.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

gdb: Result.Structs / Model.Scan into *[]map[string]interface{} silently returns empty maps instead of data or error

1 participant