英文:
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()
).
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论