Golang XML 自定义输出

4
我正在尝试创建一个实现MarshalXML输出的XML,但目前我遇到了一些问题。
我用于存储数据的结构是:
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"`
}

我想要的是: 如果“Preconstructed”字段没有设置,就不要放置<preconstructed>标签(使用标准编组器时,即使为空,它也会被放置)。
所以我所做的是:
func (preconstructed PreconstructedInfo) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
    if (PreconstructedInfo{} == preconstructed) {
        return nil
    }
    return e.EncodeElement(preconstructed, start)
}

“而且,如果我用它对单个版本实体进行编码,显然它可以工作。但是,如果我尝试对版本实体数组进行编码,就会出现以下错误:”
runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow

“数组大约有200个条目。”
“我不明白的是:”
- “为什么只有在我尝试自定义xml时才会出现堆栈溢出问题,这种情况下还尝试删除空标记,以便‘节省空间’?” - “最好的方法是什么?有人能解释一下如何为go实现自定义XML Marshaler吗?我找到了很多JSON marshal的例子,但几乎没有关于XML的。”

根据你的堆栈溢出,你引起了一个无限循环,因此EncodeElement正在调用MarshalXML,而MarshalXML又在调用EncodeElement。 - GarMan
好的,但为什么只有在尝试编排版本元素数组时才会发生这种情况,而不是在编排单个元素时? - Ivan
2个回答

4

好的,既然我最终解决了这个问题,那么我会回答自己。

显然,问题之一是EncodeElement正在使用MarshalXML,并且对于一个庞大的文件,它会导致函数调用爆炸。

无论如何,解决方案是手动编码元素的所有组件。

因此,在这种情况下,我就这样做了:

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

所以我做的是:

  1. 检查字段是否为空,如果为空返回null(与我的问题中相同)
  2. 如果不为空,则检查PreconstructedInfo中包含的值,并将它们添加到它们相关的位置,首先将属性添加到开始元素。start.Attr将包含要编组的标记的xml属性,其语法非常简单,您指定名称和值。在调用e.EncodeToken(start)之前必须执行此操作。
  3. 之后,将标记的其他元素编码到当前的开始元素中。正如您所看到的,您必须使用xml.StartElement对标记进行编码,类似于属性。
  4. 最后,您可以关闭开始标记。

这样只有在有数据可用时才会生成xml标记,并且仅在它们具有值时添加属性/子级,如果它们为空或为0,则不会添加。


FYI在err = e.EncodeToken(start)这一行中-错误被忽略了。 - colm.anseo

1

这应该可以防止无限递归 -

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

基本上,我们正在创建一个本地类型(localType),该类型与要编组的类型(PreconstructedInfo)相同,并创建一个具有相同数据的此类型的变量(localVar)。 然后我们在本地类型的本地变量上运行EncodeElement,从而防止无限循环。
我从这里学到了这个技巧-
https://jhall.io/posts/go-json-tricks-slightly-custom-marshaler/#breaking-the-loop-with-a-local-type

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接