将子元素的属性直接解析为Go结构体

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

Parsing sub-element's attribute directly into Go struct

问题

在使用Go解析XML时,我该如何将嵌套元素的属性直接读入到我的结构体中?

我的XML如下所示,它是OpenStreetMap格式的一部分:

<way id="123">
    <nd ref="101"/>
    <!-- Lots of nd elements repeated -->
    <nd ref="109"/>
</way>

我有以下结构体:

type Way struct {
    Nodes []NodeRef `xml:"nd"`
}

以及:

type NodeRef struct {
    Ref int `xml:"ref,attr"`
}

但我希望能够直接使用以下结构体:

type Way struct {
    Nodes []int `???`
}

关于解组的文档对我没有帮助。我尝试使用xml:"nd>ref,attr",但会出现"chain not valid with attr flag"的错误。

请参考下面的示例代码。在Go Playground中运行代码

package main

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

func main() {
    data := `
        <way id="5090250">
        <nd ref="822403"/>
        <nd ref="21533912"/>
        <nd ref="821601"/>
        <nd ref="21533910"/>
        <nd ref="135791608"/>
        <nd ref="333725784"/>
        <nd ref="333725781"/>
        <nd ref="333725774"/>
        <nd ref="333725776"/>
        <nd ref="823771"/>
      </way>
    `

    r := strings.NewReader(data)
    way, err := ReadWay(r)
    if err != nil {
        fmt.Println("Could not read", err)
        os.Exit(1)
    }

    fmt.Println(way)
}

// 我想摆脱这个嵌套的结构体。
type NodeRef struct {
    Ref int `xml:"ref,attr"`
}

type Way struct {
    ID int `xml:"id,attr"`
    // 我该如何将所有的<nd ref="123"/>直接写入到Nodes []int中?
    Nodes []NodeRef `xml:"nd"`
}

func ReadWay(reader io.Reader) (Way, error) {
    var way Way
    if err := xml.NewDecoder(reader).Decode(&way); err != nil {
        return way, err // 为什么我不能返回nil, err?
    }
    return way, nil
}
英文:

In parsing XML with Go, how can I read a nested element's attribute directly into my struct?

My XML looks like this below. It is part of the OpenStreetMap format:

&lt;way id=&quot;123&quot; &gt;
	    &lt;nd ref=&quot;101&quot;/&gt;
        &lt;!-- Lots of nd elements repeated --&gt;
	    &lt;nd ref=&quot;109&quot;/&gt;
&lt;/way&gt;

I have

type Way struct {
    Nodes []NodeRef `xml:&quot;nd&quot;`
}

with

type NodeRef struct {
    Ref int `xml:&quot;ref,attr&quot;`
}

but I would like to be able to do

type Way struct {
    Nodes []int `???`
}

directly.

The documentation on Unmarshalling didn't help me. I've tried using xml:&quot;nd&gt;ref,attr&quot; but that fails with "chain not valid with attr flag".

Please see the below example code. Run the code in Go Playground

package main

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

func main() {
	data := `
	    &lt;way id=&quot;5090250&quot; &gt;
	    &lt;nd ref=&quot;822403&quot;/&gt;
	    &lt;nd ref=&quot;21533912&quot;/&gt;
	    &lt;nd ref=&quot;821601&quot;/&gt;
	    &lt;nd ref=&quot;21533910&quot;/&gt;
	    &lt;nd ref=&quot;135791608&quot;/&gt;
	    &lt;nd ref=&quot;333725784&quot;/&gt;
	    &lt;nd ref=&quot;333725781&quot;/&gt;
	    &lt;nd ref=&quot;333725774&quot;/&gt;
	    &lt;nd ref=&quot;333725776&quot;/&gt;
	    &lt;nd ref=&quot;823771&quot;/&gt;
	  &lt;/way&gt;
	`

	r := strings.NewReader(data)
	way, err := ReadWay(r)
	if err != nil {
		fmt.Println(&quot;Could not read&quot;, err)
		os.Exit(1)
	}

	fmt.Println(way)
}

// I&#39;d like to get rid of this nested struct.
type NodeRef struct {
	Ref int `xml:&quot;ref,attr&quot;`
}

type Way struct {
	ID int `xml:&quot;id,attr&quot;`
	// How can I write all &lt;nd ref=&quot;123&quot;/&gt; directly into Nodes []int?
	Nodes []NodeRef `xml:&quot;nd&quot;`
}

func ReadWay(reader io.Reader) (Way, error) {
	var way Way
	if err := xml.NewDecoder(reader).Decode(&amp;way); err != nil {
		return way, err // Why can&#39;t I return nil, err?
	}
	return way, nil
}

答案1

得分: 3

简而言之,你不能直接这样做。绕过XML结构的常见方法是实现xml.Unmarshaler接口。这里有一个示例:

type Way struct {
    ID    int 
    Nodes []int
}

func (w *Way) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
    var payload struct {
        ID    int `xml:"id,attr"`
        Nodes []struct {
            Ref int `xml:"ref,attr"`
        } `xml:"nd"`
    }   

    err := d.DecodeElement(&payload, &start)
    if err != nil {
        return err 
    }   

    w.ID = payload.ID
    w.Nodes = make([]int, 0, len(payload.Nodes))

    for _, node := range payload.Nodes {
        w.Nodes = append(w.Nodes, node.Ref)
    }   

    return nil 
}

在这里,payload变量的结构与你的XML数据相对应,而Way结构体则按照你的要求进行建模。一旦解码完成,你可以根据需要使用它来初始化Way变量。

附注:为什么不能返回nil, err

你不能返回nil,因为Way既不是指针类型也不是接口类型,因此nil不是它的有效值。

英文:

In short, you can't do that directly. The common pattern to bypass the XML structure is to implement the xml.Unmarshaler interface. Here is an example:

type Way struct {
    ID    int 
    Nodes []int
}

func (w *Way) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
    var payload struct {
        ID    int `xml:&quot;id,attr&quot;`
        Nodes []struct {
            Ref int `xml:&quot;ref,attr&quot;`
        } `xml:&quot;nd&quot;`
    }   

    err := d.DecodeElement(&amp;payload, &amp;start)
    if err != nil {
        return err 
    }   

    w.ID = payload.ID
    w.Nodes = make([]int, 0, len(payload.Nodes))

    for _, node := range payload.Nodes {
        w.Nodes = append(w.Nodes, node.Ref)
    }   

    return nil 
}

Here, the payload variable is modeled after your XML data, while the Way struct is modeled as you want it to be. Once the payload is decoded, you can use it to initialize the Way variable as you want…

Side note: // Why can&#39;t I return nil, err?

You can't return nil because Way isn't either a pointer or an interface, thus nil isn't a valid value for it.

huangapple
  • 本文由 发表于 2016年3月29日 16:43:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/36279414.html
匿名

发表评论

匿名网友

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

确定