0

I want to create a struct with the values in a map.

Here's a snippet:

        log := &Log{
            Facility:  parts["facility"].(int),
            Severity:  parts["severity"].(int),
            Timestamp: parts["timestamp"].(time.Time),
            Hostname:  parts["hostname"].(string),
            AppName:   parts["appName"].(string),
            Client:    parts["client"].(string),
            Priority:  parts["priority"].(int),
            Message:   parts["message"].(string),
        }

The problem is, if one of the values is nil, panic occurs. So I wanted to do something like this:

Facility:  parts["facility"] != nil ? parts["facility"].(int) : 0

But this is not a valid syntax.

Do I have to check every key separately to handle nil cases?

Élodie Petit
  • 5,774
  • 6
  • 50
  • 88
  • 1>if the data type is know in advance then you must use a struct or map of structs 2> data,ok:=parts["facility"]; if ok && data!=nil {value=data.(int)} – Nidhin David Mar 07 '21 at 17:02
  • https://golang.org/doc/faq#Does_Go_have_a_ternary_form – Peter Mar 07 '21 at 17:30
  • See [What is the idiomatic Go equivalent of C's ternary operator?](https://stackoverflow.com/questions/19979178/what-is-the-idiomatic-go-equivalent-of-cs-ternary-operator/59375088#59375088) – icza Mar 07 '21 at 17:44

2 Answers2

7

You can do this:

 log := &Log{}
 log.Facility, _ = parts["facility"].(int)
 log.Severity, _ = parts["severity"].(int)
  ...

This will use the two-value form of type assertion, where the second value indicates if the type-assertion worked, and you then ignore that value, so the struct member is initialized with the zero-value if the map does not contain the key, or if the key is not of the required type.

Burak Serdar
  • 46,455
  • 3
  • 40
  • 59
  • 1
    Technically this is the 2-value form of the type assertion, not the map lookup. The nil interface value from the map is passed to the assertion which can then assert the interface to the desired zero value. – JimB Mar 07 '21 at 20:47
  • @JimB, you are correct I fixed the answer – Burak Serdar Mar 07 '21 at 21:01
1

You can write a wrapper for these types if you like:

func toInt(x interface{}) int {
    if i, ok := x.(int); ok {
        return i
    }
    return 0 // Or what you would like to have as a default.
}

which can be shortened if you want to use the default value for a type:

func toInt(x interface{}) int {
    i, _ := x.(int)
    return i
}

which will make it look like this:

    log := &Log{
        Facility:  toInt(parts["facility"]),
        Severity:  toInt(parts["severity"]),
        Timestamp: toTime(parts["timestamp"]),
        Hostname:  toString(parts["hostname"]),
        AppName:   toString(parts["appName"]),
        Client:    toString(parts["client"]),
        Priority:  toInt(parts["priority"]),
        Message:   toString(parts["message"]),
    }

You could go further and write your conversion functions locally:

var parts map[string]interface{}

// ...

i := func(name string) int {
    i, _ := parts[name].(int)
    return i
}

s := func(name string) string {
    s, _ := parts[name].(string)
    return s
}

t := func(name string) time.Time {
    t, _ := parts[name].(time.Time)
    return t
}

log := &Log{
    Facility:  i("facility"),
    Severity:  i("severity"),
    Timestamp: t("timestamp"),
    Hostname:  s("hostname"),
    AppName:   s("appName"),
    Client:    s("client"),
    Priority:  i("priority"),
    Message:   s("message"),
}
gonutz
  • 5,087
  • 3
  • 22
  • 40