鉴于以下结构:
type Person {
Name string `json:"name"`
}
type Employee {
Person
JobRole string `json:"jobRole"`
}
我可以像预期的那样轻松地将员工编组为JSON:
p := Person{"Bob"}
e := Employee{&p, "Sales"}
output, _ := json.Marshal(e)
fmt.Printf("%s\n", string(output))
输出:
{"名称":"鲍勃"" jobRole":"销售"}
但是当嵌入式结构具有自定义MarshalJSON()
方法...
func (p *Person) MarshalJSON() ([]byte,error) {
return json.Marshal(struct{
Name string `json:"name"`
}{
Name: strings.ToUpper(p.Name),
})
}
它完全打破了:
p := Person{"Bob"}
e := Employee{&p, "Sales"}
output, _ := json.Marshal(e)
fmt.Printf("%s\n", string(output))
现在导致:
{"名称":" BOB"}
(请注意明显缺少jobRole
字段)
这很容易预料......嵌入式Person
结构实现了被调用的MarshalJSON()
函数。
麻烦的是,它不是我想要的。我想要的是:
{"名称":" BOB"" jobRole":"销售"}
即,正常编码Employee
的字段,并按照Person
的{{1}}方法编组其字段,并交回一些整齐的JSON
现在我也可以向MarshalJSON()
添加MarshalJSON()
方法,但这要求我知道嵌入式类型也实现Employee
,并且(a)复制其逻辑,或(b)致电MarshalJSON()
' Person
并以某种方式操纵其输出以适应我想要的地方。这两种方法看起来都很邋and,而且不是很有前途的证明(如果某天我不控制的嵌入式类型会添加自定义MarshalJSON()
方法会怎么样?)
这里有没有其他选择我还没考虑过?
答案 0 :(得分:6)
不要将MarshalJSON
放在Person
上,因为它被提升为外部类型。而是制作type Name string
并Name
实施MarshalJSON
。然后将Person
更改为
type Person struct {
Name Name `json:"name"`
}
示例:https://play.golang.org/p/u96T4C6PaY
<强>更新强>
为了更一般地解决这个问题,你将不得不在外部类型上实现MarshalJSON
。内部类型的方法被提升为外部类型,所以你不会绕过它。您可以让外部类型调用内部类型MarshalJSON
然后将其解组为map[string]interface{}
这样的通用结构并添加您自己的字段。此示例执行此操作但它具有更改最终输出字段的顺序的副作用
答案 1 :(得分:1)
尽管这会产生与OP想要的输出不同的输出,但我认为它仍然可以用作防止MarshalJSON
嵌入式结构破坏包含它们的结构的编组的技术。
有a proposal for encoding/json
to recognize an inline
option in struct tags。如果可以实现,那么我认为最好避免在OP的示例中嵌入结构。
当前,a comment on the Go issue tracker中描述了一种合理的解决方法,它是此答案的基础。它由定义一个新类型组成,该类型将具有与要嵌入的原始结构相同的内存布局,但是没有一个方法:
https://play.golang.org/p/BCwcyIqv0F7
package main
import (
"encoding/json"
"fmt"
"strings"
)
type Person struct {
Name string `json:"name"`
}
func (p *Person) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
Name string `json:"name"`
}{
Name: strings.ToUpper(p.Name),
})
}
// person has all the fields of Person, but none of the methods.
// It exists to be embedded in other structs.
type person Person
type EmployeeBroken struct {
*Person
JobRole string `json:"jobRole"`
}
type EmployeeGood struct {
*person
JobRole string `json:"jobRole"`
}
func main() {
{
p := Person{"Bob"}
e := EmployeeBroken{&p, "Sales"}
output, _ := json.Marshal(e)
fmt.Printf("%s\n", string(output))
}
{
p := Person{"Bob"}
e := EmployeeGood{(*person)(&p), "Sales"}
output, _ := json.Marshal(e)
fmt.Printf("%s\n", string(output))
}
}
输出:
{"name":"BOB"}
{"name":"Bob","jobRole":"Sales"}
OP希望{"name":"BOB","jobRole":"Sales"}
。为了实现这一点,需要将Person.MarshalJSON
返回的对象“内联”到Employee.MashalJSON
产生的对象中,但要排除Person
中定义的字段。
答案 2 :(得分:0)
近4年后,我提出了一个与@jcbwlkr基本相似的答案,但不需要中间的解组/重新编组步骤,只需使用一点字节切片操作即可将两个JSON细分。
func (e *Employee) MarshalJSON() ([]byte, error) {
pJSON, err := e.Person.MarshalJSON()
if err != nil {
return nil, err
}
eJSON, err := json.Marshal(map[string]interface{}{
"jobRole": e.JobRole,
})
if err != nil {
return nil, err
}
eJSON[0] = ','
return append(pJSON[:len(pJSON)-1], eJSON...), nil
}
此方法here的其他详细信息和讨论。
答案 3 :(得分:0)
我在父结构上使用了这种方法,以防止嵌入的结构被覆盖封送处理:
func (e Employee) MarshalJSON() ([]byte, error) {
v := reflect.ValueOf(e)
result := make(map[string]interface{})
for i := 0; i < v.NumField(); i++ {
fieldName := v.Type().Field(i).Name
result[fieldName] = v.Field(i).Interface()
}
return json.Marshal(result)
}
这很方便,但是将嵌入的结构嵌套在输出中:
{"JobRole":"Sales","Person":{"name":"Bob"}}
对于像问题中这样的微小结构,@ Flimzy的答案很好,但可以更简洁地完成:
func (e Employee) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{
"jobRole": e.JobRole,
"name": e.Name,
})
}
答案 4 :(得分:-1)
在内部和外部字段中支持大量字段的更通用的方法。
副作用是您需要为每个外部结构编写此代码。
示例:https://play.golang.org/p/iexkUYFJV9K
package main
import (
"encoding/json"
"fmt"
"log"
"strings"
)
func Struct2Json2Map(obj interface{}) (map[string]interface{}, error) {
data, err := json.Marshal(obj)
if err != nil {
return nil, err
}
var kvs map[string]interface{}
err = json.Unmarshal(data, &kvs)
if err != nil {
return nil, err
}
return kvs, nil
}
type Person struct {
Name string `json:"-"`
}
func (p Person) MarshalJSONHelper() (map[string]interface{}, error) {
return Struct2Json2Map(struct {
Name string `json:"name"`
}{
Name: strings.ToUpper(p.Name),
})
}
type Employee struct {
Person
JobRole string `json:"jobRole"`
}
func (e Employee) MarshalJSON() ([]byte, error) {
personKvs, err := e.Person.MarshalJSONHelper()
if err != nil {
return nil, err
}
type AliasEmployee Employee
kvs, err := Struct2Json2Map(struct {
AliasEmployee
} {
AliasEmployee(e),
})
for k,v := range personKvs {
kvs[k] = v
}
return json.Marshal(kvs)
}
func main() {
bob := Employee{
Person: Person{
Name: "Bob",
},
JobRole: "Sales",
}
output, err := json.Marshal(bob)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(output))
}