将<tag value="val"/>在Go中反序列化为Tag字符串

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

Unmarshaling <tag value="val"/> to Tag string in Go

问题

假设我有一个定义如下的 Go 结构体:

type MyType struct {
    FieldA string
    FieldB string
    FieldC string
}

以及对应的 XML 数据,如下所示:

<obj>
    <fieldA value="apple"/>
    <fieldB value="banana"/>
</obj>

其中 FieldA 和 FieldB 是必需的,而 FieldC 是可选的。如何指定结构体标签以从 "value" 属性中获取字段的值?下面的方式会生成错误信息 "xml: fieldA>value chain not valid with attr flag":

FieldA string `xml:"fieldA>value,attr"`
FieldB string `xml:"fieldB>value,attr"`
FieldC string `xml:"fieldC>value,attr,omitempty"`

而下面的方式不会生成错误,但也无法找到字段的值:

FieldA string `xml:"fieldA"`
FieldB string `xml:"fieldB"`
FieldC string `xml:"fieldC,omitempty"`

请问有什么我可以帮助你的吗?

英文:

Say that I've got a Go struct defined as follows:

type MyType struct {
    FieldA string
    FieldB string
    FIeldC string
}

and XML that corresponds to it that looks like this:

&lt;obj&gt;
    &lt;fieldA value=&quot;apple&quot;/&gt;
    &lt;fieldB value=&quot;banana&quot;/&gt;
&lt;/obj&gt;

where FieldA and FieldB are mandatory, and FieldC is optional. How do I specify the struct tags so as to get the field's value from the "value" attribute? This:

FieldA string `xml:&quot;fieldA&gt;value,attr&quot;`
FieldB string `xml:&quot;fieldB&gt;value,attr&quot;`
FieldC string `xml:&quot;fieldC&gt;value,attr,omitempty&quot;`

generates "xml: fieldA>value chain not valid with attr flag" and this:

FieldA string `xml:&quot;fieldA&quot;`
FieldB string `xml:&quot;fieldB&quot;`
FieldC string `xml:&quot;fieldC,omitempty&quot;`

doesn't generate an error, but doesn't find the values of the fields.

答案1

得分: 1

要同时支持XML和JSON,您需要定义一个简单类型,并在其上实现xml.Unmarshalerxml.Marshaler接口。以下是一个示例:

type Field string

func (f Field) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
    v := struct {
        Value string `xml:"value,attr"`
    }{string(f)}
    return e.EncodeElement(v, start)
}

func (f *Field) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
    var v struct {
        Value string `xml:"value,attr"`
    }
    err := d.DecodeElement(&v, &start)
    *f = Field(v.Value)
    return err
}

您可以在这里找到一个示例。

英文:

To support both XML and JSON, you will have to define a simple type and implement the xml.Unmarshaler and xml.Marshaler interfaces on it, here's an example:

type Field string

func (f Field) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
	v := struct {
		Value string `xml:&quot;value,attr&quot;`
	}{string(f)}
	return e.EncodeElement(v, start)
}

func (f *Field) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
	var v struct {
		Value string `xml:&quot;value,attr&quot;`
	}
	err := d.DecodeElement(&amp;v, &amp;start)
	*f = Field(v.Value)
	return err
}

<kbd>playground</kbd>

答案2

得分: 0

你不能使用fieldA>value的形式,因为这个"path"的元素表示元素,而在你的情况下value不是一个元素。

如果你想从子元素的属性中获取一个值,你可以为其创建一个包装类型。

例如:

type Field struct {
    Value string `xml:"value,attr"`
}

type MyType struct {
    FieldA Field `xml:"fieldA"`
    FieldB Field `xml:"fieldB"`
    FieldC Field `xml:"fieldC"`
}

func main() {
    mt := MyType{}
    if err := xml.Unmarshal([]byte(src), &mt); err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", mt)
}

const src = `<obj>
    <fieldA value="apple"/>
    <fieldB value="banana"/>
</obj>`

输出结果(在Go Playground上尝试):

{FieldA:{Value:apple} FieldB:{Value:banana} FieldC:{Value:}}

编辑:

如果你想要使用同一个结构体处理XML和JSON,你应该在XML中使用元素的内容来保存数据(而不是value属性),例如:

<obj>
    <fieldA>apple</fieldA>
    <fieldB>banana</fieldB>
</obj>

并且使用以下结构体来建模:

type MyType struct {
    FieldA string `xml:"fieldA"`
    FieldB string `xml:"fieldB"`
    FieldC string `xml:"fieldC"`
}

这个相同的结构体可以从JSON中解组:

const src2 = `{"fieldA": "apple", "fieldB": "banana"}`

mt = MyType{}
if err := json.Unmarshal([]byte(src2), &mt); err != nil {
    panic(err)
}
fmt.Printf("%+v\n", mt)

输出结果仍然是一样的:

{FieldA:apple FieldB:banana FieldC:}

你可以在Go Playground上尝试使用JSON的变体。

英文:

You can't use the form of fieldA&gt;value because elements of this "path" denote elements and value is not an element in your case.

If you want to get a value out from an attribute of a child element, you can create a wrapper type for it.

For example:

type Field struct {
	Value string `xml:&quot;value,attr&quot;`
}

Using this your MyType struct:

type MyType struct {
	FieldA Field `xml:&quot;fieldA&quot;`
	FieldB Field `xml:&quot;fieldB&quot;`
	FieldC Field `xml:&quot;fieldC&quot;`
}

Testing it:

