Skip to content

Commit 94e87cb

Browse files
committed
Add support for map conversion
Mapper.MapAny now supports map conversion. `map[string]any` instances are converted to map[string]interface{}. During conversion usual callbacks are executed: Filter, Rename and MapValue.
1 parent 4b9e9f0 commit 94e87cb

File tree

4 files changed

+416
-194
lines changed

4 files changed

+416
-194
lines changed

README.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
[![Go Report Card](https://goreportcard.com/badge/github.com/elgopher/mapify)](https://goreportcard.com/report/github.com/elgopher/mapify)
77
[![codecov](https://codecov.io/gh/elgopher/mapify/branch/master/graph/badge.svg)](https://codecov.io/gh/elgopher/mapify)
88

9-
**Highly configurable** struct to map converter. _Will convert maps into other maps as well (work in progress)._
9+
**Highly configurable** struct to map converter. Converts `map[string]any` into other maps as well.
1010

1111
## Features
1212

@@ -55,4 +55,12 @@ func main() {
5555
type SomeStruct struct {
5656
Field string
5757
}
58-
```
58+
```
59+
60+
## MapAny algorithm
61+
62+
1. Take an object which is a struct, map or slice.
63+
2. Traverse entire object looking for nested structs or maps.
64+
3. **Filter** elements (struct fields, map keys)
65+
4. **Rename** field names or map keys
66+
5. **Map** (struct field or map values)

_examples/maps/main.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/elgopher/mapify"
8+
)
9+
10+
// This example shows how to filter maps and rename keys
11+
func main() {
12+
s := map[string]interface{}{
13+
"key": "value",
14+
"another": "another value",
15+
}
16+
17+
mapper := mapify.Mapper{
18+
Filter: func(path string, e mapify.Element) (bool, error) {
19+
return path == ".key", nil
20+
},
21+
Rename: func(path string, e mapify.Element) (string, error) {
22+
return strings.ToUpper(e.Name()), nil
23+
},
24+
}
25+
26+
result, err := mapper.MapAny(s)
27+
if err != nil {
28+
panic(err)
29+
}
30+
31+
fmt.Printf("%+v", result) // map[KEY:value]
32+
}

mapify.go

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ func (i Mapper) mapAny(path string, v interface{}) (interface{}, error) {
6666
return i.mapAny(path, reflectValue.Elem().Interface())
6767
case reflectValue.Kind() == reflect.Struct:
6868
return i.mapStruct(path, reflectValue)
69+
case reflectValue.Kind() == reflect.Map && reflectValue.Type().Key().Kind() == reflect.String:
70+
return i.mapStringMap(path, reflectValue)
6971
case reflectValue.Kind() == reflect.Slice:
7072
return i.mapSlice(path, reflectValue)
7173
default:
@@ -106,15 +108,33 @@ func (i Mapper) mapStruct(path string, reflectValue reflect.Value) (map[string]i
106108
value := reflectValue.Field(j)
107109
element := Element{name: fieldName, Value: value, field: &field}
108110

109-
if err := i.mapStructField(fieldPath, element, result); err != nil {
111+
if err := i.mapElement(fieldPath, element, result); err != nil {
110112
return nil, err
111113
}
112114
}
113115

114116
return result, nil
115117
}
116118

117-
func (i Mapper) mapStructField(fieldPath string, element Element, result map[string]interface{}) error {
119+
func (i Mapper) mapStringMap(path string, reflectValue reflect.Value) (map[string]interface{}, error) {
120+
result := map[string]interface{}{}
121+
122+
keys := reflectValue.MapKeys()
123+
for _, key := range keys {
124+
fieldName := key.String()
125+
fieldPath := path + "." + fieldName
126+
value := reflectValue.MapIndex(key)
127+
element := Element{name: fieldName, Value: value}
128+
129+
if err := i.mapElement(fieldPath, element, result); err != nil {
130+
return nil, err
131+
}
132+
}
133+
134+
return result, nil
135+
}
136+
137+
func (i Mapper) mapElement(fieldPath string, element Element, result map[string]interface{}) error {
118138
accepted, filterErr := i.Filter(fieldPath, element)
119139
if filterErr != nil {
120140
return fmt.Errorf("Filter failed: %w", filterErr)
@@ -156,9 +176,28 @@ func (i Mapper) mapSlice(path string, reflectValue reflect.Value) (_ interface{}
156176
}
157177
}
158178

179+
return slice, nil
180+
case reflect.Map:
181+
if reflectValue.Type().Elem().Key().Kind() != reflect.String {
182+
return reflectValue.Interface(), nil
183+
}
184+
185+
slice := make([]map[string]interface{}, reflectValue.Len())
186+
187+
for j := 0; j < reflectValue.Len(); j++ {
188+
slice[j], err = i.mapStringMap(slicePath(path, j), reflectValue.Index(j))
189+
if err != nil {
190+
return nil, err
191+
}
192+
}
193+
159194
return slice, nil
160195
case reflect.Slice:
161-
if reflectValue.Type().Elem().Elem().Kind() == reflect.Struct {
196+
sliceElem := reflectValue.Type().Elem().Elem()
197+
198+
if sliceElem.Kind() == reflect.Struct ||
199+
(sliceElem.Kind() == reflect.Map && sliceElem.Key().Kind() == reflect.String) {
200+
162201
var slice [][]map[string]interface{}
163202

164203
for j := 0; j < reflectValue.Len(); j++ {

0 commit comments

Comments
 (0)