Struct含有匿名字段,该匿名字段有MarshalJSON方法,导致Struct不能正确转换成json字符串!!

这是问题的原地址:
http://stackoverflow.com/questions/38489776/idiomatic-way-to-embed-struct-with-custom-marshaljson-method

目前我遇到,解决方法是有的,但找不到比较好的解决方法,具体我用中文重新表述问题。

给出下面的一个结构:

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))

输出的内容:

{“name”:”Bob”,”jobRole”:”Sales”}

但是当我定义了一个自定义的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))

结果却变了

{“name”:”BOB”}

很明显,这个输出缺少了jobRole字段的内容

我们很容易知道原因,因为里面的匿名字段Person结构体实现了MarshalJSON()方法,所以这个方法被调用了。

麻烦的是,这不是我想要的结果,我想要的结果是这样的:

{“name”:”BOB”,”jobRole”:”Sales”}

现在,我添加了一个MarshalJSON()方法到Employee结构体中,如下:

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”}

变成了一个“先有鸡还是先有蛋”的过程。

大家有没有什么好的办法解决?

共 3 个回复


stevewang

package main

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

type Person struct {
    Name string
}

func (p *Person) MarshalJSON() ([]byte, error) {
    str := fmt.Sprintf(`{"name":"%s"}`, strings.ToUpper(p.Name))
    return []byte(str), nil
}

type Employee struct {
    Person
    JobRole string
}

func (e *Employee) MarshalJSON() ([]byte, error) {
    str := fmt.Sprintf(`{"name":"%s", "jobRole":"%s"}`,
        strings.ToUpper(e.Name),
        e.JobRole)
    return []byte(str), nil
}

func main() {
    p := Person{"Bob"}
    e := Employee{
        Person:  p,
        JobRole: "Sales",
    }
    output, err := json.Marshal(&e)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(string(output))
}
# 0

harrykobe

这个方法有点笨,那变成把Person的装换重写了一遍,对日后维护也不是很有利

# 1

stevewang

你要是嫌这个写法笨,其实自己实现MarshalJSON岂不是更笨?因为你这个例子根本就没有必要自己实现MarshalJSON。我写的只是一个示例,用来说明在自己实现MarshalJSON的情况下怎么做而已。
你要想容易维护扩展性能好,可以自己组织调整一下。
比如

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "strings"
)

type Person struct {
    Name string
}

func (p *Person) MarshalJSON() ([]byte, error) {
    var buf bytes.Buffer
    buf.WriteByte('{')
    p.marshalJSON(&buf)
    buf.WriteByte('}')
    return buf.Bytes(), nil
}

func (p *Person) marshalJSON(w io.Writer) {
    fmt.Fprintf(w, `"name":"%s"`, strings.ToLower(p.Name))
}

type Employee struct {
    Person
    JobRole string
}

func (e *Employee) MarshalJSON() ([]byte, error) {
    var buf bytes.Buffer
    buf.WriteByte('{')
    e.Person.marshalJSON(&buf)
    buf.WriteByte(',')
    fmt.Fprintf(&buf, `"jobRole":"%s"`, e.JobRole)
    buf.WriteByte('}')
    return buf.Bytes(), nil
}
func main() {
    p := Person{"Bob"}
    e := Employee{
        Person:  p,
        JobRole: "Sales",
    }
    output, err := json.Marshal(&e)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(string(output))
}
# 2