在Go中将<tag value =“ val” />解组为Tag字符串

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:

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

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:"fieldA>value,attr"`
FieldB string `xml:"fieldB>value,attr"`
FieldC string `xml:"fieldC>value,attr,omitempty"`

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

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

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

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:"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
}

playground

You can't use the form of fieldA>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:"value,attr"`
}

Using this your MyType struct:

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

Testing it:

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

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

Output (try it on the Go Playground):

{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:

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

And the struct to model this:

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

This same struct can unmarshal from JSON:

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

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

Output: the same:

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

Try this variant (with JSON) on the Go Playground.

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

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

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

This should do the trick. Here is full example:

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
", myType)
}

The output for your sample XML would be this:

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

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