英文:
MarshalXML for root element
问题
我似乎在调用marshaller的时候遇到了问题,它没有调用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
,但它似乎会按照相同的路径构建起始模板和根元素,然后调用MarshalXML
来处理Children
节点。有没有一种简洁的方法将XmlNode
转换为xml.StartElement
,以便我可以传递它?我可以在XmlNode
上直接编写一个函数,通过创建一个StartElement
并调用EncodeElement
或Encode
来开始编组,但我想避免这样做,并坚持使用xml.Marshal
调用,以防止其他开发人员产生困惑。
有什么想法吗?
这是上述代码的可运行形式的Go Playground链接。
英文:
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.
答案1
得分: 2
要翻译的内容如下:
要么传递一个指针,例如 xml.Marshal(&root)
(playground),要么将接收器类型从指针更改为非指针,例如 func (n *XmlNode) MarshalXML
=> func (n XmlNode) MarshalXML
(playground)。
https://go.dev/ref/spec#Method_sets
> 类型 T
的方法集包含所有以接收器类型 T
声明的方法。相应指针类型 *T
的方法集包含所有以接收器 *T
或 T
声明的方法(也就是说,它还包含 T
的方法集)。
>
> 类型的方法集决定了该类型实现的接口以及可以使用该类型接收器调用的方法。
上述内容意味着,类型为 XmlNode
的变量 root
不实现 xml.Marshaler
,因为 MarshalXML
方法不是 XmlNode
的方法集的一部分。然而,类型为 *XmlNode
的变量 &root
实现 xml.Marshaler
,因为 MarshalXML
方法是 *XmlNode
的方法集的一部分。
当你将方法的接收器更改为非指针,例如 func (n XmlNode) MarshalXML
,那么类型为 XmlNode
的变量 root
将实现 xml.Marshaler
,因为 MarshalXML
方法将成为 XmlNode
的方法集的一部分。而且,引用规范还指出,类型为 *XmlNode
的变量 &root
也将实现 xml.Marshaler
,因为 MarshalXML
方法,即使在 XmlNode
上声明,也将成为 *XmlNode
的方法集的一部分。
请注意,在直接调用具体类型上的方法时,指针与非指针的方法集并不是那么重要。例如,如果你有一个类型为 XmlNode
的变量 root
,并且你想调用方法 DoXyz
,该方法以指针接收器声明为 func (n *XmlNode) DoXyz()
,那么你不需要 &root
,表达式 root.DoXyz
将编译通过。
> 如果方法集(类型为 x
的)包含 m
,并且参数列表可以分配给 m
的参数列表,方法调用 x.m()
是有效的。如果 x
是可寻址的,并且 &x
的方法集包含 m
,则 x.m()
是 (&x).m()
的简写形式。
英文:
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.
> 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()
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论