如何使用 String() 方法将嵌入式结构正确序列化为 JSON 字符串?

Dr.*_*emo 3 string format serialization json go

 package main

 import (
    "fmt"
    "encoding/json"
 )

 type Ticket struct {
    From string
    To   string
}

func (t Ticket) String() string {
    return fmt.Sprintf("%s - %s", t.From, t.To)
}

type Passenger struct {
    Name string `json:"Name"`
    Tkt  Ticket `json:"Ticket"`
}

func main() {
    p := Passenger{}
    p.Name = "John"
    p.Tkt.From = "New York"
    p.Tkt.To = "Washington"

    buf, _ := json.Marshal(p)
    fmt.Println(string(buf))
}
Run Code Online (Sandbox Code Playgroud)

此代码输出:

 {"Name":"John","Ticket":{"From":"New York","To":"Washington"}}
Run Code Online (Sandbox Code Playgroud)

但是,使用json.Marshal()方法(对于复杂结构很简单友好),如何使其输出如下:

 {"Name":"John","Ticket":"New York - Washington"}
Run Code Online (Sandbox Code Playgroud)

icz*_*cza 5

为了生成 Go 值的 JSON 表示,encoding/json包检查该值是否实现了json.Marshalerencoding.TextMarshaler接口,如果是,则使用/调用它们(按此顺序)。这是记录在json.Marshal()

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

json/encoding包不关心的String()方法。因此,如果您想控制您的值(Ticket结构)的 JSON 表示/输出,请json.Marshaler在其上实现(您可以String()根据自己的喜好调用):

func (t Ticket) MarshalJSON() ([]byte, error) {
    return []byte(`"` + t.String() + `"`), nil
}
Run Code Online (Sandbox Code Playgroud)

然后输出将如您所愿:

{"Name":"John","Ticket":"New York - Washington"}
Run Code Online (Sandbox Code Playgroud)

Go Playground上试一试。

需要注意的一件事:如果string生产者Ticket.String()包含引号",则输出将变为无效的 JSON,或者更有可能json.Marshal()返回错误。

为了处理这种转义,最好/最简单的是使用json包本身:告诉它对以下string结果进行 JSON 编码Ticket.String()

func (t Ticket) MarshalJSON() ([]byte, error) {
    return json.Marshal(t.String())
}
Run Code Online (Sandbox Code Playgroud)

现在,如果我们像这样测试它:

p.Name = "John"
p.Tkt.From = "New\" York" // CONTAINS QUOTE
p.Tkt.To = "Washington"

buf, err := json.Marshal(p)
fmt.Println(string(buf), err)
Run Code Online (Sandbox Code Playgroud)

输出仍将是有效的 JSON(在Go Playground上尝试):

{"Name":"John","Ticket":"New\" York - Washington"} <nil>
Run Code Online (Sandbox Code Playgroud)