在Go语言中,将JSON反序列化为接口变量时,需要匹配底层类型。

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

Unmarshalling JSON to interface variable while matching the underlying type in Go

问题

我正在编写一个包装器,用于映射一些我需要的附加功能。其中一些最重要的功能是能够在保留泛型性的同时进行数据的编组和解组。我使用编码/解码器编写了一个使用编码/gob编码器的编组器,但由于编组的数据最好是可读的,所以我决定使用JSON编写另一个实现。

我通过向实现对象实例传递Register()来使gob能够将数据编码为和解码为泛型接口变量。(这个资源帮助了我解决了一些细节问题!http://www.funcmain.com/gob_encoding_an_interface)

然而,JSON没有Register()方法。假设我们有一个类型为

type ConcreteImplementation struct {
    FieldA string
    FieldB string
}

func (c ConcreteImplementation) Key() string {
    return c.FieldA   // ConcreteImplementation实现了genericValue接口
}

的值,存储在类型为

type genericValue interface {
    Key() string
}

的变量中。

当我对其进行编组时,输出的JSON如下:

{"FieldA":"foo","FieldB":"bar"}

当我尝试将其解组为类型为genericValue的变量时,它报错:

<s>panic: interface conversion: map[string]interface {} is not genericValue: missing method Key</s> EDIT: 哦,实际上它是这样说的!

解码错误!json: 无法将对象解组为genericValue的Go值

很明显,它尝试按照这里所说的方式进行编组:http://blog.golang.org/json-and-go(参见“使用interface{}进行通用JSON”)

我该如何让它尝试将数据适配到特定的实现上,就像gob解码器在实现被Register()注册时所做的那样?Register()真是太好用了,它使得泛型的编组和解组变得轻而易举。我该如何让JSON做同样的事情?

英文:

I'm writing a wrapper to map with some additional functionality I need. Some of the most important things is the ability to marshal and unmarshal the data while retaining genericity. I managed to write an marshaller using the encoding/gob encoder, but since it would be nice if the marshalled data was human readable, I decided to code another implementation with JSON.

I got gob to encode from and decode to generic interface variables neatly by passing it a implementation object instance with Register(). (This resource helped me with the details! http://www.funcmain.com/gob_encoding_an_interface)

However, JSON doesn't have Register(). Let's say we have a value of type

type ConcreteImplementation struct {
    FieldA string
    FieldB string
}

func (c ConcreteImplementation) Key() string {
    return c.FieldA   // ConcreteImplementation implements genericValue
}

in a variable of type

type genericValue interface {
    Key() string
}

When I marshal that, it outputs JSON like this:

{&quot;FieldA&quot;:&quot;foo&quot;,&quot;FieldB&quot;:&quot;bar&quot;}

And when I try to unmarshal that again into a variable of type genericValue, it says:

<s>panic: interface conversion: map[string]interface {} is not genericValue: missing method Key</s> EDIT: Oops, actually it says this!

Error with decoding! json: cannot unmarshal object into Go value of genericValue

<s>Quite obviously, it tries to marshal the data like it says here: http://blog.golang.org/json-and-go (See 'Generic JSON with interface{}')</s>

How can I get it to try to fit the data to an specific implementation, like gob decoder would try if the implementation is Register()ed? Register() was godsend, it allowed to marshal and unmarshal generically like it was nothing. How do I get JSON to do the same thing?

答案1

得分: 2

如果你的类型实现了Unmarshaler接口,会怎样呢?

这里有一个小的演示。

或者可以在这里看到相同的代码:

type ConcreteImplementation struct {
    FieldA string
    FieldB string
}

func (c ConcreteImplementation) Key() string {
    return c.FieldA // ConcreteImplementation实现了genericValue接口
}

// 实现json.Unmarshaler接口
func (c *ConcreteImplementation) UnmarshalJSON(j []byte) error {
    m := make(map[string]string)
    err := json.Unmarshal(j, &m)
    if err != nil {
        return err
    }
    if v, ok := m["FieldA"]; ok {
        c.FieldA = v
    }
    if v, ok := m["FieldB"]; ok {
        c.FieldB = v
    }
    return nil
}

type genericValue interface {
    Key() string
    json.Unmarshaler
}

func decode(jsonStr []byte, v genericValue) error {
    return json.Unmarshal(jsonStr, v)
}

通过这样做,你可以将一个genericValue传递给json.Unmarshal函数。

英文:

What if your types implemented the [Unmarshaler][1]?

[Here is a small demo.][2]

Or the same code here:

type ConcreteImplementation struct {
 	FieldA string
    FieldB string
}

func (c ConcreteImplementation) Key() string {
    return c.FieldA // ConcreteImplementation implements genericValue
}

// implementing json.Unmarshaler
func (c *ConcreteImplementation) UnmarshalJSON(j []byte) error {
	m := make(map[string]string)
	err := json.Unmarshal(j, &amp;m)
	if err != nil {
		return err
 	}
    if v, ok := m[&quot;FieldA&quot;]; ok {
    	c.FieldA = v
	}
	if v, ok := m[&quot;FieldB&quot;]; ok {
		c.FieldB = v
	}
	return nil
}

type genericValue interface {
 	Key() string
    json.Unmarshaler
}

func decode(jsonStr []byte, v genericValue) error {
	return json.Unmarshal(jsonStr, v)
}

With this you can pass a genericValue to json.Unmarshal.
[1]: http://golang.org/pkg/encoding/json/#Unmarshaler
[2]: http://play.golang.org/p/wxu4KD8I_j

答案2

得分: 0

好的,以下是翻译好的内容:

好的,终于解决了。这个问题提供了答案。https://stackoverflow.com/questions/21468741/why-does-json-unmarshal-return-a-map-instead-of-the-expected-struct

“你向json传递了一个指向抽象接口的指针。你应该简单地将指向Ping的指针作为抽象接口传递。” - 这也适用于我的情况。(由于某种原因,指向抽象接口的指针对于gob包来说已经足够了。看来我需要进一步学习Go接口和反射,以了解为什么……)

如果有人有更好的答案,我还不会将这个问题标记为已解决。

英文:

Allright, got it to work finally. This question provided the answer. https://stackoverflow.com/questions/21468741/why-does-json-unmarshal-return-a-map-instead-of-the-expected-struct

"You've passed to json a pointer to an abstract interface. You should simply pass a pointer to Ping as an abstract interface" - This applied to my situation too. (For some reason a pointer TO an abstract interface was enough for gob package. It seems that I have to study Go interfaces and reflection some more to understand why...)

I won't still mark this as solved question, if someone has a better answer.

huangapple
  • 本文由 发表于 2014年3月24日 03:54:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/22596260.html
匿名

发表评论

匿名网友

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

确定