Golang中接口数据的序列化与反序列化实践

Golang中接口数据的序列化与反序列化实践 我希望能够对Go结构体进行序列化和反序列化(即双向转换),支持JSON、YAML、BSON等多种格式。这在处理接口的反序列化之前,是相当简单的。

网上有解决这个问题的方案,但缺少一个简单的框架来处理必要的步骤。我一直在努力解决这个问题,因为我非常希望它能够“直接工作”,但似乎总是需要大量针对特定应用(或者更准确地说,针对特定接口)的编码。

我目前的解决方案在 github.com/madkins23/go-serial。它在一定程度上满足了我的需求,但尝试在现有代码体中使用它并不像我期望的那么简单。我不禁在想,是否值得投入更多时间。

很多时候,我都在想,我是不是在尝试做一些反Go的事情。就像当我非常、非常想要动态查找“子类”方法时,因为我习惯于将数据视为来自类,而不是组合结构体(后者很可能解决当前的问题……我记得在某个未完成的个人项目中,我有用Java实现这个解决方案的代码)。通常,我不得不重新调整我的期望,像Gopher一样思考,事情才会变得清晰和简单,我想这里可能也是如此。

我不介意进行一些一般性的讨论。我并不是要构建一个能解决所有人问题的超级框架。这只是从我个人的(并且承认有点傻的)项目中衍生出来的东西。我更感兴趣的是,我是否真的误解了这门语言,我是否应该只做这个那个,一切就会好起来。

由于我希望将评论和讨论集中在一个论坛(就是这个论坛),我已经关闭了github上的评论和讨论功能。还有问题跟踪。真的还没准备好处理问题。


更多关于Golang中接口数据的序列化与反序列化实践的实战教程也可以访问 https://www.itying.com/category-94-b0.html

8 回复

可能确实如此。Go语言的强静态类型要求你提前定义类型。反射机制使用起来较为繁琐,这应该激励你去提前定义类型。

有时为了快速实现反序列化的临时方案,我会解码到 map[string]interface{} 中,但这只是在我确定一个更好的 struct 定义用于解码之前的原型设计阶段。

func main() {
    fmt.Println("hello world")
}

更多关于Golang中接口数据的序列化与反序列化实践的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我非常喜欢阅读这个话题。Deserialize方法可以将流中的对象反序列化到目标对象中。阅读了您的帖子后,我相信,如果没有提供其他来源,Stream使用起来会更方便,现在我可以轻松地反对实现它,并且也可以查看programmeur inhuren oekraine以获取高质量的工作。在这篇文章中,我展示的是我正在进行的研究,而不是一个教程。我会分享我的发现,以防对其他人自己的探索之旅有所帮助。

你好 @mAdkins23

你的自述文件提到:

一个接口可以被任何实现了该接口的类型的实例填充,因此解码器无法知道要生成什么类型来填充该接口。

好的,所以 Go 的 interface{} 类型本身并不提供关于要读取的类型的线索。然而,JSON 数据本身仍然存在。

问题:JSON 数据的定义难道不能为解组数据提供足够的类型信息吗?

我的意思是,如果解组器知道 JSON 结构体在接下来要读取的位置包含一个字符串,那么它就知道必须将其转换为 Go 字符串。

我(显然)遗漏了什么?

理论上,我们可以通过查看 JSON 映射中的数据来区分接口的不同实现(例如,通过查找 Barks 字段来区分 Animal 接口的 CatDog 实现),但这要求反序列化代码与接口的实际实现紧密耦合。

如果反序列化器能够从描述传入数据所有可能形式的元数据中获得帮助呢?这样,反序列化器就不需要了解实际的具体实现。(例如,“我找到了一个 Barks 字段。”)

也许 JSON 数据本身也可以被定义为包含足够的类型信息,从而无需根据检测到的字段来推断类型就能区分“猫”和“狗”(例如,直接说明“这是一只狗”,而不是“嗯,如果它会叫,那它可能是一只狗”)。

在JSON数据中添加一个额外的“类型”字段,或者使用一个包含类型和指向实际数据指针的包装器,是通用的解决方案。

反序列化这些数据格式由相应的库(例如 encoding/jsongopkg.in/yaml.v3)完成。这两个库都使用Go代码中的元数据,并提供钩子来覆盖特定类的行为。这对于接口来说不起作用,因为接口是无法直接附加方法的抽象。

因此,反序列化像下面这样的 struct 就成了问题:

type Cage struct {
    contains Animal
}

其中 Animal 是一个可能是 CatDoginterface

网上有很多关于这个问题的文章(例如 GOLANG JSON SERIALIZATION WITH INTERFACES)。我一直在尝试为自己构建一个框架,以使这个过程比每次出现都设计一个自定义解决方案更简单。

反序列化一个像这样的 struct

type Cage struct {
    contains Animal
}

其中 Animal 是一个可能是 CatDoginterface,这就是问题所在。

你有想要反序列化到那个结构体的 JSON 数据示例吗?

以及,这种尝试用非面向对象语言处理基于继承的数据是学术性质的,还是附带有实际的问题?

编辑:所以,一个通用的反序列化器无法从 Go 代码中获得足够的信息来分析传入的 JSON 数据。没有这些数据,或者没有合适的标准化元数据,反序列化器实际上没有机会。

对于这种情况,像 gojsonq 这样的包会是一个可行的选择吗?

简而言之,jsonq 解析任何 JSON 并提供查询接口,而不是填充好的结构体。

其余部分(即了解数据的结构以及如何查询所需信息)则留给包的用户。

在Go语言中,反序列化(或解组)对于struct对象的工作方式正如你所描述的。解组器使用反射来确定下一个字段是字符串,通过字段名查找,并用JSON字段的内容填充struct中的字段。

