简体   繁体   English

在 Go 中将平面 JSON 解组为嵌套结构

[英]Unmarshall flat JSON into a nested struct in Go

There is a nested struct that I want to create from a flat JSON that includes another struct:我想从包含另一个结构的平面 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"`
}

When I unmarshal a JSON I receive a "missing destination name deadline" error because Deadline is inside a Parsed_Time struct.当我解组 JSON 时,我收到“缺少目标名称截止日期”错误,因为截止日期位于 Parsed_Time 结构内。 Is there a way to custom unmarshal JSON so that parts of the JSON would be omitted without error?有没有办法自定义解组 JSON,以便在没有错误的情况下省略部分 JSON? 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.我想单独创建一个带有空 Times 的 Todo 结构,然后再次运行 Unmarshal 以将截止日期和其余时间戳分别提取到另一个结构中。 This is to avoid making two separate Get requests to a database.这是为了避免向数据库发出两个单独的 Get 请求。

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.是的 - 你可能已经知道如果一个类型实现了json.Unmarshaler ,它将在json.Unmarshal()被调用时使用该类型作为第二个参数。 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:我已经更新了您上面的代码,以存根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"`
}

Note that the only change of relevance is to ignore the Times field when `json.Unmarshal is called.请注意,相关性的唯一变化是在调用 `json.Unmarshal 时忽略Times字段。 I only changed the field names to get my IDE's linter to shut up!我只更改了字段名称以让我的 IDE 的 linter 闭嘴! 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.首先,使用类型别名消除了直接使用Todo时会发生的无限递归。 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.其次,创建嵌入*Todo的本地类型消除了完全重新键入Todo类型字段的需要 - 只需要添加所需的Deadline字段。 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() ).我还假设Deadlinetime.Time以表明此代码还允许在分配字段之前对其进行处理( time.Parse() )。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM