Essential Go JSON  Suggest an edit

Custom JSON marshaling

Writing 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":"2018-10-18T02:25:08.133422-07:00"}

Custom time JSON: {"What":"earthquake","When":"2018-18-10"}
Decoded custom time: "2018-18-10"

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.

  ↑ ↓ to navigate     ↵ to select     Esc to close