John
John

Reputation: 219

golang json and slices of interface

I am having trouble iterating over slices of interfaces that contain slices of interfaces.

This problem has arisen through attempting to work with an API call which returns JSON data. There is quite a lot of data returned and the structure differs quite dramatically depending on the request. There is also no structure for the JSON responses in the API documentation so I am trying to implement some methods for working with arbitrary JSON responses.

Presently when the initial call is made it is dropped into a map[string]interface{} and then a switch statement is run to determine the type of each element, the problem occurs when a slice of interfaces is encountered. I can't seem to do anything with them.

I have tried using the sort package a few times (specifically the sort and slicestable functions) to no avail.

The error I am receiving is:

interface conversion: interface {} is []interface {}, not map[string]interface {}

Which occurs when I try and map the slice of interfaces so I can iterate over them with the switch statement again.

output := make(map[string]interface{})
err = json.Unmarshal(body, &output)

fmt.Println(err)
identify(output)

return err
}

func identify(output map[string]interface{}) {
    fmt.Printf("%T", output)
    for a, b := range output {
        switch bb := b.(type) {
        case string:
            fmt.Println("This is a string")
        case float64:
            fmt.Println("this is a float")
        case []interface{}:
            fmt.Println(" is []interface ", bb)
            test := b.(map[string]interface{}) // falis here
            fmt.Println("Success!", test)
        default:
            return
        }
    }
}

So the basic question is how do I iterate over nested slices of interfaces without knowing the structure beforehand?

Upvotes: 1

Views: 2810

Answers (2)

Himanshu
Himanshu

Reputation: 12675

You can add a switch case which is checking the type of interface for slice of interfaces and then run the same function as recursive until whole json is parsed.

output := make(map[string]interface{})
err = json.Unmarshal(body, &output)

fmt.Println(err)
identify(output)

return err
}

func identify(output map[string]interface{}) {
    fmt.Printf("%T", output)
    for a, b := range output {
        switch bb := b.(type) {
        case string:
            fmt.Println("This is a string")
        case float64:
            fmt.Println("this is a float")
        case []interface{}:
        // Access the values in the JSON object and place them in an Item
        for _, itemValue := range jsonObj {
            fmt.Printf("%v is an interface\n", itemValue)
            identify(itemValue.(map[string]interface{}))
        }
        default:
            return
        }
    }
}

There can be deep nested json. We just needs to create options for each case until json is completely parsed.

Upvotes: 1

Pavel Nikolov
Pavel Nikolov

Reputation: 9541

Well, you can't cast []interface{} to map[string]interface{}. Since it's a slice, you can iterate over its elements (note that bb is b already cast to the appropriate type and you don't need to cast b again):

    case []interface{}:
        fmt.Println(" is []interface ", bb)
        for _, val := range bb {
            // do something with val
        }
    default:
    ...

Why do you have to deal with something that you don't know? Is it possible that you reconsider your architecture and work with known type(s)? have you seen the example of delaying unmarshaling using json.RawMessage? Or maybe try implementing the Unmarshaler interface?

Upvotes: 1

Related Questions