Error when trying to xml.Unmarshal to struct with field of type map[string]interface{}

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

Error when trying to xml.Unmarshal to struct with field of type map[string]interface{}

问题

问题在于,对于一个具有类型为map[string]interface{}的字段的结构体进行xml.Unmarshal操作将会失败,并显示以下错误信息:

未知类型 map[string]interface {}
{XMLName:{Space: Local:myStruct} Name:test Meta:map[]}

由于类型为map[string]interface{}的Meta字段是动态定义的,因此需要对其进行动态的解析。

package main

import (
	"encoding/xml"
	"fmt"
)

func main() {
	var myStruct MyStruct

	// meta是我们所知道的,在meta内部,将会发生动态属性和嵌套
	s := `<myStruct>
		<name>test</name>
		<meta>
			<someProp>something</someProp>
			<someOtherDynamic>
				<name>test</name>
				<somethingElse>test2</somethingElse>
				<nested3>
				    <name>nested3</name>
					<nested3elements>
						<elem>ele1</elem>
						<elem>ele2</elem>
					</nested3elements>
				</nested3>
			</someOtherDynamic>
		</meta>
	</myStruct>`

	err := xml.Unmarshal([]byte(s), &myStruct)
	if err == nil {
		fmt.Printf("%+v\n", myStruct)
	} else {
		fmt.Println(err)
		fmt.Printf("%+v\n", myStruct)
	}
}

type MyStruct struct {
	XMLName xml.Name               `xml:"myStruct"`
	Name    string                 `xml:"name"`
	Meta    map[string]interface{} `xml:"meta,omitempty"`
}

我在这里提供了一个示例:http://play.golang.org/p/lTDJzXXPwT

我该如何实现这个功能?


到目前为止,我找到的解决方法:

http://play.golang.org/p/gQUlvkYl7k

基本上发生了以下情况:

  1. Meta字段的xml注释被移除,因此在xml.Unmarshal中被忽略。
  2. 创建了一个新类型MapContainer,其中包含一个字段:InnerXML []byte xml:",innerxml"
  3. 添加了类型为MapContainer的MetaByte字段,使用xml:"meta,omitempty"注释

因此,在第一次xml.Unmarshal中,我们保存了meta元素的XML的字节切片。然后在我们自定义的xml unmarshal函数中,我们使用mxj包的NewMapXml函数对该字节切片进行处理,并将结构体的Meta字段设置为这个新创建的map。

这得益于这个仓库的天才,https://github.com/clbanning/mxj ,它可以将XML解组为map。

更新的当前最佳解决方案:

http://play.golang.org/p/_tw06klods

感谢Adam Vincze的贡献。

英文:

The problem is that xml.Unmarshal of a struct with a field of type map[string]interface{} will fail with the error:

unknown type map[string]interface {}
{XMLName:{Space: Local:myStruct} Name:test Meta:map[]}

Since the Meta field of type map[string]interface{} is as far as I can define, what's inside has to be dynamically unmarshalled.

package main

import (
	&quot;encoding/xml&quot;
	&quot;fmt&quot;
)

func main() {
	var myStruct MyStruct

	// meta is as far as we know, inside meta, dynamic properties and nesting will happen
	s := `&lt;myStruct&gt;
		&lt;name&gt;test&lt;/name&gt;
		&lt;meta&gt;
			&lt;someProp&gt;something&lt;/someProp&gt;
			&lt;someOtherDynamic&gt;
				&lt;name&gt;test&lt;/name&gt;
				&lt;somethingElse&gt;test2&lt;/somethingElse&gt;
				&lt;nested3&gt;
				    &lt;name&gt;nested3&lt;/name&gt;
					&lt;nested3elements&gt;
						&lt;elem&gt;ele1&lt;/elem&gt;
						&lt;elem&gt;ele2&lt;/elem&gt;
					&lt;/nested3elements&gt;
				&lt;/nested3&gt;
			&lt;/someOtherDynamic&gt;
		&lt;/meta&gt;
	&lt;/myStruct&gt;`

	err := xml.Unmarshal([]byte(s), &amp;myStruct)
	if err == nil {
		fmt.Printf(&quot;%+v\n&quot;, myStruct)
	} else {
		fmt.Println(err)
		fmt.Printf(&quot;%+v\n&quot;, myStruct)
	}
}

type MyStruct struct {
	XMLName xml.Name               `xml:&quot;myStruct&quot;`
	Name    string                 `xml:&quot;name&quot;`
	Meta    map[string]interface{} `xml:&quot;meta,omitempty&quot;`
}

I've made an example here: http://play.golang.org/p/lTDJzXXPwT

How can I achieve this?


My workaround "solution" so far:

http://play.golang.org/p/gQUlvkYl7k

Basically what happens is that:

  1. The Meta field got the xml annotations removed, thus being ignore from the xml.Unmashal
  2. New type, MapContainer, is created with a field: InnerXML []byte xml:&quot;,innerxml&quot;
  3. MetaByte field of type MapContainer is added using the xml:&quot;meta,omitempty&quot; annotations

So in the first xml.Unmarshal we save a byte slice of the meta element's XML. Then in our custom xml unmarshal function we take that byteSlice and use the magic function NewMapXml from the mxj package, and set the Meta field of the struct to this newly created map.

This is possible thanks to the genius made this repo, https://github.com/clbanning/mxj , which can unmarshal from XML to maps.

Updated current best solution:

http://play.golang.org/p/_tw06klods

Thanks to Adam Vincze

答案1

得分: 3

根据你最后需要对元数据进行的操作,你可能想要查看这个库:https://github.com/clbanning/mxj

如果你实现一个自定义的UnmarshalXML函数,你可以更好地完成这个任务。

func (m *MyStruct) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
    var v struct {
        XMLName xml.Name `xml:"myStruct"`
        Name    string   `xml:"name"`
        Meta    struct {
            Inner []byte `xml:",innerxml"`
        } `xml:"meta"`
    }

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

    m.Name = v.Name
    myMap := make(map[string]interface{})
    // ... 在这里进行 mxj 的操作 ...
    // 填充 myMap
    m.Meta = myMap

    return nil
}

type MyStruct struct {
    Name string
    Meta map[string]interface{}
}

这样可以使你的 MyStruct 结构保持整洁。

英文:

depending what you need to do with meta at the end you might want to take a look at this lib: https://github.com/clbanning/mxj

you could do it just a little nicer if you implement a custom UnmarshalXML function

func (m *MyStruct) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
	var v struct {
		XMLName xml.Name `xml:&quot;myStruct&quot;`
		Name    string   `xml:&quot;name&quot;`
		Meta    struct {
			Inner []byte `xml:&quot;,innerxml&quot;`
		} `xml:&quot;meta&quot;`
	}


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

	m.Name = v.Name
	myMap := make(map[string]interface{})
	// ... do the mxj magic here ... -
	// fill myMap
	m.Meta = myMap
		

	return nil
}

type MyStruct struct {
    Name    string
    Meta    map[string]interface{}
}

`

that leaves your MyStruct nice and clean

huangapple
  • 本文由 发表于 2015年7月1日 17:00:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/31156938.html
匿名

发表评论

匿名网友

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

确定