Tuesday, October 31, 2017

Golang JSON Marshal And Unmarshal With Interface

Problem:


Cannot deserialize to an interface.

Example:

Pet is an interface with a method called Type() that returns the type of pet.  Dog and Cat implement Type() and return "dog" and "cat" respectively.  House has one pet.

package main

import (
"encoding/json"
"fmt"
)

func main() {
// House has a pet dog.
h := House {
Pet: &Dog{},
}

b, err := json.Marshal(h)
if err != nil {
panic(err)
}


h2 := House{}
err = json.Unmarshal(b, &h2)
if err != nil {
panic(err)
}

// Expect dog
fmt.Println(h2.Pet.Type())


// House has a pet cat.
h = House {
Pet: &Cat{},
}

b, err = json.Marshal(h)
if err != nil {
panic(err)
}

h2 = House{}
err = json.Unmarshal(b, &h2)
if err != nil {
panic(err)
}

// Expect cat
fmt.Println(h2.Pet.Type())
}

type House struct {
Pet Pet
}

type Pet interface {
Type() string
}

type Dog struct {
}

func (d *Dog) Type() string {
return `dog`
}

type Cat struct {
}

func (c *Cat) Type() string {
return `cat`
}

https://play.golang.org/p/mYYK7L7IQK


When you run this code, you get this error  "json: cannot unmarshal object into Go struct field House.Pet of type main.Pet" because it doesn't how to unmarshal it an interface Pet.

Solution:


package main

import (
"encoding/json"
"fmt"
)

func main() {
// House has a pet dog.
h := House {
Pet: &Dog{},
}

b, err := json.Marshal(h)
if err != nil {
panic(err)
}


h2 := House{}
err = json.Unmarshal(b, &h2)
if err != nil {
panic(err)
}

// Expect dog
fmt.Println(h2.Pet.Type())


// House has a pet cat.
h = House {
Pet: &Cat{},
}

b, err = json.Marshal(h)
if err != nil {
panic(err)
}

h2 = House{}
err = json.Unmarshal(b, &h2)
if err != nil {
panic(err)
}

// Expect cat
fmt.Println(h2.Pet.Type())
}

type House struct {
Pet Pet
}

func (h *House) UnmarshalJSON(data []byte) error {
var m map[string]interface{}
err := json.Unmarshal(data, &m)
if err != nil {
return err
}

pet := m["Pet"]
if pet == nil {
return fmt.Errorf("cannot unmarshal pet")
}

m = pet.(map[string]interface{})
data, err = json.Marshal(m)
if err != nil {
return err
}

name := m["PetName"]
if name == nil {
return fmt.Errorf("cannot unmarshal pet")
}


p, ok := pets[name.(string)]
if !ok {
return fmt.Errorf("cannot unmarshal pet %q", name)
}

err = json.Unmarshal(data, p)
if err != nil {
return err
}

h.Pet = p
return nil
}

var pets = map[string]Pet {`Dog`: &Dog{}, `Cat`: &Cat{}}

type Pet interface {
Type() string
}

type Dog struct {
}

func (d *Dog) Type() string {
return `dog`
}

func (d *Dog) MarshalJSON() ([]byte, error) {
type MyCopy Dog
return json.Marshal(&struct {
PetName string
*MyCopy
}{
PetName: `Dog`,
MyCopy:      (*MyCopy)(d),
})
}

type Cat struct {
}

func (c *Cat) Type() string {
return `cat`
}

func (c *Cat) MarshalJSON() ([]byte, error) {
type MyCopy Cat
return json.Marshal(&struct {
PetName string
*MyCopy
}{
PetName: `Cat`,
MyCopy:      (*MyCopy)(c),
})
}

https://play.golang.org/p/Wc_eMVtOpy

No comments:

Post a Comment