在Go语言中,为什么有时候在解码时JSON的null值不会传递给UnmarshalJSON函数?

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

In Go, why is JSON null sometimes not passed to UnmarshalJSON for decoding?

问题

Go提供了encoding/json.Unmarshaler接口,以便类型可以控制它们从JSON解码的方式。在几乎所有情况下,编码的JSON值直接传递给UnmarshalJSON方法,但如果Unmarshaler是一个指针并且JSON值为null,则不会调用UnmarshalJSON,而是将指针设置为nil。这里有一个例子:

package main

import (
	"encoding/json"
	"fmt"
)

type T string

func (v *T) UnmarshalJSON(b []byte) error {
	if b[0] == 'n' {
		*v = "null"
	} else {
		*v = "not null"
	}
	return nil
}

func main() {
	var a struct {
		T   T
		PT1 *T
		PT2 *T
	}
	a.PT1 = nil // just to be explicit
	a.PT2 = new(T)
	err := json.Unmarshal([]byte(`{"T":null,"PT1":"foo","PT2":null}`), &a)
	if err != nil {
		panic(err)
	}
	fmt.Printf("a.T is %#v\n", a.T)
	if a.PT1 == nil {
		fmt.Println("a.PT1 is nil")
	} else {
		fmt.Printf("a.PT1 points to %#v\n", *a.PT1)
	}
	if a.PT2 == nil {
		fmt.Println("a.PT2 is nil")
	} else {
		fmt.Printf("a.PT2 points to %#v\n", *a.PT2)
	}
}

我期望这段代码输出:

a.T is "null"
a.PT1 points to "not null"
a.PT2 points to "null"

然而,实际输出是:

a.T is "null"
a.PT1 points to "not null"
a.PT2 is nil

所以,json.Unmarshala.PT1分配了一个新的T,它最初是nil。但是,它将a.PT2设置为nil,而不调用UnmarshalJSON,即使a.PT2不是nil。为什么会这样呢?

英文:

Go provides the encoding/json.Unmarshaler interface so types can control the way they are decoded from JSON. In almost all cases, the encoded JSON value is passed directly to the UnmarshalJSON method, but not if the Unmarshaler is a pointer and the JSON value is null. In that case, the pointer is set to nil without calling UnmarshalJSON at all. Here's an example:

package main

import (
	"encoding/json"
	"fmt"
)

type T string

func (v *T) UnmarshalJSON(b []byte) error {
	if b[0] == 'n' {
		*v = "null"
	} else {
		*v = "not null"
	}
	return nil
}

func main() {
	var a struct {
		T   T
		PT1 *T
		PT2 *T
	}
	a.PT1 = nil // just to be explicit
	a.PT2 = new(T)
	err := json.Unmarshal([]byte(`{"T":null,"PT1":"foo","PT2":null}`), &a)
	if err != nil {
		panic(err)
	}
	fmt.Printf("a.T is %#v\n", a.T)
	if a.PT1 == nil {
		fmt.Println("a.PT1 is nil")
	} else {
		fmt.Printf("a.PT1 points to %#v\n", *a.PT1)
	}
	if a.PT2 == nil {
		fmt.Println("a.PT2 is nil")
	} else {
		fmt.Printf("a.PT2 points to %#v\n", *a.PT2)
	}
}

I expected this to print

<!-- language: lang-none -->

a.T is &quot;null&quot;
a.PT1 points to &quot;not null&quot;
a.PT2 points to &quot;null&quot;

Instead, it prints

<!-- language: lang-none -->

a.T is &quot;null&quot;
a.PT1 points to &quot;not null&quot;
a.PT2 is nil

So json.Unmarshal allocates a new T for a.PT1, which is initially nil. But it sets a.PT2 to nil without calling UnmarshalJSON, even though a.PT2 was not nil. Why?

答案1

得分: 1

这是因为将指针设置为nil是处理JSON中的null最常见的方式,而*T类型的UnmarshalJSON方法本身无法实现这一点。如果在这种情况下调用UnmarshalJSON,你将不得不定义(**T).UnmarshalJSON来将*T设置为nil。这将使最常见的情况变得非常笨拙。

如果你不希望JSON中的null变成Go中的nil,就不要使用指针。

英文:

This is because setting the pointer to nil is the most common way to handle a JSON null, and there is no way for an UnmarshalJSON method of *T to do that on its own. If UnmarshalJSON were called in this case, you would have to define (**T).UnmarshalJSON to set the *T to nil. This would make the most common case very awkward.

If you don't want JSON null to become Go nil, don't use a pointer.

huangapple
  • 本文由 发表于 2015年12月9日 02:46:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/34163625.html
匿名

发表评论

匿名网友

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

确定