Introduction
In this blog post, we will discuss an interesting case in Golang where using a custom `UnmarshalJSON` method on a struct with both inner and outer fields results in only the inner fields being unmarshaled. We will look into why this occurs and suggest two alternative solutions to overcome this issue. Let's start by understanding the problem.
The Problem
Consider the following Go code with a struct named **`Person`** that has inner and outer fields:
type Name struct {
First string `json:"first"`
Last string `json:"last"`
}
type Person struct {
Name
Age int `json:"age"`
}
Now, we want to implement a custom `UnmarshalJSON` method for the **`Person`** struct:
func (p *Person) UnmarshalJSON(data []byte) error {
type alias Person
aux := struct {
*alias
Age int `json:"age"`
}{
alias: (*alias)(p),
}
if err := json.Unmarshal(data, &aux); err != nil {
return err
}
p.Age = aux.Age
return nil
}
The expected behavior is that the custom `UnmarshalJSON` method should unmarshal both inner (Name) and outer (Age) fields. However, it turns out that only the inner fields are unmarshaled, and the outer field (Age) is ignored.
Why This Happens
The issue arises due to the use of the embedded struct Name
in the Person
struct. When the custom UnmarshalJSON
method is called, it tries to unmarshal the JSON data into the embedded struct first. The outer field (Age) is then shadowed by the inner field with the same name in the auxiliary struct, which causes it to be ignored during the unmarshaling process.
Alternatives
To overcome this issue, we have two alternative solutions:
1. Refactor the struct to have no inner fields:
type Person struct {
FirstName string `json:"first"`
LastName string `json:"last"`
Age int `json:"age"`
}
By doing this, we avoid the issue of field shadowing and ensure all fields are unmarshaled correctly.
1. Create a separate UnmarshalJSON
method for the inner struct:
func (n *Name) UnmarshalJSON(data []byte) error {
type alias Name
aux := &struct {
*alias
}{
alias: (*alias)(n),
}
if err := json.Unmarshal(data, aux); err != nil {
return err
}
return nil
}
By creating a separate UnmarshalJSON
method for the inner struct (**`Name`**), we ensure that the JSON data is correctly unmarshaled for both the inner and outer fields.
Conclusion
In this blog post, we explored the peculiar behavior of Golang's custom UnmarshalJSON
method when used with a struct containing both inner and outer fields. We discovered that only the inner fields are unmarshaled, and the outer fields are ignored due to field shadowing. To resolve this issue, we presented two alternative solutions - either refactor the struct to have no inner fields or create a separate `UnmarshalJSON` method for the inner struct.