Skip to content

Commit

Permalink
Add support for map conversion
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
elgopher committed Mar 23, 2022
1 parent 4b9e9f0 commit 94e87cb
Show file tree
Hide file tree
Showing 4 changed files with 416 additions and 194 deletions.
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/elgopher/mapify)](https://goreportcard.com/report/github.com/elgopher/mapify)
[![codecov](https://codecov.io/gh/elgopher/mapify/branch/master/graph/badge.svg)](https://codecov.io/gh/elgopher/mapify)

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

## Features

Expand Down Expand Up @@ -55,4 +55,12 @@ func main() {
type SomeStruct struct {
Field string
}
```
```

## MapAny algorithm

1. Take an object which is a struct, map or slice.
2. Traverse entire object looking for nested structs or maps.
3. **Filter** elements (struct fields, map keys)
4. **Rename** field names or map keys
5. **Map** (struct field or map values)
32 changes: 32 additions & 0 deletions _examples/maps/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package main

import (
"fmt"
"strings"

"github.com/elgopher/mapify"
)

// This example shows how to filter maps and rename keys
func main() {
s := map[string]interface{}{
"key": "value",
"another": "another value",
}

mapper := mapify.Mapper{
Filter: func(path string, e mapify.Element) (bool, error) {
return path == ".key", nil
},
Rename: func(path string, e mapify.Element) (string, error) {
return strings.ToUpper(e.Name()), nil
},
}

result, err := mapper.MapAny(s)
if err != nil {
panic(err)
}

fmt.Printf("%+v", result) // map[KEY:value]
}
45 changes: 42 additions & 3 deletions mapify.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ func (i Mapper) mapAny(path string, v interface{}) (interface{}, error) {
return i.mapAny(path, reflectValue.Elem().Interface())
case reflectValue.Kind() == reflect.Struct:
return i.mapStruct(path, reflectValue)
case reflectValue.Kind() == reflect.Map && reflectValue.Type().Key().Kind() == reflect.String:
return i.mapStringMap(path, reflectValue)
case reflectValue.Kind() == reflect.Slice:
return i.mapSlice(path, reflectValue)
default:
Expand Down Expand Up @@ -106,15 +108,33 @@ func (i Mapper) mapStruct(path string, reflectValue reflect.Value) (map[string]i
value := reflectValue.Field(j)
element := Element{name: fieldName, Value: value, field: &field}

if err := i.mapStructField(fieldPath, element, result); err != nil {
if err := i.mapElement(fieldPath, element, result); err != nil {
return nil, err
}
}

return result, nil
}

func (i Mapper) mapStructField(fieldPath string, element Element, result map[string]interface{}) error {
func (i Mapper) mapStringMap(path string, reflectValue reflect.Value) (map[string]interface{}, error) {
result := map[string]interface{}{}

keys := reflectValue.MapKeys()
for _, key := range keys {
fieldName := key.String()
fieldPath := path + "." + fieldName
value := reflectValue.MapIndex(key)
element := Element{name: fieldName, Value: value}

if err := i.mapElement(fieldPath, element, result); err != nil {
return nil, err
}
}

return result, nil
}

func (i Mapper) mapElement(fieldPath string, element Element, result map[string]interface{}) error {
accepted, filterErr := i.Filter(fieldPath, element)
if filterErr != nil {
return fmt.Errorf("Filter failed: %w", filterErr)
Expand Down Expand Up @@ -156,9 +176,28 @@ func (i Mapper) mapSlice(path string, reflectValue reflect.Value) (_ interface{}
}
}

return slice, nil
case reflect.Map:
if reflectValue.Type().Elem().Key().Kind() != reflect.String {
return reflectValue.Interface(), nil
}

slice := make([]map[string]interface{}, reflectValue.Len())

for j := 0; j < reflectValue.Len(); j++ {
slice[j], err = i.mapStringMap(slicePath(path, j), reflectValue.Index(j))
if err != nil {
return nil, err
}
}

return slice, nil
case reflect.Slice:
if reflectValue.Type().Elem().Elem().Kind() == reflect.Struct {
sliceElem := reflectValue.Type().Elem().Elem()

if sliceElem.Kind() == reflect.Struct ||
(sliceElem.Kind() == reflect.Map && sliceElem.Key().Kind() == reflect.String) {

var slice [][]map[string]interface{}

for j := 0; j < reflectValue.Len(); j++ {
Expand Down
Loading

0 comments on commit 94e87cb

Please sign in to comment.