这是问题的原地址:
[http://stackoverflow.com/questions/38489776/idiomatic-way-to-embed-struct-with-custom-marshaljson-method](http://stackoverflow.com/questions/38489776/idiomatic-way-to-embed-struct-with-custom-marshaljson-method "http://stackoverflow.com/questions/38489776/idiomatic-way-to-embed-struct-with-custom-marshaljson-method")
目前我遇到,解决方法是有的,但找不到比较好的解决方法,具体我用中文重新表述问题。
给出下面的一个结构:
```go
type Person {
Name string `json:"name"`
}
type Employee {
Person
JobRole string `json:"jobRole"`
}
```
我可以很轻松地转换成我想需要的JSON字符串格式,例子如下:
```go
p := Person{"Bob"}
e := Employee{&p, "Sales"}
output, _ := json.Marshal(e)
fmt.Printf("%s\n", string(output))
```
输出的内容:
> {"name":"Bob","jobRole":"Sales"}
但是当我定义了一个自定义的MarshalJSON()方法后……
```go
func (p *Person) MarshalJSON() ([]byte,error) {
return json.Marshal(struct{
Name string `json:"name"`
}{
Name: strings.ToUpper(p.Name),
})
}
```
同样的输出代码:
```go
p := Person{"Bob"}
e := Employee{&p, "Sales"}
output, _ := json.Marshal(e)
fmt.Printf("%s\n", string(output))
```
结果却变了
> {"name":"BOB"}
很明显,这个输出缺少了`jobRole`字段的内容
我们很容易知道原因,因为里面的匿名字段`Person`结构体实现了`MarshalJSON()`方法,所以这个方法被调用了。
麻烦的是,这不是我想要的结果,我想要的结果是这样的:
> {"name":"BOB","jobRole":"Sales"}
现在,我添加了一个`MarshalJSON()`方法到`Employee`结构体中,如下:
```go
func (e *Employee) MarshalJSON() ([]byte,error) {
return json.Marshal(struct{
Person
JobRole string `json:"jobRole"`
}{
Person: e.Person,
JobRole: e.JobRole,
})
}
```
但我仍要输出匿名字段`Person`的内容,那么就必定会调用`Person`结构体里面的`MarshalJSON()`方法,这样一下,整个过程变成了一个死循环,得出的结果仍然是:
> {"name":"BOB"}
变成了一个“先有鸡还是先有蛋”的过程。
大家有没有什么好的办法解决?
其实overflow里面说的这个方法不是不可以,只是这个结构体可能有很多用途,比如ORM需要判断里面的类型从而对它进行转换,现在将NAME的string类型换成了NAME类型,这样就会导致ORM检测不了类型,写不进数据库
#2
更多评论
我的理解,问题是两个。
1、如何正确输出:
```Go
{"Name":"BOB","jobRole":"Sales"}
```
在overflow说明很清楚,如果要自己实现MarshalJSON,那么就要重新定义被struct使用的属性类型。原文如下:
>To solve this more generically you're going to have to implement MarshalJSON on the outer type. Methods on the inner type are promoted to the outer type so you're not going to get around that. You could have the outer type call the inner type's MarshalJSON then unmarshal that into a generic structure like map[string]interface{} and add your own fields. This example does that but it has a side effect of changing the order of the final output fields
如果不这么做,那么对于绑定的符合属性,则会因为属性具有MarshalJSON方法,而中断Marshal。
因此,要实现楼主要求的代码为:
```Go
package main
import (
"encoding/json"
"fmt"
"strings"
)
type NAME string
func (n NAME )MarshalJSON()([]byte,error){
return json.Marshal(strings.ToUpper(string(n)))
}
type Person struct {
Name NAME `json:"Name"`
}
type Employee struct {
Person
JobRole string `json:"jobRole"`
}
func main() {
foo1()
}
func foo1() {
p := Person{"Bob"}
e := Employee{p, "Sales"}
output, _ := json.Marshal(e)
fmt.Printf("%s\n", string(output))
}
```
>PS:按照示例代码,无法重现,怀疑是接收器多了个指针"*"。
2、死循环
很明显,不存在,否则不会输出答案。
具体实现逻辑是:Marshal-->e.reflectValue-->valueEncoder-->typeEncoder-->newTypeEncoder。
```Go
// newTypeEncoder constructs an encoderFunc for a type.
// The returned encoder only checks CanAddr when allowAddr is true.
func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc {
if t.Implements(marshalerType) {
return marshalerEncoder
}
if t.Kind() != reflect.Ptr && allowAddr {
if reflect.PtrTo(t).Implements(marshalerType) {
return newCondAddrEncoder(addrMarshalerEncoder, newTypeEncoder(t, false))
}
}
//....
}
```
从newTypeEncoder函数中可以看出,如果示例中的Employee具有MarshalJSON方法,则直接调用并返回,并不会再次调用其属性Person的MarshalJSON了,更不会有死循环的情况发生。
#1