The challenge of type asserting []interface{} to string slices.

 When working with JSON data it can be easy to silently miss converting data, if one is not doing the typical unmarshalling into a well defined struct but instead into a generic map[string]interface{}. Given the following data block:

 
var jsonBytes = []byte(` 
	{ 
		"data": [ 
			"one", 
			"two", 
			"three"
		] 
	} 
`) 

We can unmarshal it via

var jsonBlob map[string]interface{}
err := json.Unmarshal(jsonBytes, &jsonBlob)
if err != nil {
    fmt.Println("error:", err)
}

If we then try and extract the data element and convert it to a slice of strings via a straightforward type assertion:

data := jsonBlob["data"].([]string)

// or as a two step process
data := jsonBlob["data"]
dataSlice := data.([]string)


This ends up causing a panic:

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

 Sometimes we get ‘clever’ and guard our type assertions like:

 
if data, ok := jsonBlob["data"].([]string); ok {
    // do something with data
}

This sort of cleverness comes back to bite us when it silently never executes the inner code block. At least it didn’t panic.

What are we to do when we know we’re getting a string slice from the JSON block? It actually isn’t too difficult, we just let go know that this is an interface slice and range over it copying the data into a predefined slice variable:

var dataSlice []string
for _, v := range a["data"].([]interface{}) {
    if s, ok := v.(string); ok {
        dataSlice = append(dataSlice, s)
    }
}

Example on the Go playground: https://play.golang.org/p/MEdGhEzh6BE

Comments & Responses

Leave a Reply

Your email address will not be published. Required fields are marked *