Tricky Go xml.Unmarshal() case Go的xml.Unmarshal()的棘手案例

huangapple go评论82阅读模式
英文:

Tricky Go xml.Unmarshal() case

问题

我正在尝试在Go中解组类似以下的XML:

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

或者这样的XML:

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

或者这样的XML:

<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 {
        // 无效的属性
    }
}
switch se.Name.Local {
case "code":
case "valueBoolean":
    property.Value = value == "true"
case "valueString":
    property.Value = value
case "valueInteger":
    property.Value, err = strconv.ParseInt(value, 10, 64)
case "valueDecimal":
    property.Value, err = strconv.ParseFloat(value, 64)
...
}

但是我无法弄清楚如何告诉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 float64  `xml:"valueDecimal>value,attr"`
}

但这样效率很低,特别是考虑到我将有大量这些实例,并且这样做没有办法在不添加另一个属性来指示类型的情况下推导出类型。

我是否可以将其与常规的XML解组方法结合起来,只需手动处理复杂部分,还是需要从头编写整个解组器来处理此类型?

英文:

I'm trying to unmarshal XML like this in Go:

&lt;property&gt;
  &lt;code value=&quot;abc&quot;/&gt;
  &lt;valueBoolean value=&quot;true&quot;/&gt;
&lt;/property&gt;

or this

&lt;property&gt;
  &lt;code value=&quot;abc&quot;/&gt;
  &lt;valueString value=&quot;apple&quot;/&gt;
&lt;/property&gt;

or this

&lt;property&gt;
  &lt;code value=&quot;abc&quot;/&gt;
  &lt;valueDecimal value=&quot;3.14159&quot;/&gt;
&lt;/property&gt;

etc., into this:

type Property struct {
    Code  string      `xml:&quot;code&gt;value,attr&quot;`
    Value interface{}
}

where the tag (valueBoolean, valueString, etc.) tells me what the type of the value attribute is. The XML that I'm trying to parse is part of an international standard, so I don't have any control over its definition. It wouldn't be hard to implement parsing these things, something like:

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

but I can't figure out how to tell the XML package to find it, and these things are buried in other XML that I'd really rather use xml.Unmarshal to handle. Alternately, I could redefine the type as:

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

but that's pretty inefficient, particularly given that I'll have a large number of instances of these things, and this leaves me no way to derive the type without adding another attribute to indicate the type.

Can I somehow tie this into the normal XML unmarshalling method, just handling the tricky part by hand, or do I need to write the whole unmarshaller for this type from scratch?

答案1

得分: 5

感谢OneOfOne的指导,这是一个与标准XML解组器很好配合的实现:

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
英文:

Thanks to the pointer from OneOfOne, here's an implementation that works well with the standard XML unmarshaler:

package main
import (
&quot;encoding/xml&quot;
&quot;fmt&quot;
&quot;strconv&quot;
&quot;strings&quot;
)
type Property struct {
Code  string `xml:&quot;code&quot;`
Value interface{}
}
const xmldata = `&lt;properties&gt;
&lt;property&gt;
&lt;code value=&quot;a&quot;/&gt;
&lt;valueBoolean value=&quot;true&quot;/&gt;
&lt;/property&gt;
&lt;property&gt;
&lt;code value=&quot;b&quot;/&gt;
&lt;valueString value=&quot;apple&quot;/&gt;
&lt;/property&gt;
&lt;property&gt;
&lt;code value=&quot;c&quot;/&gt;
&lt;valueDecimal value=&quot;3.14159&quot;/&gt;
&lt;/property&gt;
&lt;/properties&gt;
`
func (p *Property) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
if start.Name.Local != &quot;property&quot; {
return fmt.Errorf(&quot;Invalid start tag for Property&quot;)
}
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 == &quot;value&quot; {
value = attr.Value
valueAssigned = true
} else {
return fmt.Errorf(&quot;Invalid attribute %s&quot;, attr.Name.Local)
}
}
if !valueAssigned {
return fmt.Errorf(&quot;Valid attribute missing&quot;)
}
switch se.Name.Local {
case &quot;code&quot;:
p.Code = value
case &quot;valueBoolean&quot;:
if value == &quot;true&quot; {
p.Value = true
} else if value == &quot;false&quot; {
p.Value = false
} else {
return fmt.Errorf(&quot;Invalid string %s for Boolean value&quot;, value)
}
case &quot;valueString&quot;, &quot;valueCode&quot;, &quot;valueUri&quot;:
p.Value = value
case &quot;valueInteger&quot;:
if ival, err := strconv.ParseInt(value, 10, 32); err != nil {
return err
} else {
p.Value = ival
}
case &quot;valueDecimal&quot;:
if dval, err := strconv.ParseFloat(value, 64); err != nil {
return err
} else {
p.Value = dval
}
default:
return fmt.Errorf(&quot;Invalid tag %s for property&quot;, se.Name.Local)
}
}
}
return nil
}
func main() {
r := strings.NewReader(xmldata)
type Properties struct {
List []Property `xml:&quot;property&quot;`
}
var properties Properties
d := xml.NewDecoder(r)
if err := d.Decode(&amp;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, &quot;is true&quot;)
} else {
fmt.Println(p.Code, &quot;is false&quot;)
}
default:
fmt.Println(p.Code, &quot;=&quot;, p.Value)
}
}
}

Output is:

a is true
b = apple
c = 3.14159

huangapple
  • 本文由 发表于 2016年4月29日 21:38:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/36939874.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定