【问题标题】:Deserialize into map[string]interface{} as a concrete map type反序列化为 map[string]interface{} 作为具体的地图类型
【发布时间】:2017-11-21 04:06:28
【问题描述】:
type Foo struct {
    M map[string]interface{} `json:"m"`
}

type Bar struct {
    I int `json:"i"`
}

type Bar2 struct {
    S string `json:"s"`
}


func do() {
    concreteFoo1 := Foo {
        M: make(map[string]Bar),
    }
    json.Unmarshal([]byte(`{"m": {"a": { "i": 1 }}}`), &concreteFoo1)

    concreteFoo2 := Foo {
        M: make(map[string]Bar2),
    }

    json.Unmarshal([]byte(`{"m": {"a": { "s": "hello" }}}`), &concreteFoo2)

}

编译失败:

不能使用 make(map[string]Bar) (type map[string]Bar) 作为类型 map[string]interface {} in field value

不能使用 make(map[string]Bar2) (type map[string]Bar2) 作为类型 map[string]interface {} in field value

如何编译并支持 Foo 的两种变体?

【问题讨论】:

  • "我怎样才能让它编译并支持 Foo 的两种变体?"你不能。您必须决定 Foo.M. 的类型。

标签: json go


【解决方案1】:

Foo.m 更改为 map[string]Bar 而不是您所拥有的,这将允许它编译。要使其工作,您需要将Foo.m 更改为Foo.MBar.i 更改为Bar.I。 Go JSON 库不会对未导出的属性进行解组或编组。将大写属性映射到小写 JSON 元素需要使用标签。完整的工作示例here

【讨论】:

  • 感谢@laz,这会起作用,但问题是,如前所述,我不想更改 Foo。我应该解释为什么。我还有一些其他数据 {"m": {"a": { "s": "hello" }}} 我也想反序列化为 Foo。我已经用这两种变体更新了问题,并包含了对使用大写字段名称的修复。
【解决方案2】:

改变这一行: M map[string]interface{} json:"m"

进入: M接口{}json:"m"

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

【讨论】:

  • 是的,行得通。一个缺点是它丢失了地图类型信息,因此地图索引需要类型断言,例如:concreteFoo1.M.(map[string]interface{})["a"] 有没有办法保留地图类型,并为值提供灵活的类型?
【解决方案3】:

如果可能的Bar 类型集是无限的,您应该使用Stud's answer 中的解决方案。解组后,您只需执行一次类型断言即可获得map,这比对map[string]interface{} 类型的每个值执行类型断言要好得多。

但是,如果该集合仅限于几种类型,您可以创建一个父 Bar 类型,该类型嵌入所有可能的 Bar 变体列表。如果嵌入类型的字段有可能发生冲突,您仍然可以让嵌入类型实现 json.Unmarshaler 接口,并对在何处解组的内容执行一些自定义逻辑。

例如像这样:

type BarSet struct {
    *Bar
    *Bar2
}

type Bar struct {
    I int `json:"i"`
}

type Bar2 struct {
    S string `json:"s"`
}

https://play.golang.org/p/tJfqtnP-CX

如果您想尽可能严格,您应该创建单独的 Foo 类型,每个类型都有其独特的 M map[string]... 字段。

【讨论】:

  • Bar 或 Bar2 的实例的内存布局是什么,是否总是有两个插槽用于两个指针?
  • 如果你问的是大小,是的,带有两个指针 nilBarSet 实例的大小为 8(每个 4)。
【解决方案4】:

可以制作通用接口

type Foo struct {
    M map[string]Ibar `json:"m"`
}

type Ibar interface{}

然后解压进去。

当您需要变量的特定值时进行类型断言。

https://play.golang.org/p/fcs-Yp-ck2

【讨论】:

    【解决方案5】:

    我再次查看并认为自定义解组器可能是不错的选择。

    func (f *Foo) UnmarshalJSON(b []byte) error {
        var tmp struct {
            M map[string]json.RawMessage `json:"m"`
        }
        var foo = Foo{
            M: make(map[string]interface{}),
        }
        var err error
        //for key, rawValue := range
        err = json.Unmarshal(b, &tmp)
        if err != nil {
            return err
        }
        for key, rawValue := range tmp.M {
            var bar Bar
            if json.Unmarshal(rawValue, &bar) == nil && bar.I != 0 { // custom check
                foo.M[key] = bar
                continue
            }
            var bar2 Bar2
            if json.Unmarshal(rawValue, &bar2) == nil && bar2.S != "" { // custom check
                foo.M[key] = bar2
                continue
            }
        }
        *f = foo
        return nil
    }
    

    优点:

    • 逻辑集中在一处

    • 标准调用UnmarshalJSON

    • 可以处理多个子对象

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

    【讨论】:

      猜你喜欢
      • 2017-07-13
      • 2014-10-26
      • 2018-09-08
      • 2015-01-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-03-08
      • 2016-09-03
      相关资源
      最近更新 更多