当下一个字段是interface时,这是不可能的,因为接口几乎可以由任何类型实例化。无法将方法附加到interface本身来确定要实例化的类型,任何解组代码都必须附加到实现该接口的类型上。唯一可以附加代码来确定类型、实例化它,然后解组该实例化对象的地方,就是拥有该interface作为字段的struct

JSON数据本身通常是无关类型的。在最可能的情况下,数据是Go的struct,等效的JSON数据将是一个映射。事实上,JSON可以解组到map[string]interface{}中。

理论上,可以通过查看JSON映射中的数据来区分接口的不同实现(例如,Animal接口可以通过查找Barks字段来区分CatDog实现),但这要求反序列化代码与接口的实际实现紧密耦合。而且这段代码必须附加到每个拥有该interface字段的struct上,而不是附加到interface本身。

更通用的方法要么在JSON映射中添加一个“type”字段,要么将对象包装在一个带有类型字段的映射中。当前的go-serial实现使用后者。但同样,这个包装/解包装过程必须附加到每个拥有接口字段的“父”struct上。

在Go中处理接口的序列化与反序列化确实需要一些技巧,因为接口类型在运行时才能确定具体类型。下面是一个实践示例,展示如何通过自定义JSON编解码来处理多种格式的接口数据。

首先,定义接口和实现结构体:

package main

import (
    "encoding/json"
    "fmt"
    "gopkg.in/yaml.v3"
)

type Animal interface {
    Speak() string
}

type Dog struct {
    Name string `json:"name" yaml:"name"`
    Breed string `json:"breed" yaml:"breed"`
}

func (d Dog) Speak() string {
    return "Woof!"
}

type Cat struct {
    Name string `json:"name" yaml:"name"`
    Color string `json:"color" yaml:"color"`
}

func (c Cat) Speak() string {
    return "Meow!"
}

对于JSON序列化,使用类型包装器:

type AnimalWrapper struct {
    Type string `json:"type"`
    Data json.RawMessage `json:"data"`
}

func MarshalAnimal(a Animal) ([]byte, error) {
    var wrapper AnimalWrapper
    
    switch v := a.(type) {
    case *Dog:
        wrapper.Type = "dog"
        data, err := json.Marshal(v)
        if err != nil {
            return nil, err
        }
        wrapper.Data = data
    case *Cat:
        wrapper.Type = "cat"
        data, err := json.Marshal(v)
        if err != nil {
            return nil, err
        }
        wrapper.Data = data
    default:
        return nil, fmt.Errorf("unknown animal type")
    }
    
    return json.Marshal(wrapper)
}

func UnmarshalAnimal(data []byte) (Animal, error) {
    var wrapper AnimalWrapper
    if err := json.Unmarshal(data, &wrapper); err != nil {
        return nil, err
    }
    
    switch wrapper.Type {
    case "dog":
        var dog Dog
        if err := json.Unmarshal(wrapper.Data, &dog); err != nil {
            return nil, err
        }
        return &dog, nil
    case "cat":
        var cat Cat
        if err := json.Unmarshal(wrapper.Data, &cat); err != nil {
            return nil, err
        }
        return &cat, nil
    default:
        return nil, fmt.Errorf("unknown animal type: %s", wrapper.Type)
    }
}

对于YAML支持,实现类似的编解码:

func MarshalAnimalYAML(a Animal) ([]byte, error) {
    var result map[string]interface{}
    
    switch v := a.(type) {
    case *Dog:
        result = map[string]interface{}{
            "type": "dog",
            "data": v,
        }
    case *Cat:
        result = map[string]interface{}{
            "type": "cat",
            "data": v,
        }
    default:
        return nil, fmt.Errorf("unknown animal type")
    }
    
    return yaml.Marshal(result)
}

func UnmarshalAnimalYAML(data []byte) (Animal, error) {
    var wrapper struct {
        Type string `yaml:"type"`
        Data map[string]interface{} `yaml:"data"`
    }
    
    if err := yaml.Unmarshal(data, &wrapper); err != nil {
        return nil, err
    }
    
    switch wrapper.Type {
    case "dog":
        dataBytes, _ := json.Marshal(wrapper.Data)
        var dog Dog
        if err := json.Unmarshal(dataBytes, &dog); err != nil {
            return nil, err
        }
        return &dog, nil
    case "cat":
        dataBytes, _ := json.Marshal(wrapper.Data)
        var cat Cat
        if err := json.Unmarshal(dataBytes, &cat); err != nil {
            return nil, err
        }
        return &cat, nil
    default:
        return nil, fmt.Errorf("unknown animal type: %s", wrapper.Type)
    }
}

使用示例:

func main() {
    // JSON序列化示例
    dog := &Dog{Name: "Rex", Breed: "German Shepherd"}
    
    jsonData, err := MarshalAnimal(dog)
    if err != nil {
        panic(err)
    }
    fmt.Printf("JSON: %s\n", jsonData)
    
    animal, err := UnmarshalAnimal(jsonData)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Animal speaks: %s\n", animal.Speak())
    
    // YAML序列化示例
    cat := &Cat{Name: "Whiskers", Color: "Black"}
    
    yamlData, err := MarshalAnimalYAML(cat)
    if err != nil {
        panic(err)
    }
    fmt.Printf("YAML:\n%s\n", yamlData)
    
    animal2, err := UnmarshalAnimalYAML(yamlData)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Animal speaks: %s\n", animal2.Speak())
}

对于BSON格式,可以使用类似的方法,只需替换编解码器。这种模式的关键是使用类型字段来标识具体类型,并在反序列化时根据类型字段创建正确的结构体实例。

回到顶部