Go 中类型名称处理与encoding/json 的等价性是什么?

kon*_*tin 1 polymorphism marshalling go

散文简短描述

\n

我有一种情况,我想将 JSON 数据解组到一个结构数组(一个FooBar多个),所有结构都实现一个通用接口MyInterface。此外,实现该接口的所有合格结构类型都有一个公共字段,我Discrimininator在下面的示例中命名了该字段。\nDiscriminator\xc2\xb9 允许为每个 Discriminator 值唯一地找到正确的结构类型。

\n

问题和错误消息

\n

但在解组过程中,代码并不“知道”哪个是正确的“目标”类型。解组失败。

\n
\n

无法将对象解组为 main.MyInterface 类型的 Go 值

\n
\n

MWE 在

\n

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

\n
package main\n\nimport (\n    "encoding/json"\n    "fmt"\n)\n\ntype MyInterface interface {\n    // some other business logic methods go here!\n    GetDiscriminator() string // GetDiscriminator returns something like a key that is unique per struct type implementing the interface\n}\n\ntype BaseStruct struct {\n    Discriminator string // will always be "Foo" for all Foos, will always be "Bar" for all Bars\n}\n\ntype Foo struct {\n    BaseStruct\n    // actual fields of the struct don\'t matter. it\'s just important that they\'re different from Bar\n    FooField string\n}\n\nfunc (foo *Foo) GetDiscriminator() string {\n    return foo.Discriminator\n}\n\ntype Bar struct {\n    BaseStruct\n    // actual fields of the struct don\'t matter. it\'s just important that they\'re different from Foo\n    BarField int\n}\n\nfunc (bar *Bar) GetDiscriminator() string {\n    return bar.Discriminator\n}\n\n// Foo and Bar both implement the interface.\n// Foo and Bars are always distinguishible if we check the value of Discriminator\n\nfunc main() {\n    list := []MyInterface{\n        &Bar{\n            BaseStruct: BaseStruct{Discriminator: "Bar"},\n            BarField:   42,\n        },\n        &Foo{\n            BaseStruct: BaseStruct{Discriminator: "Foo"},\n            FooField:   "hello",\n        },\n    }\n    jsonBytes, _ := json.Marshal(list)\n    jsonString := string(jsonBytes)\n    fmt.Println(jsonString)\n    // [{"Discriminator":"Bar","BarField":42},{"Discriminator":"Foo","FooField":"hello"}]\n    var unmarshaledList []MyInterface\n    err := json.Unmarshal(jsonBytes, &unmarshaledList)\n    if err != nil {\n        // Unmarshaling failed: json: cannot unmarshal object into Go value of type main.MyInterface\n        fmt.Printf("Unmarshaling failed: %v", err)\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

在其他语言中

\n

.NET 中已知的 TypeNameHandling

\n

在 Newtonsoft(一种流行的 .NET JSON 框架)中,这个问题可以通过称为“ TypeNameHandling ”的东西来解决,或者可以使用自定义 JsonConverter 来解决。该框架将在根级别向序列化/封送的 JSON 添加类似魔术"$type"键的内容,然后用于确定反序列化/解封送时的原始类型。

\n

ORM 中的多态性

\n

\xc2\xb9当具有相同基数的多个类型的实例保存在同一个表中时,ORM 中的术语“多态性”下也会出现类似的情况。通常会引入一个鉴别器列,因此得名上面的示例。

\n

mko*_*iva 6

您可以实施自定义json.Unmarshaler. 为此,您需要使用命名切片类型而不是未命名的[]MyInterface

在自定义解组器实现中,您可以将 JSON 数组解组为切片,其中切片的每个元素代表json.RawMessage相应的 JSON 对象。之后,您可以迭代原始消息片段。在循环中,仅从每个原始消息中解组该Discriminator字段,然后使用Discriminator该字段的值来确定可以将完整原始消息解组的正确类型,最后解组完整消息并将结果添加到接收者。

type MyInterfaceSlice []MyInterface

func (s *MyInterfaceSlice) UnmarshalJSON(data []byte) error {
    array := []json.RawMessage{}
    if err := json.Unmarshal(data, &array); err != nil {
        return err
    }

    *s = make(MyInterfaceSlice, len(array))
    for i := range array {
        base := BaseStruct{}
        data := []byte(array[i])
        if err := json.Unmarshal(data, &base); err != nil {
            return err
        }

        var elem MyInterface
        switch base.Discriminator {
        case "Foo":
            elem = new(Foo)
        case "Bar":
            elem = new(Bar)
        }
        if elem == nil {
            panic("whoops")
        }

        if err := json.Unmarshal(data, elem); err != nil {
            return err
        }
        (*s)[i] = elem
    }
    return nil
}
Run Code Online (Sandbox Code Playgroud)

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

  • 如果类型不兼容,可能会返回“错误”而不是“恐慌”。 (2认同)