Custom JSON marshaling
suggest changeWriting custom JSON marshalling
Sometimes a type doesn’t have an obvious mapping to JSON.
How would you serialize time.Time
? There are so many possibilities.
Go provides a default JSON mapping for time.Time
. We can implement custom marshaller for user-defined types like structs.
For existing types we can define a new (but compatible) type.
Here’s a custom marshalling and unmarshaling for time.Time
that only serializes year/month/date part:
type Event struct {
What string
When time.Time
}
e := Event{
What: "earthquake",
When: time.Now(),
}
d, err := json.Marshal(&e)
if err != nil {
log.Fatalf("json.MarshalIndent failed with '%s'\n", err)
}
fmt.Printf("Standard time JSON: %s\n", string(d))
type customTime time.Time
const customTimeFormat = `"2006-02-01"`
func (ct customTime) MarshalJSON() ([]byte, error) {
t := time.Time(ct)
s := t.Format(customTimeFormat)
return []byte(s), nil
}
func (ct *customTime) UnmarshalJSON(d []byte) error {
t, err := time.Parse(customTimeFormat, string(d))
if err != nil {
return err
}
*ct = customTime(t)
return nil
}
type Event2 struct {
What string
When customTime
}
e := Event2{
What: "earthquake",
When: customTime(time.Now()),
}
d, err := json.Marshal(&e)
if err != nil {
log.Fatalf("json.Marshal failed with '%s'\n", err)
}
fmt.Printf("\nCustom time JSON: %s\n", string(d))
var decoded Event2
err = json.Unmarshal(d, &decoded)
if err != nil {
log.Fatalf("json.Unmarshal failed with '%s'\n", err)
}
t := time.Time(decoded.When)
fmt.Printf("Decoded custom time: %s\n", t.Format(customTimeFormat))
notCustom()
custom()
Standard time JSON: {"What":"earthquake","When":"2020-07-12T07:31:46.88184504Z"}
Custom time JSON: {"What":"earthquake","When":"2020-12-07"}
Decoded custom time: "2020-12-07"
Notice that receiver type of UnmashalJSON
is a pointer to the type.
This is necessary for changes to persist outside the function itself.
Marshaling structs with private fields
Consider a struct with both exported and unexported fields:
type MyStruct struct {
uuid string
Name string
}
Imagine you want to Marshal()
this struct into valid JSON for storage in something like etcd.
However, since uuid
in not exported, the json.Marshal()
skips it.
To marshal private fields without making them public we can use a custom marshaller:
type MyStruct struct {
uuid string
Name string
}
func (m MyStruct) MarshalJSON() ([]byte, error) {
j, err := json.Marshal(struct {
Uuid string
Name string
}{
Uuid: m.uuid,
Name: m.Name,
})
if err != nil {
return nil, err
}
return j, nil
}
s := MyStruct{
uuid: "uid-john",
Name: "John",
}
d, err := json.Marshal(&s)
if err != nil {
log.Fatalf("json.MarshalIndent failed with '%s'\n", err)
}
fmt.Printf("Person in compact JSON: %s\n", string(d))
Person in compact JSON: {"Uuid":"uid-john","Name":"John"}
Custom marshaling behind the scenes
How does custom marshaling works?
Package JSON defines 2 interfaces: Marshaler
and Unmarshaler
.
type Marshaler interface {
MarshalJSON() ([]byte, error)
}
type Unmarshaler interface {
UnmarshalJSON([]byte) error
}
By implementing those functions we make our type conform to Marshaler
or Unmarshaler
interface.
JSON encoder / decoder checks if the value being encoded conforms to those interfaces and will call those functions instead of executing default logic.