Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to omit empty fields in GO when generating XML

Tags:

xml

go

I have the following struct:

type CustomAttribute struct {
    Id     string   `xml:"attribute-id,attr,omitempty"`
    Values []string `xml:"value,omitempty"`
}

type Store struct {
    XMLName          xml.Name          `xml:"store"`
    Id               string            `xml:"store-id,attr,omitempty"`
    Name             string            `xml:"name,omitempty"`
    Address1         string            `xml:"address1,omitempty"`
    Address2         string            `xml:"address2,omitempty"`
    City             string            `xml:"city,omitempty"`
    PostalCode       string            `xml:"postal-code,omitempty"`
    StateCode        string            `xml:"state-code,omitempty"`
    CountryCode      string            `xml:"country-code,omitempty"`
    Phone            string            `xml:"phone,omitempty"`
    Lat              float64           `xml:"latitude,omitempty"`
    Lng              float64           `xml:"longitude,omitempty"`
    CustomAttributes []CustomAttribute `xml:"custom-attributes>custom-attribute,omitempty"`
}

and then I initialise the struct as follows:

    store := &Store{
        Id:          storeId,
        Name:        row[4],
        Address1:    row[5],
        Address2:    row[6],
        City:        row[7],
        PostalCode:  row[9],
        StateCode:   row[8],
        CountryCode: row[11],
        Phone:       row[10],
    }

So the CustomAttributes array is always empty, and len(store.CustomAttributes) is 0 so any idea why the generated XML still contains the empty "custom-attributes" tag?

    <custom-attributes></custom-attributes>
like image 583
daniels Avatar asked Feb 08 '23 01:02

daniels


2 Answers

One solution is to make the CustomAttributes field a pointer. It will be omitted when the value is nil. Look for "zero values" in Marshal documentation.

package main

import (
    "encoding/xml"
    "fmt"
    "log"
)

type Store struct {
    XMLName          xml.Name          `xml:"store"`
    CustomAttributes *[]CustomAttribute `xml:"custom-attributes>custom-attribute,omitempty"`
}

type CustomAttribute struct {
    Id     string   `xml:"attribute-id,attr,omitempty"`
    Values []string `xml:"value,omitempty"`
}

func print(store *Store) {
    data, err := xml.Marshal(store)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(string(data))
}

func main() {
    print(&Store{})
    print(&Store{
        CustomAttributes: &[]CustomAttribute{},
    })
    print(&Store{
        CustomAttributes: &[]CustomAttribute{
            {Id: "hello"},
        },
    })
}

Playground

like image 67
Grzegorz Żur Avatar answered Feb 15 '23 11:02

Grzegorz Żur


I think what happens here is that since you specified the name of the element as being nested, custom-attributes>custom-attribute that sort of implied the "outer" ("intermediate"?, "container"?) element, custom-attributes should exist — in part because of nothing prevents you from tagging any number of other fields with names including the same outer element — like custom-attributes>foobar.

Tracking the case none of such fields were marshaled and hence their outer element should not be used is probably too much for the marshaler which — I suppose — is explicitly written to keep as less context as possible while it works.

Hence while I understand you being puzzled, I think this behaviour is understandable once you squint at it a bit more long.

As to what to do about solving it, I'd personally try to be more explicit and wrapped your slice into a struct type, like with

type CustomAttributes struct {
    XMLName xml.Name `xml:"custom-attributes"`
    Items []CustomAttribute `xml:"custom-attributes>custom-attribute"`
}

and then would have a custom marshaler on it:

func (cas CustomAttributes) MarshalXML(e *xml.Encoder,
        start xml.StartElement) (err error) {
    if len(cas.Items) == 0 {
        return nil
     }

    err = e.EncodeToken(start)
    if err != nil {
        return
    }
    err = e.Encode(cas.Items)
    if err != nil {
        return
    }
    return e.EncodeToken(xml.EndElement{
        Name: start.Name,
    })
}

Playground link.

like image 45
kostix Avatar answered Feb 15 '23 10:02

kostix