Golang:在处理嵌套的JSON Unmarshaler时遇到了问题。

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

Golang: Having trouble with nested JSON Unmarshaler

问题

给定以下代码:

package main

import (
	"encoding/json"
	"log"
)

type Somefin string

func (s *Somefin) UnmarshalJSON(b []byte) error {
	log.Println("Unmarshaling", string(b))
	*s = Somefin("~" + string(b) + "~")
	return nil
}

type Wat struct {
	A, B    string
	*Somefin
}

func main() {
	b := []byte(`{"A":"foo","B":"bar","Somefin":"baz"}`)
	w := &Wat{Somefin: new(Somefin)}

	err := json.Unmarshal(b, w)
	log.Println(w, err)
}

我得到了以下输出:

# go run wat.go
2013/12/14 13:59:17 Unmarshaling {"A":"foo","B":"bar","Somefin":"baz"}
2013/12/14 13:59:17 &{  <nil>} <nil>

所以 Somefin 键似乎尝试对整个结构进行解组,而不仅仅是它应该解组的键。我是做错了还是这是 json 编码器的一个 bug?顺便说一下,这是在 go 1.2 上的情况。

英文:

Given the following code:

package main

import (
	&quot;encoding/json&quot;
	&quot;log&quot;
)

type Somefin string

func (s *Somefin) UnmarshalJSON(b []byte) error {
	log.Println(&quot;Unmarshaling&quot;,string(b))
	*s = Somefin(&quot;~&quot;+string(b)+&quot;~&quot;)
	return nil
}

type Wat struct {
	A, B string
	*Somefin
}

func main() {
	b := []byte(`{&quot;A&quot;:&quot;foo&quot;,&quot;B&quot;:&quot;bar&quot;,&quot;Somefin&quot;:&quot;baz&quot;}`)
	w := &amp;Wat{Somefin: new(Somefin)}

	err := json.Unmarshal(b,w)
	log.Println(w, err)
}

I get the following output:

# go run wat.go
2013/12/14 13:59:17 Unmarshaling {&quot;A&quot;:&quot;foo&quot;,&quot;B&quot;:&quot;bar&quot;,&quot;Somefin&quot;:&quot;baz&quot;}
2013/12/14 13:59:17 &amp;{  &lt;nil&gt;} &lt;nil&gt;

So the Somefin key is for some reason trying to Unmarshal the entire structure instead of just the key it ought to. Am I doing this wrong or is this a bug in the json encoder? This is on go 1.2, btw.

答案1

得分: 4

为什么最后没有结果?

这不是解码器的错误,而是你的代码中的错误。你只是在UnmarshalJSON中给本地指针s分配了另一个地址。修正后的代码如下:

func (s *Somefin) UnmarshalJSON(b []byte) error {
    log.Println("Unmarshaling", string(b))
    sn := Somefin("~" + string(b) + "~")
    *s = sn
    return nil
}

s = &sn的语义是将地址&sn赋给s。这类似于s = 42

*s = sn的语义是将sn的内容复制到s指向的位置。

这要求s指向一个有效的内存位置,不能为nil。你的代码示例用法如下(play):

w := &Wat{Somefin: new(Somefin)}

err := json.Unmarshal(b, w)
log.Printf("%#v (%s)\n", w, err)

关键是使用new(Somefin)Wat进行初始化,以使UnmarshalJSON中的指针s有效(指向使用new(Somefin)创建的对象)。

为什么在UnmarshalJSON中获取整个字符串?

嵌入不是多态。虽然嵌入对象的方法集(在你的例子中是Somefin)被提升到外部,但这并不意味着该方法现在在嵌入结构上工作,而不是在嵌入的结构上工作。

小例子(play):

type Inner struct { a int }
func (i *Inner) A() int { return i.a }

type Outer struct {
    *Inner
    a int
}

i := &Inner{}
o := Outer{Inner: i}

fmt.Println("Inner.A():", i.A())
fmt.Println("Outer.A():", o.A())

o.a = 2

fmt.Println("Outer.A():", o.A())

使用多态,你期望Outer.A()返回2,因为方法A()将在Outer的范围内而不是Inner的范围内运行。使用嵌入,作用域永远不会改变,A()将始终在Inner上运行。

相同的效果使得你的UnmarshalJSON无法看到Somefin中的两个成员AB,因为它们根本不在Somefin的范围内:

  1. JSON库在Wat上看到UnmarshalJSON,因为SomefinUnmarshalJSON被提升到外部。
  2. JSON库在Somefin中找不到任何匹配的元素,并返回整个输入。
英文:

Why you are getting no result at the end

This is no bug in the decoder, it is a bug in your code. You're just assigning another address
to the local pointer s in UnmarshalJSON. Corrected code:

func (s *Somefin) UnmarshalJSON(b []byte) error {
    log.Println(&quot;Unmarshaling&quot;,string(b))
    sn := Somefin(&quot;~&quot;+string(b)+&quot;~&quot;)
    *s = sn
    return nil
}

Semantics of s = &amp;sn: Assign the address &amp;sn to s. This is similar to s = 42.

Semantics of *s = sn: Copy whatever is sn to the place where s points to.

One requirement for this to work is that s points to a valid memory location and must not be nil.
Example usage of your code (play):

w := &amp;Wat{Somefin: new(Somefin)}

err := json.Unmarshal(b,w)
log.Printf(&quot;%#v (%s)\n&quot;, w, err)

Crucial is the initialization of Wat with a new Somefin so that the pointer s in
UnmarshalJSON is valid (points to the object created with new(Somefin)).

Why you are getting the whole string in UnmarshalJSON

Embedding is not polymorphism. While the method set of the embedded object (in your case
Somefin) is promoted to the outside, this does not mean that the method is now working
on the embedding struct rather than the embedded one.

Small example (play):

type Inner struct { a int }
func (i *Inner) A() int { return i.a }

type Outer struct {
	*Inner
	a int
}

i := &amp;Inner{}
o := Outer{Inner: i}

fmt.Println(&quot;Inner.A():&quot;, i.A())
fmt.Println(&quot;Outer.A():&quot;, o.A())

o.a = 2

fmt.Println(&quot;Outer.A():&quot;, o.A())

With polymorphism you would expect Outer.A() to return 2 as method A() would operate in
the scope of Outer and not Inner anymore. With embedding the scope is never changed and
A() will always remain operating on Inner.

The same effect prevents your UnmarshalJSON from seeing the two members A and B as these
are simply not in the scope of Somefin:

  1. JSON library sees UnmarshalJSON on Wat because UnmarshalJSON from Somefin gets promoted to the outside
  2. JSON library cannot find any matching element in Somefin and delivers the whole input

答案2

得分: 2

我弄清楚了。

如果你的结构定义如下:

type Wat struct {
    A, B string
    Somefin
}

那么我在原始问题中描述的错误就会发生。但是如果你这样做:

type Wat struct {
    A, B string
    Somefin Somefin
}

那么就不会发生这个错误。请查看Chris对这个答案的评论,他对此进行了很好的解释。

英文:

I figured this out.

If you have the struct definition like this:

type Wat struct {
    A, B string
    Somefin
}

Then the error I described in the OP happens. But if you do:

type Wat struct {
    A, B string
    Somefin Somefin
}

Then it doesn't. Check out Chris's comment to this answer for a good explanation of why.

huangapple
  • 本文由 发表于 2013年12月15日 03:14:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/20587157.html
匿名

发表评论

匿名网友

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

确定