在Golang中序列化模型

huangapple go评论76阅读模式
英文:

Serializing Models in Golang

问题

我正在尝试将我的代码分离为模型(models)和序列化器(serializers),其中序列化器负责处理所有的 JSON 责任,即关注点分离。我还希望能够调用模型对象 obj.Serialize() 来获取序列化器结构体 obj,然后进行编组(marshal)。因此,我想出了以下设计。为了避免循环导入,我不得不在序列化器中使用接口,这导致我在模型中使用了 getter 方法。我读到过,getter/setter 不是 Go 代码的惯用写法,而且我不想在我的模型中到处都有“样板”getter 代码。对于我想要实现的目标,有没有更好的解决方案,同时保持关注点分离和 obj.Serialize()

src/
    models/
        a.go
    serializers/
        a.go

models/a.go

import "../serializers"

type A struct {
  name string
  age int // 不要编组我
}

func (a *A) Name() string {
  return a.name
}

// Serialize 将 A 转换为 ASerializer
func (a *A) Serialize() interface{} {
    s := serializers.ASerializer{}
    s.SetAttrs(a)
    return s
}

serializers/a.go

// AInterface 用于获取 A 的属性
type AInterface interface {
  Name() string
}

// ASerializer 包含 JSON 字段和值
type ASerializer struct {
  Name `json:"full_name"`
}

// SetAttrs 为 ASerializer 设置属性
func (s *ASerializer) SetAttrs(a AInterface) {
  s.Name = a.Name()
}

希望这能帮到你!

英文:

I'm trying to separate my code into models and serializers with the idea that there be defined serializers that handles all json responsibilities, i.e. separation of concerns. I also want to be able to call a model object obj.Serialize() to get the serializer struct obj that I can then marshal. Therefore, I've come up with the following design. To avoid circular import I had to use interfaces in my serializers which leads to using getters in my models. I've read that getters/setters aren't idiomatic go code and I would prefer not to have "boilerplate" getter code all over my models. Is there a better solution to what I want to accomplish, keeping in mind I want separation of concerns and obj.Serialize()?

src/
    models/
        a.go
    serializers/
        a.go

models/a.go

import "../serializers"

type A struct {
  name string
  age int // do not marshal me
}

func (a *A) Name() string {
  return a.name
}

// Serialize converts A to ASerializer
func (a *A) Serialize() interface{} {
	s := serializers.ASerializer{}
	s.SetAttrs(a)
	return s
}

serializers/a.go

// AInterface used to get Post attributes
type AInterface interface {
  Name() string
}

// ASerializer holds json fields and values
type ASerializer struct {
  Name `json:"full_name"`
}

// SetAttrs sets attributes for PostSerializer
func (s *ASerializer) SetAttrs(a AInterface) {
  s.Name = a.Name()
}

答案1

得分: 4

看起来你实际上是想在内部结构体和JSON之间进行翻译。我们可以利用json库来实现。

如果你希望特定的库以特定的方式处理结构体字段,可以使用标签。下面的示例展示了如何使用json标签告诉json库不将字段age编组为JSON,并且只在字段jobTitle不为空时添加字段jobTitle,并且在JSON中将字段jobTitle称为title。当Go中的结构体包含大写(导出的)字段,但你连接到的JSON API使用小写键时,这种重命名功能非常有用。

type A struct {
  Name     string
  Age      int `json:"-"`
  location string // 未导出(私有)字段不会包含在JSON编组输出中
  JobTitle string `json:"title,omitempty"`
}

如果你需要预先计算一个字段,或者只是在结构体的JSON输出中添加一个不是该结构体成员的字段,我们可以通过一些技巧来实现。当JSON对象再次解码为Golang结构体时,不符合条件的字段(在检查重命名字段和大小写差异之后)将被忽略。

// AntiRecursionMyStruct用于避免在MashalJSON中出现无限递归。仅供json包使用。
type AntiRecursionMyStruct MyStruct

// MarshalJSON实现了json.Marshaller接口。这使我们可以按照自己的方式将该结构体编组为JSON。在这种情况下,我们添加了一个字段,然后将其转换为另一种不实现json.Marshaller接口的类型,从而让json库为我们编组它。
func (t MyStruct) MarshalJSON() ([]byte, error) {
	return json.Marshal(struct {
		AntiRecursionMyStruct
		Kind string // 我们想要添加的字段,在这种情况下是生成该结构体的Golang类型的文本表示
	}{
		AntiRecursionMyStruct: AntiRecursionMyStruct(t),
		Kind: fmt.Sprintf("%T", MyStruct{}),
	})
}

请记住,JSON只会包含你导出的(大写的)结构体成员。我多次犯过这个错误。

作为一般规则,如果某个东西看起来太复杂,那可能有更好的方法来做。

英文:

It looks like you are actually trying to translate between your internal structs and json. We can start by taking advantage of the json library.

If you want certain libraries to handle your struct fields in certain ways, there are tags. This example shows how json tags tell json to never marshal the field age into json, and to only add the field jobTitle if it is not empty, and that the field jobTitle is actually called title in json. This renaming feature is very useful when structs in go contain capitalized (exported) fields, but the json api you're connecting to uses lowercase keys.

type A struct {
  Name     string
  Age      int `json:"-"`// do not marshal me
  location      string // unexported (private) fields are not included in the json marshal output
  JobTitle string `json:"title,omitempty"` // in our json, this field is called "title", but we only want to write the key if the field is not empty.
}

If you need to precompute a field, or simply add a field in your json output of a struct that isn't a member of that struct, we can do that with some magic. When json objects are decoded again into golang structs, fields that don't fit (after checking renamed fields and capitalization differences) are simply ignored.

// AntiRecursionMyStruct avoids infinite recursion in MashalJSON. Only intended for the json package to use.
type AntiRecursionMyStruct MyStruct

// MarshalJSON implements the json.Marshaller interface. This lets us marshal this struct into json however we want. In this case, we add a field and then cast it to another type that doesn't implement the json.Marshaller interface, and thereby letting the json library marshal it for us.
func (t MyStruct) MarshalJSON() ([]byte, error) {
	return json.Marshal(struct {
		AntiRecursionMyStruct
		Kind string // the field we want to add, in this case a text representation of the golang type used to generate the struct
	}{
		AntiRecursionMyStruct: AntiRecursionMyStruct(t),
		Kind: fmt.Sprintf("%T", MyStruct{}),
	})
}

Keep in mind that json will only include your exported (capitalized) struct members. I've made this misstake multiple times.

As a general rule, if something seems too complicated, there's probably a better way to do it.

huangapple
  • 本文由 发表于 2016年1月14日 02:04:41
  • 转载请务必保留本文链接:https://go.coder-hub.com/34773908.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定