Home > database >  MarshalXML for root element
MarshalXML for root element

Time:12-10

I seem to be having a problem getting the marshaller to call MarshalXML for the root node. I have a struct for storing the XML like so:

type XmlNode struct {
    Name     string            `xml:"-"`
    Attrs    map[string]string `xml:"-"`
    Children []XmlNode         `xml:",any"`
}

I have a MarshalXML function like so:

func (n *XmlNode) MarshalXML(encoder *xml.Encoder, start xml.StartElement) error {
    fmt.Println("Inside MarshalXML")

    // Name
    start.Name = xml.Name{Local: n.Name}

    // Attrs
    for k, v := range n.Attrs {
        start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: k}, Value: v})
    }

    type node XmlNode
    return encoder.EncodeElement((*node)(n), start)
}

And I'm calling it with code similar to:

func main() {
    root := XmlNode{Name: "root", Attrs: map[string]string{}}
    root.Attrs["rootAttr"] = "rootValue"

    child := XmlNode{Name: "child", Attrs: map[string]string{}}
    child.Attrs["childAttr"] = "childValue"

    root.Children = append(root.Children, child)
    out, _ := xml.Marshal(root)
    fmt.Println(string(out))
}

When it runs, I see the following output:

Inside MarshalXML
<XmlNode><child childAttr="childValue"></child></XmlNode>

I'm expecting:

Inside MarshalXML
Inside MarshalXML
<root rootAttr="rootValue"><child childAttr="childValue"></child></root>

I know that I can code the name into a specific attribute, but I'm more concerned with handling the attributes from a map. I'm also likely not supposed to be modifying the start element there, but it seems to work for this. My problem is that the MarshalXML doesn't get called for the root element, but it does for any children. So, even if I were to do something correctly there, I'm not sure I can intercept that root element to modify it appropriately.

I briefly tried using xml.Encode directly, but it seems to follow into the same path to build the start template and root, and then calls the MarshalXML for the Children node. Is there a clean way to get XmlNode converted into xml.StartElement so I can pass that in? I can write a function on XmlNode directly that'll start the marshalling by making a StartElement and calling EncodeElement or Encode, but I'm trying to avoid that and stick with the xml.Marshal call, to prevent some confusion for other devs.

Any thoughts?

Here's a Go Playground link to the above code in a runnable form.

CodePudding user response:

Either pass in a pointer, i.e. xml.Marshal(&root) (playground), or change the receiver type from pointer to non-pointer, i.e. func (n *XmlNode) MarshalXML => func (n XmlNode) MarshalXML (playground).


https://go.dev/ref/spec#Method_sets

The method set of type T consists of all methods declared with receiver type T. The method set of the corresponding pointer type *T is the set of all methods declared with receiver *T or T (that is, it also contains the method set of T).

The method set of a type determines the interfaces that the type implements and the methods that can be called using a receiver of that type.

The above means that the variable root, which is of type XmlNode, DOES NOT implement xml.Marshaler because the MarshalXML method IS NOT part of XmlNode's method set. However &root, which is of type *XmlNode, DOES implement xml.Marshaler because the MarshalXML method IS part of *XmlNode's method set.

When you change the method's receiver to non-pointer, i.e. func (n XmlNode) MarshalXML then, root, which is of type XmlNode, will implement xml.Marshaler because the MarshalXML method will be part of XmlNode's method set. And the cited spec also says that, &root, which is of type *XmlNode will also implement xml.Marshaler because the MarshalXML method, even though declared on XmlNode, will also be part of *XmlNode's method set.


Note that this pointer vs non-pointer method set stuff is not so important to think about when you want to invoke a method on a concrete type directly as opposed to through an interface. For example if you have the variable root which if of type XmlNode, and you want to invoke the method DoXyz, which is declared with a pointer receiver as func (n *XmlNode) DoXyz(), then you DO NOT need &root, the expression root.DoXyz will compile just fine.

https://go.dev/ref/spec#Calls

A method call x.m() is valid if the method set of (the type of) x contains m and the argument list can be assigned to the parameter list of m. If x is addressable and &x's method set contains m, x.m() is shorthand for (&x).m().

  • Related