简体   繁体   中英

How to check variable declared as map[string]interface{} is actually map[string]string?

I have a variable that needs to be either a string or map[string]string (will be deserializing from JSON). So I declare it as interface{} . How can I check that the value is map[string]string ?

This question How to check interface is a map[string]string in golang almost answers my question. But the accepted answer only works if the variable is declared as a map[string]string not if the variable is interface{} .

package main

import (
    "fmt"
)

func main() {

    var myMap interface{}
    myMap = map[string]interface{}{
        "foo": "bar",
    }
    _, ok := myMap.(map[string]string)
    if !ok {
        fmt.Println("This will be printed")
    }
}

See https://play.golang.org/p/mA-CVk7bdb9

I can use two type assertions though. One on the map and one on the map value.

package main

import (
    "fmt"
)

func main() {
    var myMap interface{}
    myMap = map[string]interface{}{
        "foo": "bar",
    }

    valueMap, ok := myMap.(map[string]interface{})
    if !ok {
        fmt.Println("will not be printed")
    }
    for _, v := range valueMap {
        if _, ok := v.(string); !ok {
            fmt.Println("will not be printed")
        }
    }
}

See https://play.golang.org/p/hCl8eBcKSqE

Question: is there a better way?

If you declare a variable as type interface{} , it is type interface{} . It is not , ever, some map[keytype]valuetype value. But a variable of type interface{} can hold a value that has some other concrete type. When it does so, it does so—that's all there is to it. It still is type interface{} , but it holds a value of some other type.

An interface value has two parts

The key distinction here is between what an interface{} variable is , and what it holds . Any interface variable actually has two slots inside it: one to hold what type is stored in it, and one to hold what value is stored in it. Any time you—or anyone—assign a value to the variable, the compiler fills in both slots: the type, from the type of the value you used, and the value, from the value you used. 1 The interface variable compares equal to nil if it has nil in both slots; and that's also the default zero value.

Hence, your runtime test:

valueMap, ok := myMap.(map[string]interface{})

is a sensible thing to do: if myMap holds a value that has type map[string]interface , ok gets set to true and valueMap contains the value (which has that type). If myMap holds a value with some other type, ok gets set to false and valueMap gets set to the zero-value of type map[string]interface{} . In other words, at runtime, the code checks the type-slot first, then either copies the value-slot across to valueMap and sets ok to true, or sets valueMap to nil and sets ok to false.

If and when ok has been set to true , each valueMap[k] value is type interface{} . As before, for myMap itself, each of these interface{} variables can—but do not have to—hold a value of type string , and you must use some sort of "what is the actual type-and-value" run-time test to tease them apart.

When you use json.Unmarshal to stuff decoded JSON into a variable of type interface{} , it is capable of deserializing any of these documented JSON types. The list then tells you what type gets stuffed into the interface variable:

 bool, for JSON booleans float64, for JSON numbers string, for JSON strings []interface{}, for JSON arrays map[string]interface{}, for JSON objects nil for JSON null

So after doing json.Unmarshal into a variable of type interface{} , you should check what type got put into the type-slot of the variable. You can do this with an assertion and an ok boolean, or you can, if you prefer, use a type switch to decode it:

var i interface
if err := json.Unmarshal(data, &i); err != nil {
    panic(err)
}
switch v := i.(type) {
case string:
    ... code ...
case map[string]interface{}:
    ... code ...
... add some or all of the types listed ...
}

The thing is, no matter what you do in code here, you did have json.Unmarshal put something into an interface{} , and interface{} is the type of i . You must test at runtime what type and value pair the interface holds.

Your other option is to inspect your JSON strings manually and decide what type of variable to provide to json.Unmarshal . That gives you less code to write after the Unmarshal , but more code to write before it.

There's a more complete example here, on the Go playground , of using type switches to inspect the result from a json.Unmarshal . It's deliberately incomplete but, I think, has enough input and output cases to let you work out how to handle everything, given the quote above about what json.Unmarshal writes into a variable of type interface{} .


1 Of course, if you assign one interface{} from some other interface{} :

var i1, i2 interface{}
... set i1 from some actual value ...
// more code, then:
i2 = i1

the compiler just copies both slots from i1 into i2 . The two-separate-slots thing becomes clearer when you do:

var f float64
... code that sets f to, say, 1.5 ...
i2 = f

for instance, as that writes float64 into the type-slot, and the value 1.5 into the value-slot. The compiler knows that f is float64 so the type-setting just means "stick a constant in it". The compiler doesn't necessarily know the value of f so the value-setting is a copy of whatever the actual value is.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM