Essential Go XML  Suggest an edit

Custom XML marshaling

Writing custom XML 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 XML 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 unmarshalling 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 := xml.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) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
	t := time.Time(ct)
	v := t.Format(customTimeFormat)
	return e.EncodeElement(v, start)
}

func (ct *customTime) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
	var s string
	err := d.DecodeElement(&s, &start)
	if err != nil {
		return err
	}
	t, err := time.Parse(customTimeFormat, s)
	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 := xml.Marshal(&e)
if err != nil {
	log.Fatalf("xml.Marshal failed with '%s'\n", err)
}
fmt.Printf("\nCustom time XML: %s\n", string(d))
var decoded Event2
err = xml.Unmarshal(d, &decoded)
if err != nil {
	log.Fatalf("xml.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: <Event><What>earthquake</What><When>2018-03-10T22:52:18.820904-08:00</When></Event>

Custom time XML: <Event2><What>earthquake</What><When>2018-10-03</When></Event2>
Decoded custom time: 2018-10-03

Notice that receiver type of UnmashalXML is a pointer to the type.

This is necessary for changes to persist outside the function itself.

Custom marshaling behind the scenes

How does custom marshaling works?

Package XML defines 2 interfaces: Marshaler and Unmarshaler.

type Marshaler interface {
    MarshalXML(e *Encoder, start StartElement) error
}

type Unmarshaler interface {
    UnmarshalXML(d *Decoder, start StartElement) error
}

By implementing those functions we make our type conform to Marshaler or Unmarshaler interface.

XML 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