golang 解组(反序列化)结构中的变量类型字典

golang unmarshal (deserialize) variable type dictionary in struct

这是我的问题的人为示例,所以请忽略这是通过使用带有 json 可选参数的单一结构解决的。

鉴于:

    {
        "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
        }
    }
}

我在 json 的示例位中只有 name 参数,但假设我的代码中有更多参数。有没有办法对所有参数使用 normal/default 解组,然后只手动接管 address_dict,或者一旦我承诺为这个对象实现接口,我在手动反序列化每个参数时卡住了?

我尝试了以下方法,并很快意识到为什么它不起作用。

    err = json.Unmarshal(b, self)
    if err != nil {
        return err
    }

为避免复制联系人字段,使用对象嵌入来隐藏需要特殊处理的字段。

使用工厂模式消除地址类型之间的代码重复。

查看评论了解更多信息:

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
}
 

Run it on the playground.

在对该问题的评论中,OP 表示不应使用其他类型。这个答案使用了两种额外的类型(contactNoMethodsdata 的匿名类型)。

另一个答案的替代方法是声明一个命名的 map[string]AddressIf 类型并让它实现 json.Unmarshaler 接口,然后您不必执行任何临时/匿名类型和转换舞蹈。

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
}

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