Search code examples
jsongounmarshalling

Custom unmarshalling from flat json to nested struct


Lets say I have two struct that are related like this:

type SecretUser struct {
    UserInfo `json:"userInfo"`
    Password string `json:"password"`
}

type UserInfo struct {
    FirstName string `json:"firstName"`
    LastName  string `json:"lastName"`
    Email     string `json:"email"`
}

And I receive a JSON in this form:

{
    "firstName": "nice",
    "lastName":"guy",
    "email":"[email protected]",
    "password":"abc123"
}

I want to unmarshall this JSON into a SecretUser. Is there a better way than doing it like this?

func (u *User) UnmarshalJSON(data []byte) error {
    var objmap map[string]*json.RawMessage
    var password string
    var err error

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

    if err := json.Unmarshal(data, &u.UserInfo); err != nil {
        return err
    }
    
    err = json.Unmarshal(*objmap["password"], &password)
    if err != nil {
        return err
    }

    u.Password = password
    return nil
}

Basically, I partially unmarshall the JSON into a UserInfo struct and then read it again to extract the password. I don't want to create another struct for just unmarshalling this JSON cleanly or use an external library (unless it's part of the standard). Is there a more clean/efficient way of doing this, without reading the JSON twice or setting every field manually from a map?


Solution

  • Simply include the UserData into the SecretUser struct and do not specify a json tag for it.

    type UserInfo struct {
        FirstName string `json:"firstName"`
        LastName  string `json:"lastName"`
        Email     string `json:"email"`
    }
    
    type SecretUser struct {
        UserInfo
        Password string `json:"password"`
    }
    
    func main() {
        data := []byte(`{"firstName": "nice","lastName":"guy","email":"[email protected]","password":"abc123"}`)
        var u SecretUser
        json.Unmarshal(data, &u)
        fmt.Println(u)
    }
    

    Go Play Space example