Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed bug with certain types of map keys and updated ConvertAnyToString internal function #4

Merged
merged 1 commit into from
Jun 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,11 @@ Output maps are keyed by either the exported field name directly OR by the use o
In the case of slices, maps, or other embedded/nested structures, the output maps keys are "namespaced" in the following ways:
* **Structures**: The embedded/nested structure name will prepend the inner fields as `[parentFieldName].[childFieldName] => value`.
* **Maps**: Data pulled from maps will appears as `[mapFieldName].[mapKeyToString] => [value]`.
* If the map key is unable to be directly converted to a string, the key will come back as the type name as seen via reflection.
* If the map key is a pointer, it is dereferenced until we get to the core value.
* A nil pointer will ultimately bubble up as the string constant `DEFAULT_SUBKEY_STRING` wrapped in squre brackets (ex. with no convert options): `[emptyKey]`.
* If the map key is otherwise unable to be directly converted to a string, the key will come back as the type name as seen via reflection.
* In the event the map key is a `float` (of any type) or `complex64`/`complex128`, the conversion function to string uses the '`g`' modifier with a precision of -1 (see notes on https://pkg.go.dev/strconv#FormatFloat and https://pkg.go.dev/strconv#FormatComplex).
* In all cases, these keys are also subject to the conversion options (above).
* **Slices**: Data pulled form slices will appear as `[sliceFieldName].[sliceIndex] => [value]`.

As the amount of nesting increases, so does the namespacing; for example:
Expand Down
3 changes: 3 additions & 0 deletions internal/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ func ConvertAnyToString(val any) string {

for {
if valOf.Kind() == reflect.Pointer {
if reflect.Value(valOf).IsNil() {
return ""
}
valOf = valOf.Elem()
continue
}
Expand Down
18 changes: 17 additions & 1 deletion pkg/struct2map.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ STRUCT_MEMBER_PROC:
return ret
}

const DEFAULT_SUBKEY_STRING = "emptyKey"

func fieldToMap(dest map[string]any, parentKeyName, mapKeyName string, workingField reflect.Value, omitEmpty bool, nameModFunc func(string) string) {
for {
if workingField.Kind() == reflect.Pointer {
Expand Down Expand Up @@ -185,7 +187,21 @@ func fieldToMap(dest map[string]any, parentKeyName, mapKeyName string, workingFi
dest[k] = v
}
} else {
dest[fmt.Sprintf("%s.%s", keyName, internal.ConvertAnyToString(mapItr.Key().Interface()))] = mapVal.Interface()
needBrkt := false
subKey := internal.ConvertAnyToString(mapItr.Key().Interface())
if subKey == "" {
subKey = DEFAULT_SUBKEY_STRING
needBrkt = true
}
if nameModFunc != nil {
subKey = nameModFunc(subKey)
}

if needBrkt {
dest[fmt.Sprintf("%s.[%s]", keyName, subKey)] = mapVal.Interface()
} else {
dest[fmt.Sprintf("%s.%s", keyName, subKey)] = mapVal.Interface()
}
}
}
case reflect.Slice:
Expand Down
57 changes: 38 additions & 19 deletions pkg/struct2map_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type complexTestStruct struct {
MapFieldIntKey map[int]string `struct2map:"mapFieldIntKey"`
MapFieldStrKeyStructVal map[string]simpleTestStruct `struct2map:"mapFieldStrKeyStructVal"`
MapFieldStrKeyStructPtrVal map[string]*simpleTestStruct `struct2map:"mapFieldStrKeyStructPtrVal"`
MapFieldPointerKey map[*string]string `struct2map:"mapFieldPointerKey"`
}

type complexTestStructEmbed struct {
Expand Down Expand Up @@ -73,6 +74,8 @@ func Test_RegularCases(t *testing.T) {
}
testStructPtr := &testStruct

testStrKey := "testKey"

testSet := []struct {
Name string
TestStructure any
Expand Down Expand Up @@ -110,6 +113,7 @@ func Test_RegularCases(t *testing.T) {
MapFieldIntKey: map[int]string{1: "test1", 2: "test2"},
MapFieldStrKeyStructVal: map[string]simpleTestStruct{"simpleStruct1": *testStructPtr},
MapFieldStrKeyStructPtrVal: map[string]*simpleTestStruct{"simpleStructPtr1": testStructPtr},
MapFieldPointerKey: map[*string]string{nil: "testing1", &testStrKey: "testing2"},
},
ExpectedMap: map[string]any{
"mapFieldIntKey.1": "test1",
Expand All @@ -126,13 +130,15 @@ func Test_RegularCases(t *testing.T) {
"mapFieldStrKeyStructVal.regularField": 1,
"mapFieldStrKeyStructVal.regularFieldOmitEmpty": 1,
"mapFieldStrKeyStructVal.regularFieldPointerPointer": 1,
"sliceField.0": 1,
"sliceField.1": 1,
"sliceField.2": 1,
"sliceFieldPtrVal.0": 1,
"sliceFieldPtrVal.1": 1,
"sliceFieldPtrVal.2": 1,
"topLevelBool": true,
"sliceField.0": 1,
"sliceField.1": 1,
"sliceField.2": 1,
"sliceFieldPtrVal.0": 1,
"sliceFieldPtrVal.1": 1,
"sliceFieldPtrVal.2": 1,
"topLevelBool": true,
"mapFieldPointerKey.[emptyKey]": "testing1",
"mapFieldPointerKey.testKey": "testing2",
},
},
{
Expand Down Expand Up @@ -391,6 +397,8 @@ func Test_MapKeyOptions(t *testing.T) {
RegularFieldPointerPointer: &simpleIntPtr,
}

testStrKey := "testKey"

testStructPtr := &complexTestStructEmbed{
ComplexTestStruct: complexTestStruct{
TopLevelField: true,
Expand All @@ -401,6 +409,7 @@ func Test_MapKeyOptions(t *testing.T) {
MapFieldIntKey: map[int]string{1: "test1", 2: "test2"},
MapFieldStrKeyStructVal: map[string]simpleTestStruct{"test1": simpleStruct},
MapFieldStrKeyStructPtrVal: map[string]*simpleTestStruct{"testPtr1": &simpleStruct},
MapFieldPointerKey: map[*string]string{nil: "testing1", &testStrKey: "testing2"},
},
AnonStruct: struct {
RegStruct simpleTestStruct
Expand Down Expand Up @@ -450,6 +459,8 @@ func Test_MapKeyOptions(t *testing.T) {
"complexteststruct.slicefield.2": 3,
"complexteststruct.slicefieldptrval.0": 1,
"complexteststruct.toplevelfield": true,
"complexteststruct.mapfieldpointerkey.[emptykey]": "testing1",
"complexteststruct.mapfieldpointerkey.testkey": "testing2",
},
},
{
Expand All @@ -467,10 +478,10 @@ func Test_MapKeyOptions(t *testing.T) {
"ANONSTRUCT.REGSTRUCTPTR.REGULARFIELDPOINTERPOINTER": 1,
"COMPLEXTESTSTRUCT.MAPFIELDINTKEY.1": "test1",
"COMPLEXTESTSTRUCT.MAPFIELDINTKEY.2": "test2",
"COMPLEXTESTSTRUCT.MAPFIELDSTRKEY.test1": 1,
"COMPLEXTESTSTRUCT.MAPFIELDSTRKEY.test2": 2,
"COMPLEXTESTSTRUCT.MAPFIELDSTRKEY.test3": 3,
"COMPLEXTESTSTRUCT.MAPFIELDSTRKEYPTRVAL.test1": 1,
"COMPLEXTESTSTRUCT.MAPFIELDSTRKEY.TEST1": 1,
"COMPLEXTESTSTRUCT.MAPFIELDSTRKEY.TEST2": 2,
"COMPLEXTESTSTRUCT.MAPFIELDSTRKEY.TEST3": 3,
"COMPLEXTESTSTRUCT.MAPFIELDSTRKEYPTRVAL.TEST1": 1,
"COMPLEXTESTSTRUCT.MAPFIELDSTRKEYSTRUCTPTRVAL.REGULARFIELDNAMETAG": 1,
"COMPLEXTESTSTRUCT.MAPFIELDSTRKEYSTRUCTPTRVAL.REGULARFIELDNOTAG": 1,
"COMPLEXTESTSTRUCT.MAPFIELDSTRKEYSTRUCTPTRVAL.REGULARFIELDOMITEMPTY": 1,
Expand All @@ -484,6 +495,8 @@ func Test_MapKeyOptions(t *testing.T) {
"COMPLEXTESTSTRUCT.SLICEFIELD.2": 3,
"COMPLEXTESTSTRUCT.SLICEFIELDPTRVAL.0": 1,
"COMPLEXTESTSTRUCT.TOPLEVELFIELD": true,
"COMPLEXTESTSTRUCT.MAPFIELDPOINTERKEY.[EMPTYKEY]": "testing1",
"COMPLEXTESTSTRUCT.MAPFIELDPOINTERKEY.TESTKEY": "testing2",
},
},
{
Expand All @@ -501,10 +514,10 @@ func Test_MapKeyOptions(t *testing.T) {
"AnonStruct.RegStructPtr.RegularFieldPointerPointer": 1,
"ComplexTestStruct.MapFieldIntKey.1": "test1",
"ComplexTestStruct.MapFieldIntKey.2": "test2",
"ComplexTestStruct.MapFieldStrKey.test1": 1,
"ComplexTestStruct.MapFieldStrKey.test2": 2,
"ComplexTestStruct.MapFieldStrKey.test3": 3,
"ComplexTestStruct.MapFieldStrKeyPtrVal.test1": 1,
"ComplexTestStruct.MapFieldStrKey.Test1": 1,
"ComplexTestStruct.MapFieldStrKey.Test2": 2,
"ComplexTestStruct.MapFieldStrKey.Test3": 3,
"ComplexTestStruct.MapFieldStrKeyPtrVal.Test1": 1,
"ComplexTestStruct.MapFieldStrKeyStructPtrVal.RegularFieldNameTag": 1,
"ComplexTestStruct.MapFieldStrKeyStructPtrVal.RegularFieldNoTag": 1,
"ComplexTestStruct.MapFieldStrKeyStructPtrVal.RegularFieldOmitEmpty": 1,
Expand All @@ -518,6 +531,8 @@ func Test_MapKeyOptions(t *testing.T) {
"ComplexTestStruct.SliceField.2": 3,
"ComplexTestStruct.SliceFieldPtrVal.0": 1,
"ComplexTestStruct.TopLevelField": true,
"ComplexTestStruct.MapFieldPointerKey.[EmptyKey]": "testing1",
"ComplexTestStruct.MapFieldPointerKey.TestKey": "testing2",
},
},
{
Expand Down Expand Up @@ -552,6 +567,8 @@ func Test_MapKeyOptions(t *testing.T) {
"complexTestStruct.sliceField.2": 3,
"complexTestStruct.sliceFieldPtrVal.0": 1,
"complexTestStruct.topLevelField": true,
"complexTestStruct.mapFieldPointerKey.[emptyKey]": "testing1",
"complexTestStruct.mapFieldPointerKey.testKey": "testing2",
},
},
{
Expand All @@ -569,10 +586,10 @@ func Test_MapKeyOptions(t *testing.T) {
"anon_struct.reg_struct_ptr.regular_field_pointer_pointer": 1,
"complex_test_struct.map_field_int_key.1": "test1",
"complex_test_struct.map_field_int_key.2": "test2",
"complex_test_struct.map_field_str_key.test1": 1,
"complex_test_struct.map_field_str_key.test2": 2,
"complex_test_struct.map_field_str_key.test3": 3,
"complex_test_struct.map_field_str_key_ptr_val.test1": 1,
"complex_test_struct.map_field_str_key.test_1": 1,
"complex_test_struct.map_field_str_key.test_2": 2,
"complex_test_struct.map_field_str_key.test_3": 3,
"complex_test_struct.map_field_str_key_ptr_val.test_1": 1,
"complex_test_struct.map_field_str_key_struct_ptr_val.regular_field_name_tag": 1,
"complex_test_struct.map_field_str_key_struct_ptr_val.regular_field_no_tag": 1,
"complex_test_struct.map_field_str_key_struct_ptr_val.regular_field_omit_empty": 1,
Expand All @@ -586,6 +603,8 @@ func Test_MapKeyOptions(t *testing.T) {
"complex_test_struct.slice_field.2": 3,
"complex_test_struct.slice_field_ptr_val.0": 1,
"complex_test_struct.top_level_field": true,
"complex_test_struct.map_field_pointer_key.[empty_key]": "testing1",
"complex_test_struct.map_field_pointer_key.test_key": "testing2",
},
},
}
Expand Down
Loading