将平面JSON解组成Go中的嵌套结构

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

Unmarshall flat JSON into a nested struct in Go

问题

有一个嵌套结构,我想从一个包含另一个结构的扁平JSON中创建它:

type Todo struct {
    Todo_id   int    `json:"todo_id" db:"todo_id"`
    Todo_name string `json:"todo_name" db:"todo_name"`

    User_id int         `json:"user_id" db:"user_id"`
    Subs    []Sub       `json:"subs" db:"subs"`
    Times   Parsed_Time `json:"times" db:"times"`
}

当我解组JSON时,我收到一个"missing destination name deadline"的错误,因为Deadline在Parsed_Time结构体内部。有没有办法自定义解组JSON,以便可以省略JSON的某些部分而不报错?我想单独创建一个带有空Times的Todo结构体,然后再次运行Unmarshal,将deadline和其他时间戳的剩余部分分别提取到另一个结构体中。这样可以避免对数据库进行两次单独的Get请求。

英文:

There is a nested struct that I want to create from a flat JSON that includes another struct:

type Todo struct {
	Todo_id   int    `json:"todo_id" db:"todo_id"`
	Todo_name string `json:"todo_name" db:"todo_name"`

	User_id int         `json:"user_id" db:"user_id"`
	Subs    []Sub       `json:"subs" db:"subs"`
	Times   Parsed_Time `json:"times" db:"times"`
}

When I unmarshal a JSON I receive a "missing destination name deadline" error because Deadline is inside a Parsed_Time struct. Is there a way to custom unmarshal JSON so that parts of the JSON would be omitted without error? I would like to separately create a Todo struct with an empty Times, then run Unmarshal again to extract deadline and the rest of timestamps separately into another struct. This is to avoid making two separate Get requests to a database.

答案1

得分: 1

是的 - 你可能已经知道,如果一个类型实现了json.Unmarshaler接口,那么当调用json.Unmarshal()并将该类型作为第二个参数时,它将被使用。经常出现的问题是需要在自定义的解组代码中解组接收者的类型。这可以通过多种方式来解决,其中最常见的是使用本地类型来进行解组。通过巧妙地使用类型别名,你可以节省很多重复的代码。

我已经更新了你上面的代码,将Todo字段表示的类型替换为如下所示的存根类型:

type Sub int

type ParsedTime struct {
	Deadline time.Time
	Created  time.Time
}

type Todo struct {
	ID   int          `json:"todo_id" db:"todo_id"`
	Name string       `json:"todo_name" db:"todo_name"`
	UserID int        `json:"user_id" db:"user_id"`
	Subs   []Sub      `json:"subs" db:"subs"`
	Times  ParsedTime `json:"-" db:"times"`
}

请注意,唯一的相关更改是在调用json.Unmarshal时忽略Times字段。我只是更改了字段名称以使我的IDE的lint工具停止报错!有了这些类型,我们可以定义一个自定义的解组器,如下所示:

func (t *Todo) UnmarshalJSON(data []byte) error {
	type TodoJSON Todo

	todo := struct {
		*TodoJSON
		Deadline string `json:"deadline"`
	}{
		TodoJSON: (*TodoJSON)(t),
	}

	if err := json.Unmarshal(data, &todo); err != nil {
		return err
	}

	deadline, err := time.Parse(time.RFC3339, todo.Deadline)
	if err != nil {
		return err
	}

	t.Times.Deadline = deadline

	return nil
}

这段代码使用了两个关键技术。首先,使用类型别名消除了直接使用Todo会导致的无限递归问题。其次,创建一个嵌入*Todo的本地类型消除了完全重新输入Todo类型的字段的需要 - 只需要添加所需的Deadline字段即可。我还假设Deadline是一个time.Time类型,以展示在分配之前可以对该字段进行处理(使用time.Parse())。

英文:

Yes - you probably already know that if a type implements json.Unmarshaler, it will be used when json.Unmarshal() is called with that type as the second parameter. The problem that often occurs is the need to unmarshal the receiver's type as part of the custom unmarshal code. This can be overcome in several ways, the most common of which is to use a local type to do the unmarshaling. You can save a lot of duplicate code with the judicious use of a type alias though.

I've updated your code above to stub out the types represented by Todo's fields as follows:

type Sub int

type ParsedTime struct {
	Deadline time.Time
	Created  time.Time
}

type Todo struct {
	ID   int          `json:"todo_id" db:"todo_id"`
	Name string       `json:"todo_name" db:"todo_name"`
	UserID int        `json:"user_id" db:"user_id"`
	Subs   []Sub      `json:"subs" db:"subs"`
	Times  ParsedTime `json:"-" db:"times"`
}

Note that the only change of relevance is to ignore the Times field when `json.Unmarshal is called. I only changed the field names to get my IDE's linter to shut up! With these types, we can define a custom unmarshaler as follows:

func (t *Todo) UnmarshalJSON(data []byte) error {
	type TodoJSON Todo

	todo := struct {
		*TodoJSON
		Deadline string `json:"deadline"`
	}{
		TodoJSON: (*TodoJSON)(t),
	}

	if err := json.Unmarshal(data, &todo); err != nil {
		return err
	}

	deadline, err := time.Parse(time.RFC3339, todo.Deadline)
	if err != nil {
		return err
	}

	t.Times.Deadline = deadline

	return nil
}

There are two key techniques used in this code. First, using a type alias eliminates the infinite recursion that would occur if Todo was used directly. Second, creating a local type that embeds *Todo eliminates the need to completely retype the Todo type's fields - only the desired Deadline field needs to be added. I also assumed that Deadline was a time.Time to show that this code also allows the field to be processed before it's assigned (time.Parse()).

huangapple
  • 本文由 发表于 2022年6月18日 22:41:22
  • 转载请务必保留本文链接:https://go.coder-hub.com/72670118.html
匿名

发表评论

匿名网友

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

确定