Ivan
Ivan

Reputation: 4234

Golang XML customize output

I'm trying to create an XML implementing the MarshalXML output. But currently i'm facing several issues.

The structure i'm using for storing the data is:

type Edition struct {
    Launch         string             `xml:"launch" json:"launch"`
    Code           string             `xml:"code" json:"code"`
    Names          []NameNode         `xml:"names>name"`
    Cards          CardsComposition   `xml:"cards" json:"cards,omitempty"`
    Preconstructed PreconstructedInfo `xml:"preconstructed" json:"preconstructed,omitempty"`
    Vault          *struct{}          `xml:"vault" json:"vault"`
    Online         *struct{}          `xml:"online" json:"online"`
}

What i want is: If the Preconstructed field is not set, don't put the <preconstructed> tag (using the standard marshaler it put it even if it is empty).

So what i did is:

func (preconstructed PreconstructedInfo) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
    if (PreconstructedInfo{} == preconstructed) {
        return nil
    }
    return e.EncodeElement(preconstructed, start)
}

And it apparently works, if I use it for encoding a single Edition entity. But if I try to encode an array of Edition entities, I get the following error:

runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow

(the array is ~200 entries)

So what I don't understand is:

Upvotes: 4

Views: 4435

Answers (2)

snath03
snath03

Reputation: 130

This should prevent the infinite recursion -

func (preconstructed PreconstructedInfo) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
    if (PreconstructedInfo{} == preconstructed) {
        return nil
    }
    type localType PreconstructedInfo
    var localVar PreconstructedInfo = localType(preconstructed)
    return e.EncodeElement(localVar, start)
}

Basically we're creating a local type (localType) that's identical to the type being marshaled (PreconstructedInfo), and create a variable of this type (localVar) having the same data. Then we run EncodeElement on the local variable of the local type, thereby preventing an infinite loop.

I learnt this trick from here -
https://jhall.io/posts/go-json-tricks-slightly-custom-marshaler/#breaking-the-loop-with-a-local-type

Upvotes: 2

Ivan
Ivan

Reputation: 4234

Ok, i'll answer myself since i finally solved that issue.

So apparently one of the problem is that EncodeElement is making use of MarshalXML, and with a huge file it cause an explosion function calls.

Anyway the solution is to manually encode all components of the element.

So in that case i did that:

// MarshalXML generate XML output for PrecsontructedInfo
func (preconstructed PreconstructedInfo) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) {
    if (PreconstructedInfo{} == preconstructed) {
        return nil
    }
    if preconstructed.Decks > 0 {
        start.Attr = []xml.Attr{xml.Attr{Name: xml.Name{Local: "decks"}, Value: strconv.Itoa(preconstructed.Size)}}
    }
    if strings.Compare(preconstructed.Type, "") != 0 {
        start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "type"}, Value: preconstructed.Type})
    }

    err = e.EncodeToken(start)
    e.EncodeElement(preconstructed.Size, xml.StartElement{Name: xml.Name{Local: "size"}})
    return e.EncodeToken(xml.EndElement{Name: start.Name})
}

So what i did is:

  1. Check if the field is empty or not, if yes return null (the same as in my question)
  2. If not empty, check for the values contained in PreconstructedInfo, and add them in their relevant position, add first attributes to the start element. start.Attr will contain the xml attributes for the tag being Marshalled, the syntax for it is quite easy, you specify the name and the value. This has to be done before calling e.EncodeToken(start).
  3. After that encode the other elements of the tag into the current start Element. As you can see you must encode the tag using the xml.StartElement, in a similar way as for the attribute.
  4. Finally you can close the start Tag.

In that way it will generate the xml tag only if data is available, and add attributes/childs only if they have a value, if they are empty or 0 it will not be added.

Upvotes: 5

Related Questions