Go的MarshalJSON行为与多个嵌入结构体有关。

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

Go MarshalJSON behavior with multiple embedded structs

问题

我正在测试使用嵌入结构进行go json marshaling。然而,我发现当我嵌入time.Time时,为什么它总是覆盖其他嵌入结构,即使它们也提供了自己的自定义marshaling?下面的代码总是打印出"0001-01-01T00:00:00Z"。

package main

import (
	"encoding/json"
	"fmt"
	"time"
)

type A struct {
}

func (a A) Print() {
	fmt.Println("A")
}

type B struct {
	B int
}

func (a A) MarshalJSON() ([]byte, error) {
	return []byte(`"a"`), nil
}

func (b B) MarshalJSON() ([]byte, error) {
	return []byte(`"b"`), nil
}

func (a B) Print() {
	fmt.Println("A")
}

type C struct {
	A
	B
	time.Time
	C int `json:"C"`
}

func main() {
	fmt.Println("Hello, 世界")
	c := C{}
	decode, err := json.Marshal(c)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(string(decode))
}
英文:

I'm testing out go json marshaling with embedded structs. However, I see that when I embed time.Time, why does it always override the other embedded structs even though they also provide their own custom marshaling? The code below always print out "0001-01-01T00:00:00Z"

package main

import (
	"encoding/json"
	"fmt"
	"time"
)

type A struct {
}

func (a A) Print() {
	fmt.Println("A")
}

type B struct {
	B int
}

func (a A) MarshalJSON() ([]byte, error) {
	return []byte(`"a"`), nil
}

func (b B) MarshalJSON() ([]byte, error) {
	return []byte(`"b"`), nil
}

func (a B) Print() {
	fmt.Println("A")
}

type C struct {
	A
	B
	time.Time
	C int `json:"C"`
}

func main() {
	fmt.Println("Hello, 世界")
	c := C{}
	decode, err := json.Marshal(c)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(string(decode))
}

答案1

得分: 5

如果嵌入了具有相同名称字段或方法的多个类型,则这些字段或方法将不再直接可访问。

选择器:

  • 对于类型为T*T(其中T不是指针或接口类型)的值xx.f表示在T中最浅深度处存在的字段或方法f。如果最浅深度处不止一个f,则选择器表达式是非法的。

这意味着,给定以下类型集合:

type S1 struct { F string }

type S2 struct { F string }

type S3 struct {
    S1
    S2
}

表达式s3.F是非法的:

var s3 S3
_ = s3.F // ambiguous selector s3.F

这是因为在最浅深度处存在多个F。你可以在playground上尝试。

相同的规则适用于方法。由此可知,你的类型C不满足json.Marshaler接口,因为它在相同深度上嵌入了多个实现MarshalJSON()方法的类型。你可以在playground上自行验证。

然而,问题仍然存在,为什么嵌入的time.Time的自定义编组仍然被使用。这是因为time.Time不仅实现了json.Marshaler接口,还实现了encoding.TextMarshaler接口(文档playground)。而且,json.Marshal文档中如下所述:

  • Marshal递归地遍历值v。如果遇到的值实现了Marshaler接口且不是空指针,则Marshal调用其MarshalJSON方法生成JSON。如果没有MarshalJSON方法,但该值实现了encoding.TextMarshaler接口,Marshal将调用其MarshalText方法并将结果编码为JSON字符串。

你可以在这里看到,一旦AB也实现了encoding.TextMarshaler接口,那么time.TimeMarshalText方法将不再被使用。

英文:

If you embed multiple types that have identically named fields or methods, then those fields or methods will not be accessible directly anymore.

Selectors:

> x.f
> 1. For a value x of type T or *T where T is not a pointer or
> interface type, x.f denotes the field or method at the shallowest depth
> in T where there is such an f. If there is not exactly one f
> with shallowest depth, the selector expression is illegal.

That means that, given the following set of types:

type S1 struct { F string }

type S2 struct { F string }

type S3 struct {
    S1
    S2
}

the expression s3.F is illegal:

var s3 S3
_ = s3.F // ambiguous selector s3.F

this is because there are more than one F at the shallowest depth. You can try it on playground.

The same rules apply to methods. From this it follows that your type C does NOT satisfy the json.Marshaler interface because it embeds, at the same depth, more that one type that implements the MarshalJSON() method. You can see that for yourself on playground.


Then, however, the question still remains as to why the embedded time.Time's custom marshaling is used regardless. This is because time.Time implements not only the json.Marshaler interface but also the encoding.TextMarshaler interface (docs & playground). And the json.Marshal's documentation says the following:

> Marshal traverses the value v recursively. If an encountered value
> implements the Marshaler interface and is not a nil pointer, Marshal
> calls its MarshalJSON method to produce JSON. If no MarshalJSON method
> is present but the value implements encoding.TextMarshaler instead,
> Marshal calls its MarshalText method and encodes the result as a JSON
> string
.

You can see here that the behaviour described above holds once you also have A or B implement the encoding.TextMarshaler interface, then the time.Time's MarshalText method will not be used anymore.

huangapple
  • 本文由 发表于 2022年4月29日 11:50:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/72052635.html
匿名

发表评论

匿名网友

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

确定