rexposadas
rexposadas

Reputation: 3189

Why would adding a func in an interface cause json.Unmarshal to fail?

Why does this fail with the error :

json: cannot unmarshal object into Go struct field Person.spouse of type main.Spouse

type Spouse interface {
    Name() // Adding this causes an error
}

type Address interface {
}

type Person struct {
    Name string   `json:"name"`
    A    *Address `json:"address"`
    S    *Spouse  `json:"spouse"`
}

func main() {
    b := []byte(`{
     "name":"sarah", 
     "address":{
         "street":"101 main" 
         }, 
     "spouse":{
         "name":"joe"
         }
     }
    `)

    p := &Person{}
    if err := json.Unmarshal(b, p); err != nil {
        fmt.Printf("%s", err)
    }
}

Looking at the docs, I don't see why adding a function in an interface would cause an error. I was expecting json.Unmarshal to simply ignore the Name() function since it's not part of the list processed by json.Unmarshal.

Here is the code in go playground.

go version go1.10 darwin/amd64

Upvotes: 4

Views: 1146

Answers (2)

mu is too short
mu is too short

Reputation: 434685

From the fine manual:

func Unmarshal
[...]
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

You're trying to unmarshal a JSON object into an interface so the value behind that interface is going to be a map[string]interface{} to represent the key/value pairs. But map[string]interface{} doesn't have a Name() method so it doesn't implement your Spouse interface.

If we simplify your example a little and get rid of the Name() method we can see what's going on:

type Spouse interface {
}

type Person struct {
    S *Spouse `json:"spouse"`
}

func main() {
    b := []byte(`{"spouse":{ "name":"joe" } }`)

    p := &Person{}
    if err := json.Unmarshal(b, p); err != nil {
        fmt.Printf("%s", err)
    }
    spouse := (*p.S).(map[string]interface{})
    fmt.Printf("%+v\n", spouse)
}

spouse is a map[string]interface{} as documented.

Upvotes: 6

MingiuX
MingiuX

Reputation: 100

The problem is that you are trying to map the value of spouse in the json with a particular data type that doesn't match. As reported in this article, json uses empty interface to map generic data types to the right one used in the file.

The json package uses map[string]interface{} and []interface{} values to store arbitrary JSON objects and arrays; it will happily unmarshal any valid JSON blob into a plain interface{} value. The default concrete Go types are:

  • bool for JSON booleans,
  • float64 for JSON numbers,
  • string for JSON strings, and
  • nil for JSON null.

If you want to see it from another perspective, it means that you can't use anything other than these data types to map data in JSON. And your not empty interface isn't a valid type.

Upvotes: 1

Related Questions