0
votes

Currently, I try to parse JSON to map[string][]interface{}, but unmarshalling returns an error. According to (https://golang.org/pkg/encoding/json/), to unmarshal JSON into an interface value, Unmarshal stores one of these in the interface value:

  • 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

I wonder if golang is able to unmarshal map[string][]interface{}. The following is code snippet. I am new to Golang, thanks for help in advance.

// emailsStr looks like "{"isSchemaConforming":true,"schemaVersion":0,"unknown.0":[{"email_address":"[email protected]"},{"email_address":"[email protected]"}]}"

emailsRaw := make(map[string][]*entities.Email)
err := json.Unmarshal([]byte(emailsStr), &emailsRaw)

Error message:

&json.UnmarshalTypeError{Value:"number", Type:(*reflect.rtype)(0x151c7a0), Offset:44, Struct:"", Field:""}

2
You're asking about map[string][]interface{} but in your code it's map[string][]*entities.Email - zerkms
I assume that entities.Email is interface{} type, right? - Summer Ji
Not right: maps in go are not covariant. - zerkms

2 Answers

3
votes

The Go encoding/json package will only unmarshal dynamically to a map[string]interface{}. From there, you will need to use type assertions and casting to pull out the values you want, like so:

func main() {
    jsonStr := `{"isSchemaConforming":true,"schemaVersion":0,"unknown.0":[{"email_address":"[email protected]"},{"email_address":"[email protected]"}]}`

    dynamic := make(map[string]interface{})
    json.Unmarshal([]byte(jsonStr), &dynamic)

    firstEmail := dynamic["unknown.0"].([]interface{})[0].(map[string]interface{})["email_address"]

    fmt.Println(firstEmail)
}

(https://play.golang.org/p/VEUEIwj3CIC)

Each time, Go's .(<type>) operator is used to assert and cast the dynamic value to a specific type. This particular code will panic if anything happens to be the wrong type at runtime, like if the contents of unknown.0 aren't an array of JSON objects.

The more idiomatic (and robust) way to do this in Go is to annotate a couple structs with json:"" tags and have encoding/json unmarshal into them. This avoids all the nasty brittle .([]interface{}) type casting:

type Email struct {
    Email string `json:"email_address"`
}

type EmailsList struct {
    IsSchemaConforming bool `json:"isSchemaConforming"`
    SchemaVersion      int  `json:"schemaVersion"`
    Emails []Email `json:"unknown.0"`
}

func main() {
    jsonStr := `{"isSchemaConforming":true,"schemaVersion":0,"unknown.0":[{"email_address":"[email protected]"},{"email_address":"[email protected]"}]}`

    emails := EmailsList{}
    json.Unmarshal([]byte(jsonStr), &emails)

    fmt.Printf("%+v\n", emails)
}

(https://play.golang.org/p/iS6e0_87P2J)

0
votes

A better approach will be to use struct for main schema and then use an slice of email struct for fetching the data for email entities get the values from the same according to requirements. Please find the solution below :-

package main

import (
    "fmt"
    "encoding/json"
)

type Data struct{
    IsSchemaConforming bool `json:"isSchemaConforming"`
    SchemaVersion float64 `json:"schemaVersion"`
    EmailEntity []Email `json:"unknown.0"`
}

// Email struct
type Email struct{
    EmailAddress string `json:"email_address"`
} 

func main() {
    jsonStr := `{"isSchemaConforming":true,"schemaVersion":0,"unknown.0":[{"email_address":"[email protected]"},{"email_address":"[email protected]"}]}`

    var dynamic Data
    json.Unmarshal([]byte(jsonStr), &dynamic)
    fmt.Printf("%#v", dynamic)
}