user121169
user121169

Reputation: 51

`,omitempty` and pointer fields in Go's standard `xml` package

When marshalling a boolean field of a struct to XML, the ,omitempty option is not very useful for most purposes—false is a zero value of boolean variables in Go and, as expected, boolean ,omitempty fields with value of false are ignored when marshalling. The most often suggested solution seems to be using pointers which would allow to indicate whether a value is present at all. Here is my basic implementation of this idea:

package main

import (
    "encoding/xml"
    "fmt"
)

type Person struct {
    XMLName   xml.Name `xml:"person"`
    IsMarried *bool    `xml:"married"`           // Required field.
    IsRetired *bool    `xml:"retired,omitempty"` // Optional field.
}

func boolPointer(b bool) *bool {
    return &b
}

func printPersonXml(person Person) {
    output, err := xml.MarshalIndent(person, "  ", "    ")
    if err != nil {
        fmt.Printf("error: %v\n", err)
    } else {
        fmt.Println(string(output))
    }
}

func main() {
    person := Person{
        IsMarried: boolPointer(true),
        IsRetired: nil,
    }
    printPersonXml(person)
}

This works as expected and produces the output

  <person>
      <married>true</married>
  </person>

However, it seems that in this case ,omitempty option loses its meaning entirely. Any field with nil value will not be included in the produced XML code. For example, if I change the contents of main() to

person := Person{
    IsMarried: nil,
    IsRetired: nil,
}
printPersonXml(person)

the output becomes

  <person></person>

even though I would prefer

  <person>
      <married></married>
  </person>

As noted here, this is probably the expected behaviour: "Marshal handles a pointer by marshaling the value it points at or, if the pointer is nil, by writing nothing."

However, is it possible to achieve my preferred behaviour using standard xml package? If yes, would that require introducing a new type and a custom MarshalXML() method for it?

Although here I focus on boolean variables for obvious reasons, I would like to extend this approach to pointers of other basic types as well.

Upvotes: 2

Views: 758

Answers (1)

mkopriva
mkopriva

Reputation: 38203

"is it possible to achieve my preferred behaviour using standard xml package? If yes, would that require introducing a new type and a custom MarshalXML() method for it?" -- Yes, and yes.

For example:

type Bool struct {
    Bool    bool
    IsValid bool
}

func (b Bool) MarshalXML(e *xml.Encoder, se xml.StartElement) error {
    if b.IsValid {
        return e.EncodeElement(b.Bool, se)
    }
    return e.EncodeElement("", se)
}

type OptionalBool struct {
    Bool    bool
    IsValid bool
}

func (b OptionalBool) MarshalXML(e *xml.Encoder, se xml.StartElement) error {
    if b.IsValid {
        return e.EncodeElement(b.Bool, se)
    }
    return nil
}

https://play.golang.org/p/C2fuBfv69Ny

Upvotes: 3

Related Questions