kon*_*tin 1 polymorphism marshalling go
我有一种情况,我想将 JSON 数据解组到一个结构数组(一个Foo或Bar多个),所有结构都实现一个通用接口MyInterface。此外,实现该接口的所有合格结构类型都有一个公共字段,我Discrimininator在下面的示例中命名了该字段。\nDiscriminator\xc2\xb9 允许为每个 Discriminator 值唯一地找到正确的结构类型。
但在解组过程中,代码并不“知道”哪个是正确的“目标”类型。解组失败。
\n\n\n无法将对象解组为 main.MyInterface 类型的 Go 值
\n
https://play.golang.org/p/Dw1hSgUezLH
\npackage 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}\nRun Code Online (Sandbox Code Playgroud)\n在 Newtonsoft(一种流行的 .NET JSON 框架)中,这个问题可以通过称为“ TypeNameHandling ”的东西来解决,或者可以使用自定义 JsonConverter 来解决。该框架将在根级别向序列化/封送的 JSON 添加类似魔术"$type"键的内容,然后用于确定反序列化/解封送时的原始类型。
\xc2\xb9当具有相同基数的多个类型的实例保存在同一个表中时,ORM 中的术语“多态性”下也会出现类似的情况。通常会引入一个鉴别器列,因此得名上面的示例。
\n您可以实施自定义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