简体   繁体   中英

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:

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.

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() ).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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