根元素的 MarshalXML

MarshalXML for root element

我似乎无法让编组器为根节点调用 MarshalXML。我有一个用于存储 XML 的结构,如下所示:

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

我有一个 MarshalXML 函数,如下所示:

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)
}

我用类似于以下的代码调用它:

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))
}

运行时,我看到以下输出:

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

我期待:

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

我知道我可以将名称编码到特定属性中,但我更关心如何处理地图中的属性。我也可能不应该在那里修改开始元素,但它似乎适用于此。我的问题是 MarshalXML 不会为根元素调用,但它会为任何子元素调用。所以,即使我在那里做了一些正确的事情,我也不确定我是否可以拦截那个根元素来适当地修改它。

我简单地尝试过直接使用xml.Encode,但它似乎遵循相同的路径构建启动模板和根目录,然后为Children节点调用MarshalXML .有没有一种干净的方法可以将 XmlNode 转换为 xml.StartElement 以便我可以将其传递?我可以直接在 XmlNode 上编写一个函数,该函数将通过创建 StartElement 并调用 EncodeElementEncode 来开始编组,但我试图避免这种情况并坚持使用 xml.Marshal 调用,以防止对其他开发者造成混淆。

有什么想法吗?

这里是上述代码的可运行形式 Go Playground link。

要么传入一个指针,即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.

上面的意思是 XmlNode 类型的变量 root 没有实现 xml.Marshaler 因为 MarshalXML 方法不是 XmlNode 方法集的一部分。但是 &root 类型 *XmlNode 确实实现了 xml.Marshaler 因为 MarshalXML 方法是 *XmlNode 的一部分的方法集。

当您将方法的接收者更改为非指针时,即 func (n XmlNode) MarshalXML 那么,XmlNode 类型的 root 将实现 xml.Marshaler 因为 MarshalXML 方法将成为XmlNode 方法集的一部分。引用的规范还说,&root 类型 *XmlNode 也将实现 xml.Marshaler 因为 MarshalXML 方法,即使在 XmlNode 上声明,也将成为 *XmlNode 方法集的一部分。


请注意,当您想直接在具体类型上而不是通过接口调用方法时,考虑此指针与非指针方法集的内容并不那么重要。例如,如果您有变量 root,如果类型为 XmlNode,并且您想要调用方法 DoXyz,该方法使用指针接收器声明为 func (n *XmlNode) DoXyz(),那么你不需要 &root,表达式 root.DoXyz 会编译得很好。

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().