复杂的Go xml.Unmarshal()用例

5

我将尝试在Go语言中对以下格式的XML进行反序列化:

<property>
  <code value="abc"/>
  <valueBoolean value="true"/>
</property>

或者这个

<property>
  <code value="abc"/>
  <valueString value="apple"/>
</property>

或者这个

<property>
  <code value="abc"/>
  <valueDecimal value="3.14159"/>
</property>

将等等内容转化为这样的格式:
type Property struct {
    Code  string      `xml:"code>value,attr"`
    Value interface{}
}

标签中的(valueBooleanvalueString等)告诉我值属性的类型。我正在尝试解析的XML是国际标准的一部分,因此我无法控制其定义。实现解析这些内容不难,可以做如下处理:

var value string
for a := range se.Attr {
    if a.Name.Local == "value" {
        value = a.Value
    } else {
        // Invalid attribute
    }
}
switch se.Name.Local {
case "code":
case "valueBoolean":
    property.Value = value == "true"
case "valueString":
    property.Value = value
case "valueInteger":
    property.Value, err = strconv.ParseInteger(value)
case "valueDecimal":
    property.Value, err = strconv.ParseFloat(value)
...
}

但我不知道如何告诉XML包去找它,而这些东西被埋在其他XML中,我真的很想使用xml.Unmarshal进行处理。或者,我可以重新定义类型为:

type Property struct {
    Code         string `xml:"code>value,attr"`
    ValueBoolean bool   `xml:"valueBoolean>value,attr"`
    ValueString  string `xml:"valueString>value,attr"`
    ValueInteger int    `xml:"valueInteger>value,attr"`
    ValueDecimal float  `xml:"valueDecimal>value,attr"`
}

但这种方法非常低效,尤其是考虑到我有大量这些东西的实例,这样做没有办法在不添加另一个属性来指示类型的情况下推导出类型。

我能否以某种方式将其绑定到正常的XML取消编组方法中,只是手动处理棘手的部分,或者需要从头编写此类型的完整取消编组器?


我认为编写自定义的反序列化程序是实现它的唯一途径。 - OneOfOne
这是其他 XML 的一部分,其定义有数千行。我是否需要为整个内容编写自己的反序列化程序? - Scott Deerwester
不,只针对属性结构体。 - OneOfOne
啊,我明白了。如果我在结构体中添加一个Unmarshal方法,那么xml.Decoder.DecodeElement会自动调用它吗? - Scott Deerwester
1
是的,但请记得您还需要分配 Code 成员。如果您弄清楚了,请将代码发布为答案。 - OneOfOne
1个回答

5

感谢OneOfOne的指针,这里有一个实现,可以很好地与标准XML unmarshaler一起使用:

package main

import (
    "encoding/xml"
    "fmt"
    "strconv"
    "strings"
)

type Property struct {
    Code  string `xml:"code"`
    Value interface{}
}

const xmldata = `<properties>
  <property>
<code value="a"/>
<valueBoolean value="true"/>
  </property>
  <property>
<code value="b"/>
<valueString value="apple"/>
  </property>
  <property>
<code value="c"/>
<valueDecimal value="3.14159"/>
  </property>
</properties>
`

func (p *Property) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
    if start.Name.Local != "property" {
        return fmt.Errorf("Invalid start tag for Property")
    }

    for {
        tok, err := d.Token()

        if tok == nil {
            break
        }

        if err != nil {
            return err
        }

        switch se := tok.(type) {
        case xml.StartElement:
            var value string
            var valueAssigned bool

            for _, attr := range se.Attr {
                if attr.Name.Local == "value" {
                    value = attr.Value
                    valueAssigned = true
                } else {
                    return fmt.Errorf("Invalid attribute %s", attr.Name.Local)
                }
            }

            if !valueAssigned {
                return fmt.Errorf("Valid attribute missing")
            }

            switch se.Name.Local {
            case "code":
                p.Code = value
            case "valueBoolean":
                if value == "true" {
                    p.Value = true
                } else if value == "false" {
                    p.Value = false
                } else {
                    return fmt.Errorf("Invalid string %s for Boolean value", value)
                }
            case "valueString", "valueCode", "valueUri":
                p.Value = value
            case "valueInteger":
                if ival, err := strconv.ParseInt(value, 10, 32); err != nil {
                    return err
                } else {
                    p.Value = ival
                }
            case "valueDecimal":
                if dval, err := strconv.ParseFloat(value, 64); err != nil {
                    return err
                } else {
                    p.Value = dval
                }
            default:
                return fmt.Errorf("Invalid tag %s for property", se.Name.Local)
            }
        }
    }

    return nil
}

func main() {
    r := strings.NewReader(xmldata)

    type Properties struct {
        List []Property `xml:"property"`
    }

    var properties Properties

    d := xml.NewDecoder(r)

    if err := d.Decode(&properties); err != nil {
        fmt.Println(err.Error())
    }

    for _, p := range properties.List {
        switch p.Value.(type) {
        case bool:
            if p.Value.(bool) {
                fmt.Println(p.Code, "is true")
            } else {
                fmt.Println(p.Code, "is false")
            }
        default:
            fmt.Println(p.Code, "=", p.Value)
        }
    }
}

输出结果为:

a is true
b = apple
c = 3.14159

谢谢分享!一个小问题,xml:"code>value,attr"不再需要,因此可以安全地删除。 - OneOfOne
我没有意识到这个问题!我会修复它。 - Scott Deerwester

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