This is a contrived example of my issue, so please ignore that this is solved by using a singular struct w/ json optional param.
Given:
{
"name": "alice",
"address_dict": {
"home": { "address": "123 st" },
"work": { "address": "456 rd", "suite": "123"}
}
}
type AddressIf interface{}
type AddressHome {
Address string `json:"address"`
}
type AddressWork {
Address string `json:"address"`
Suite string `json:"suite"`
}
type Contact struct {
Name string `json:"name"`
AddressMap map[string]AddressIf `json:"address_map"`
}
func(self *Contact) UnmarshalJSON(b []byte) error {
var objMap map[string]*json.RawMessage
err := json.Unmarshal(b, &objMap
if err != nil {
return err
}
var rawAddressMap map[string]*json.RawMessage
err = json.Unmarshal(*objMap["address_map"], &rawAddressMap)
if err != nil {
return err
}
// how do we unmarshal everything else in the struct, and only override the handling of address map???
// <failing code block is here - beg - just a tad, just a tad, just a tad - recursive>
err = json.Unmarshal(b, self)
if err != nil {
return err
}
// <failing code block is here - end>
if nil == self.AddressMap {
self.AddressMap = make(map[string]AddressIf)
}
for key, value := range rawAddressMap {
switch key {
case "home":
dst := &AddressHome{}
err := json.Unmarshal(*value, dst)
if err != nil {
return err
} else {
self.AddressMap[key] = dst
}
case "work":
dst := &AddressWork{}
err := json.Unmarshal(*value, dst)
if err != nil {
return err
} else {
self.AddressMap[key] = dst
}
default:
continue
}
}
}
I have just the name
param in this sample bit of json, but let's assume I have more in my code. Is there a way to use the normal/default unmarshal for all the params, and then only manually take over for the address_dict
, or is it the case that once I commit to implementing the interface for this object, I'm stuck manually deserializing each parameter?
I tried the following, and quickly realized why it didn't work.
err = json.Unmarshal(b, self)
if err != nil {
return err
}
To avoid copying Contact fields, use object embedding to shadow the fields that require special handling.
Use the factory pattern to eliminate code duplication across the address types.
See commentary for more info:
var addressFactories = map[string]func() AddressIf{
"home": func() AddressIf { return &AddressHome{} },
"work": func() AddressIf { return &AddressWork{} },
}
func (self *Contact) UnmarshalJSON(b []byte) error {
// Declare type with same base type as Contact. This
// avoids recursion below because this type does not
// have Contact's UnmarshalJSON method.
type contactNoMethods Contact
// Unmarshal to this value. The Contact.AddressMap
// field is shadowed so we can handle unmarshal of
// each value in the map.
var data = struct {
*contactNoMethods
AddressMap map[string]json.RawMessage `json:"address_map"`
}{
// Note that all fields except AddressMap are unmarshaled
// directly to Contact.
(*contactNoMethods)(self),
nil,
}
if err := json.Unmarshal(b, &data); err != nil {
return err
}
// Unmarshal each addresss...
self.AddressMap = make(map[string]AddressIf, len(data.AddressMap))
for k, raw := range data.AddressMap {
factory := addressFactories[k]
if factory == nil {
return fmt.Errorf("unknown key %s", k)
}
address := factory()
if err := json.Unmarshal(raw, address); err != nil {
return err
}
self.AddressMap[k] = address
}
return nil
}
In a comment on the question, OP says that no additional types should be used. This answer uses two additional types ( contactNoMethods
and the anonymous type for data
).
An alternative to the other answer would be to declare a named map[string]AddressIf
type and have it implement the json.Unmarshaler
interface, then you do not have to do any of the temporary / anon type and conversion dance.
type AddressMap map[string]AddressIf
func (m *AddressMap) UnmarshalJSON(b []byte) error {
if *m == nil {
*m = make(AddressMap)
}
raw := make(map[string]json.RawMessage)
if err := json.Unmarshal(b, &raw); err != nil {
return err
}
for key, val := range raw {
switch key {
case "home":
dst := new(AddressHome)
if err := json.Unmarshal(val, dst); err != nil {
return err
}
(*m)[key] = dst
case "work":
dst := new(AddressWork)
if err := json.Unmarshal(val, dst); err != nil {
return err
}
(*m)[key] = dst
}
}
return nil
}
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.