func main() {
	mt := MyType{}
	if err := xml.Unmarshal([]byte(src), &amp;mt); err != nil {
		panic(err)
	}
	fmt.Printf(&quot;%+v\n&quot;, mt)
}

const src = `&lt;obj&gt;
    &lt;fieldA value=&quot;apple&quot;/&gt;
    &lt;fieldB value=&quot;banana&quot;/&gt;
&lt;/obj&gt;`

Output (try it on the <kbd>Go Playground</kbd>):

{FieldA:{Value:apple} FieldB:{Value:banana} FieldC:{Value:}}

Edit:

If you want to handle both XML and JSON with one struct, in the XML you should use the content of the element to hold the data (and not the value attribute), for example:

&lt;obj&gt;
    &lt;fieldA&gt;apple&lt;/fieldA&gt;
    &lt;fieldB&gt;banana&lt;/fieldB&gt;
&lt;/obj&gt;

And the struct to model this:

type MyType struct {
	FieldA string `xml:&quot;fieldA&quot;`
	FieldB string `xml:&quot;fieldB&quot;`
	FieldC string `xml:&quot;fieldC&quot;`
}

This same struct can unmarshal from JSON:

const src2 = `{&quot;fieldA&quot;: &quot;apple&quot;, &quot;fieldB&quot;: &quot;banana&quot;}`

mt = MyType{}
if err := json.Unmarshal([]byte(src2), &amp;mt); err != nil {
	panic(err)
}
fmt.Printf(&quot;%+v\n&quot;, mt)

Output: the same:

{FieldA:apple FieldB:banana FieldC:}
{FieldA:apple FieldB:banana FieldC:}

Try this variant (with JSON) on the <kbd>Go Playground</kbd>.

答案3

得分: 0

你可以通过引入一个Field结构体来实现,该结构体具有一个Value成员:

type MyType struct {
    FieldA Field `xml:"fieldA"`
    FieldB Field `xml:"fieldB"`
    FieldC Field `xml:"fieldC"`
}

type Field struct {
    Value string `xml:"value,attr"`
}

这样就可以实现了。以下是完整的示例代码:

package main

import (
    "encoding/xml"
    "fmt"
    "io"
    "os"
    "strings"
)

type MyType struct {
    FieldA Field `xml:"fieldA"`
    FieldB Field `xml:"fieldB"`
    FieldC Field `xml:"fieldC"`
}

type Field struct {
    Value string `xml:"value,attr"`
}

func deserializeMyType(reader io.Reader) (MyType, error) {
    myType := MyType{}
    decoder := xml.NewDecoder(reader)
    err := decoder.Decode(&myType)
    if err != nil {
        return MyType{}, err
    }

    return myType, nil
}

func main() {
    inputXML := `<obj>
    <fieldA value="apple"/>
    <fieldB value="banana"/>
</obj>`

    xmlReader := strings.NewReader(inputXML)
    myType, err := deserializeMyType(xmlReader)
    if err != nil {
        fmt.Fprintf(os.Stderr, "%s", err.Error())
        os.Exit(1)
    }

    fmt.Fprintf(os.Stdout, "%#v\n", myType)
}

对于你的示例XML,输出将是:

main.MyType{FieldA:main.Field{Value:"apple"}, FieldB:main.Field{Value:"banana"}, FieldC:main.Field{Value:""}}

你可以在Go源代码的golang.org/src/encoding/xml/example_test.go中找到其他反序列化XML属性的示例。

英文:

You could do it by introducing a Field struct that has a Value member:

type MyType struct {
	FieldA Field `xml:&quot;fieldA&quot;`
	FieldB Field `xml:&quot;fieldB&quot;`
	FIeldC Field `xml:&quot;fieldC&quot;`
}

type Field struct {
	Value string `xml:&quot;value,attr&quot;`
}

This should do the trick. Here is full example:

package main

import (
	&quot;encoding/xml&quot;
	&quot;fmt&quot;
	&quot;io&quot;
	&quot;os&quot;
	&quot;strings&quot;
)

type MyType struct {
	FieldA Field `xml:&quot;fieldA&quot;`
	FieldB Field `xml:&quot;fieldB&quot;`
	FieldC Field `xml:&quot;fieldC&quot;`
}

type Field struct {
	Value string `xml:&quot;value,attr&quot;`
}

func deserializeMyType(reader io.Reader) (MyType, error) {
	myType := MyType{}
	decoder := xml.NewDecoder(reader)
	err := decoder.Decode(&amp;myType)
	if err != nil {
		return MyType{}, err
	}

	return myType, nil
}

func main() {
	inputXML := `&lt;obj&gt;
	&lt;fieldA value=&quot;apple&quot;/&gt;
	&lt;fieldB value=&quot;banana&quot;/&gt;
&lt;/obj&gt;`

	xmlReader := strings.NewReader(inputXML)
	myType, err := deserializeMyType(xmlReader)
	if err != nil {
		fmt.Fprintf(os.Stderr, &quot;%s&quot;, err.Error())
		os.Exit(1)
	}

	fmt.Fprintf(os.Stdout, &quot;%#v\n&quot;, myType)
}

The output for your sample XML would be this:

main.MyType{FieldA:main.Field{Value:&quot;apple&quot;}, FieldB:main.Field{Value:&quot;banana&quot;}, FieldC:main.Field{Value:&quot;&quot;}}

You can find other examples for derserializing XML attributes in the go sources at golang.org/src/encoding/xml/example_test.go.

huangapple
  • 本文由 发表于 2016年4月30日 22:56:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/36956397.html
匿名

发表评论

匿名网友

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

确定