3

I faced with a problem how to iterate through the map[string]interface{} recursively with additional conditions.

1) if a value is a map - recursively call the method

2) if a value is an array - call method for array

3) if a value isn't a map - process it.

Now when method try to execute doc.throughMap(mv) - error occurs So how can I convert some value to needed type after reflect confirm that value is a map or an array?

type MapType map[string]interface{}
type ArrayType []interface{}
func (doc *Document) throughMap(docMap MapType) MapType {
    for k, v := range docMap {
        vt := reflect.TypeOf(v)
        switch vt.Kind() {
        case reflect.Map:
            if mv, ok := v.(map[string]interface{}); ok {
                docMap[k] = doc.throughMap(mv)
            } else {
                panic("error.")
            }
        case reflect.Array, reflect.Slice:
            if mv, ok := v.([]interface{}); ok {
                docMap[k] = doc.throughArray(mv)
            } else {
                panic("error.")
            }
        default:
            docMap[k] = doc.processType(v)
        }
    }
    return docMap
}

Stacktrace:

panic: error. [recovered]
    panic: error.

goroutine 1 [running]:
encoding/json.(*encodeState).marshal.func1(0xc000074cd0)
    /usr/local/go/src/encoding/json/encode.go:301 +0x9a
panic(0x4bd700, 0x4f9b70)
    /usr/local/go/src/runtime/panic.go:513 +0x1b9
project-name/package/name.(*Document).throughMap(0xc00000c028, 0xc000060180, 0xc00007e000)
    /home/path/to/project/document.go:231 +0x3f4
project-name/package/name.(*Document).convertDocument(0xc00000c028)
    /home/path/to/project/document.go:217 +0x33
project-name/pachage/name.(*Document).MarshalJSON(0xc00000c028, 0x4db740, 0xc00000c028, 0x7f3f0f7540c0, 0xc00000c028, 0xc00001c101)
    /home/path/to/project/document.go:167 +0xd8
encoding/json.marshalerEncoder(0xc00007c000, 0x4db740, 0xc00000c028, 0x16, 0xc000070100)
    /usr/local/go/src/encoding/json/encode.go:453 +0xb7
encoding/json.(*encodeState).reflectValue(0xc00007c000, 0x4db740, 0xc00000c028, 0x16, 0x4c0100)
    /usr/local/go/src/encoding/json/encode.go:333 +0x82
encoding/json.(*encodeState).marshal(0xc00007c000, 0x4db740, 0xc00000c028, 0x4f0100, 0x0, 0x0)
    /usr/local/go/src/encoding/json/encode.go:305 +0xf4
encoding/json.Marshal(0x4db740, 0xc00000c028, 0xc000034698, 0x3, 0x3, 0x4d, 0x0)
    /usr/local/go/src/encoding/json/encode.go:160 +0x52
main.main()
    /home/path/to/project/main.go:21 +0x34d
creedqq
  • 302
  • 4
  • 15
  • _"I faced with a problem..."_ And what is that problem? – icza Nov 05 '18 at 10:48
  • Sorry, already added. – creedqq Nov 05 '18 at 10:50
  • _" error occurs"_ What kind of error? Please be more specific. – icza Nov 05 '18 at 10:51
  • Added in description – creedqq Nov 05 '18 at 11:11
  • The problem is that you check if the _kind_ is a map, and then you attempt to type assert a concrete map type. But that fails if it is a different map type. Your code only attempts to handle a single map type. – icza Nov 05 '18 at 11:12
  • Yes, but how can I fix this? I thought that map[string]interface{] is something like map[string]anything – creedqq Nov 05 '18 at 11:22
  • No, it's not. You can only type-assert concrete types or interfaces. So it would help to know what actual types you might have in the map. If you know, you can enumerate them. It would be easier to use a type switch. – icza Nov 05 '18 at 11:23
  • In case when I change the stored structure from map[string]int to map[string]interface{} - all works fine. – creedqq Nov 05 '18 at 11:23
  • So there are any change to create unified method for all types? – creedqq Nov 05 '18 at 11:25
  • 1
    Again, if all maps inside the map are of concrete type `map[string]interface{}`, you may use a simple type conversion or switch. If inner maps map be other custom map types having that map as the underlying type, you may use [`reflect.Value.Conver()`](https://golang.org/pkg/reflect/#Value.Convert) to handle them in unity. – icza Nov 05 '18 at 11:27
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/183119/discussion-between-creedqq-and-icza). – creedqq Nov 05 '18 at 11:29
  • May you give me an example? I didn't find any regarding Value.Convert() Thanks. – creedqq Nov 05 '18 at 11:51
  • 1
    If slices and maps are always the concrete types `[]interface{}` and `map[string]interface{}`, then use type assertions to walk through structure. See answer [here](https://stackoverflow.com/questions/29366038/looping-iterate-over-the-second-level-nested-json-in-go-lang) for an example. If not, then use reflection for all operations. See answer [here](https://stackoverflow.com/questions/47664320/golang-recursively-reflect-both-type-of-field-and-value) for starting point (there's probably a better example of using reflect, but I cannot find it quickly). – Charlie Tumahai Nov 05 '18 at 15:26

2 Answers2

11

Use the following code to recurse through maps, arrays and slices of any type:

func walk(v reflect.Value) {
    fmt.Printf("Visiting %v\n", v)
    // Indirect through pointers and interfaces
    for v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface {
        v = v.Elem()
    }
    switch v.Kind() {
    case reflect.Array, reflect.Slice:
        for i := 0; i < v.Len(); i++ {
            walk(v.Index(i))
        }
    case reflect.Map:
        for _, k := range v.MapKeys() {
            walk(v.MapIndex(k))
        }
    default:
        // handle other types
    }
}
Charlie Tumahai
  • 113,709
  • 12
  • 249
  • 242
2

Following is working for me

func main() {
    x := MapType{
        "a": MapType{
            "x": MapType{
                "p": ArrayType{"l", "o", "l"},
            },
        } ,
    }
    d := &Document{}
    fmt.Println(d.throughMap(x))

}

type Document struct {}

type MapType map[string]interface{}
type ArrayType []interface{}
func (doc *Document) throughMap(docMap MapType) MapType {
    for k, v := range docMap {
        fmt.Println(k, v)
        vt := reflect.TypeOf(v)
        switch vt.Kind() {
        case reflect.Map:
            if mv, ok := v.(MapType); ok {
                docMap[k] = doc.throughMap(mv)
            } else {
                panic("error.")
            }
        case reflect.Array, reflect.Slice:
            if mv, ok := v.(ArrayType); ok {
                docMap[k] = doc.throughArray(mv)
            } else {
                panic("error.")
            }
        default:
            docMap[k] = doc.processType(v)
        }
    }
    return docMap
}

func (doc *Document) throughArray(arrayType ArrayType) ArrayType  {
    return arrayType
}

func (doc *Document) processType(x interface{}) interface{} {
    return x
}
hoque
  • 5,735
  • 1
  • 19
  • 29
  • The issue in a case, outer map contain another map with a different type. nested := map[string]int{ "rsc": 5, "gri": 52, } commits := map[string]interface{}{ "rsc": 3711, "adg": nested, } – creedqq Nov 05 '18 at 16:27