【问题标题】:From Java to Golang: Unmarshaling Polymorphic JSON从 Java 到 Golang:解组多态 JSON
【发布时间】:2019-11-28 00:29:23
【问题描述】:

这里是新手 golang 程序员。我正在重写一个 Java 应用程序。 Java 应用程序使用一个对象模型,该模型利用 Jackson 的 Polymorphic Typing 功能来处理与 JSON 的编组/解组。假设我无法更改 JSON 对象的形状。

鉴于 Go 提供的多态性是 interface{},因此很难想出一个能够提供与多态性相同的使用模式的“对象模型”。

我第一次尝试解决的方法是这样的:


type Thing struct {
    ID   string `json:"id"`
    Type string `json:"@type"`
}

type SpecificThing struct {
    Thing
    SpecificField string `json:"specificField"`
}

type AnotherSpecificThing struct {
    Thing
    AnotherSpecificField string `json:"anotherSpecificField"`
}

但这需要将具体的子类型实例传递给 unmarshal 方法。

我试图通过创建“Union Structs”作为编组和解组的工具来解决这个问题:

type Thing struct {
    ID      string      `json:"id"`
    Type    string      `json:"@type"`
    Payload interface{} `json:"-"`
}

type SpecificThing struct {
    SpecificField string `json:"specificField"`
}

type AnotherSpecificThing struct {
    AnotherSpecificField string `json:"anotherSpecificField"`
}

type superThing struct {
    ID   string `json:"id"`
    Type string `json:"@type"`
    *SpecificThing
    *AnotherSpecificThing
}

func (t *Thing) UnmarshalJSON(b []byte) error {
    //error checking omitted for brevity
    var st superThing

    _ = json.Unmarshal(b, &st)

    t.ID = st.ID
    t.Type = st.Type

    switch t.Type {
    case "specificThing":
        t.Payload = st.SpecificThing
    case "anotherSpecificThing":
        t.Payload = st.AnotherSpecificThing
    }
    return nil
}

func TestUnmarshal(t *testing.T) {
    data := []byte(`
    {
        "id":"some id",
        "@type":"specificThing",
        "specificField": "some specific field value"
    }   
    `)

    var th Thing
    _ = json.Unmarshal(data, &th)
}

就动态 JSON 而言,就能够编组和解组而言,这工作得很好。不利的一面是,模型的消费者需要对 Payload 进行类型断言,才能与子类型交互以完成任何实际工作。理想情况下,是否存在允许传递“事物”抽象级别的解决方案,该解决方案还允许在需要时与子类型进行交互?根据阅读,接口可以用于这种情况,但我很难看到这个模型将如何利用它们。想法?

【问题讨论】:

    标签: json go


    【解决方案1】:

    我认为让 Thing 成为一个接口并实现 UnmarshalJSON,这几乎可以满足您的需求(如果用户需要接口未提供的功能,那么用户仍然必须使用类型断言/切换,但这几乎是不可避免的)。这将如下所示:

    package main
    
    import (
        "encoding/json"
        "fmt"
    )
    
    func main() {
        data := []byte(`
        {
            "id":"some id",
            "@type":"specificThing",
            "specificField": "some specific field value"
        }   
        `)
    
        var th ThingHolder 
        err := json.Unmarshal(data, &th)
        if err != nil {
            panic(err)
        }
        mySpecThing := th.T.(*SpecificThing )
        fmt.Printf("%v", mySpecThing)
    }
    
    type Thing interface {
        ID() string
    }
    
    type ThingHolder struct {
        T Thing
    }
    
    type SpecificThing struct {
        Id            string `json:"id"`
        Type          string `json:"@type"`
        SpecificField string `json:"specificField"`
    }
    
    func (s *SpecificThing) ID() string {
        return s.Id
    }
    
    func (t *ThingHolder) UnmarshalJSON(b []byte) error {
        var objMap map[string]*json.RawMessage
        err := json.Unmarshal(b, &objMap)
        if err != nil {
            return err
        }
    
        // Now lets see what 'things' the JSON contains
        // by looking at JSON keys
        jsonType, ok := objMap["@type"]
        if !ok {
            return fmt.Errorf("No Type")
        }
        var goType string
        err = json.Unmarshal(*jsonType, &goType)
        if err != nil {
        return fmt.Errorf("error getting type: %s", err)
        }   
    
        switch goType {
        case "specificThing":
        var st SpecificThing
            err = json.Unmarshal(b, &st)
            if err != nil {
                return err
            }
            t.T = &st
        default:
        return fmt.Errorf("Unknown type %s", goType )
        }
    
        return nil
    }
    

    Greg TrowBridge's blog 详细介绍了这种方法。

    • 在 OP 指出我错过了一个测试用例后更新。代码现已经过测试,可以正常工作。

    【讨论】:

    • 感谢您的回复。请参阅 OP 中的测试函数,例如进来的 json 将是一个 SpecificThing。据我了解,接口上不允许 (t *Thing) 的接收者。我现在在 ide 中尝试了同样的操作,也得到了编译器错误:“invalid receiver *Thing (pointer or interface type)”
    • 道歉 - 忽略向下滚动并错过了您的测试功能。将更新此代码以包含此代码(使用具体类型进行解组)
    • 希望这样会更好(现在已经用您的测试用例对其进行了测试)。
    • 这里看起来不错。它将用我的方法解决可用性问题。我没有考虑将自定义解组附加到高于被解组的结构的级别。类型检查在 Thing 上是不可避免的,但在我们需要使用 instanceof 的基于 Java 的方法中也是不可避免的。将此标记为已接受。感谢您的帮助!
    • 请注意,这会将所有 JSON 字节解组两次,并将“@Type”字段解组三次。这就是我在this comment 中提到的。但是,解组到 map[string]*json.RawMessage 至少比其他解组操作更轻。
    猜你喜欢
    • 2022-01-21
    • 2017-09-07
    • 1970-01-01
    • 2021-07-11
    • 1970-01-01
    • 2021-09-14
    • 1970-01-01
    • 1970-01-01
    • 2020-02-04
    相关资源
    最近更新 更多