-
您不能将 MarshalWithESTag() 方法添加到 json 包,因为 Go 不允许 safe monkey patching。但是,您可以将MarshalWithESTag() 方法添加到您的Foo 结构中,此示例还向您展示了如何调用它:
func (f Foo) MarshalWithESTag() ([]byte, error) {
data, err := json.Marshal(f)
return data,err
}
func main() {
f := &Foo{"Bar"}
data, _ := f.MarshalWithESTag()
log.Println(string(data)) // -> {"bar":"Bar"}
}
-
接下来,您需要将 MarshalJSON() method 添加到您的 Foo 结构中。当您调用 json.Marshal() 并将 Foo 的实例传递给它时,它将被调用。
以下是一个简单的示例,它硬编码了 {"hello":"goodbye"} 的返回值,所以 you can see in the playground 如何添加MarshalJSON() 到 Foo 会影响 json.Marshal(Foo{"Bar"}):
func (f Foo) MarshalJSON() ([]byte, error) {
return []byte(`{"hello":"goodbye"}`),nil
}
输出将是:
{"hello":"goodbye"}
-
在MarshalJSON() 方法中,我们需要使用es tags 而不是json 标签生成JSON,这意味着我们需要在该方法中生成JSON,因为Go 没有为我们提供JSON;它希望我们生成它。
在 Go 中生成 JSON 的最简单方法是使用 json.Marshal()。但是,如果我们使用json.Marshal(f),其中f 是Foo 的一个实例,在调用MarshalJson() 时会以receiver 的形式传递,最终会陷入无限递归循环!
解决方案是基于Foo 的现有类型并且与Foo 相同的create a new struct type,除了它的标识。基于Foo 创建一个新类型esFoo 非常简单:
type esFoo Foo
-
由于我们有esFoo,我们现在可以将Foo 的实例转换为esFoo 类型,以打破与我们自定义MarshalJSON() 的关联。这是有效的,因为我们的方法特定于具有Foo 的identity 类型而不是esFoo 类型。将 esFoo 的实例传递给 json.Marshal() 允许我们使用从 Go 获得的默认 JSON 编组。
为了说明,在这里您可以看到一个使用 esFoo 并将其 Bar 属性设置为 "baz" 的示例,从而为我们提供 {"test":"baz"} 的输出 (您也可以在去游乐场):
type esFoo Foo
func (f Foo) MarshalJSON() ([]byte, error) {
es := esFoo(f)
es.Bar = "baz"
_json,err := json.Marshal(es)
return _json,err
}
输出将是:
{"test":"baz"}
-
接下来我们处理和操作MarshalJSON() 中的 JSON。 This can be done 通过使用 json.Unmarshal() 到 interface{} 变量,然后我们可以使用 type assertion 将变量视为 map。
这是一个独立的示例,与前面的示例无关通过打印map[maker:Chevrolet model:Corvette year:2021] 来说明这一点(同样你可以在 Go Playground 中 see it work):
package main
import (
"encoding/json"
"fmt"
)
type Car struct {
Maker string `json:"maker" es:"fabricante"`
Model string `json:"model" es:"modelo"`
Year int `json:"year" es:"año"`
}
var car = Car{
Maker:"Chevrolet",
Model:"Corvette",
Year:2021,
}
func main() {
_json,_ := json.Marshal(car)
var intf interface{}
_ = json.Unmarshal(_json, &intf)
m := intf.(map[string]interface{})
fmt.Printf("%v",m)
}
输出将是:
map[maker:Chevrolet model:Corvette year:2021]
-
我们的下一个挑战是访问标签。可以使用Reflection 访问标签。 Go 在标准 reflect 包中提供反射功能。
使用上面的 Car 结构,这里有一个简单的例子来说明如何使用反射。它使用reflect.TypeOf() 函数将类型作为值检索,然后自省该类型以检索每个字段的标签。检索每个标签的代码是 t.Field(i).Tag.Lookup("es"),希望这有点不言自明(同样是在 Go Playground 中的 check it out):
func main() {
t := reflect.TypeOf(car)
for i:=0; i<t.NumField();i++{
tag, _ := t.Field(i).Tag.Lookup("es")
fmt.Printf("%s\n",tag)
}
}
输出将是:
fabricante
modelo
año
-
现在我们已经涵盖了所有构建块,我们可以将它们整合到一个有效的解决方案中。唯一值得一提的是创建一个与m 长度相同的新映射变量_m,以允许我们使用es 标签存储值:
func (f Foo) MarshalJSON() ([]byte, error) {
es := esFoo(f)
_json,err := json.Marshal(es)
{
if err != nil {
goto end
}
var intf interface{}
err = json.Unmarshal(_json, &intf)
if err != nil {
goto end
}
m := intf.(map[string]interface{})
_m := make(map[string]interface{},len(m))
t := reflect.TypeOf(f)
i := 0
for _,v := range m {
tag, found := t.Field(i).Tag.Lookup("es")
if !found {
continue
}
_m[tag] = v
i++
}
_json,err = json.Marshal(_m)
}
end:
return _json,err
}
-
但是,还有一个细节未完成。使用上述所有代码,f.MarshalWithESTag() 将为es 标签生成 JSON,但json.Marshal(f) 也会生成 JSON,我们希望后者返回其对 json 标签的使用。
所以我们只需要解决:
一个。添加一个初始值为false的本地包变量useESTags,
b.在调用json.Marshal()之前修改f.MarshalWithESTag(),将useESTags设置为true,然后
c。在返回之前将useESTags 设置回false,并且
d。最后修改MarshalJSON() 以仅在useESTags 设置为true 时执行es 标签所需的逻辑:
这将我们带到了最终代码——Foo 中的第二个属性以提供更好的示例(最后,您当然可以在 Go Playground 中使用 see here):
package main
import (
"encoding/json"
"log"
"reflect"
)
type Foo struct {
Foo string `json:"test" es:"bar"`
Bar string `json:"live" es:"baz"`
}
type esFoo Foo
var useESTags = false
func (f Foo) MarshalWithESTag() ([]byte, error) {
useESTags = true
data, err := json.Marshal(f)
useESTags = false
return data,err
}
func (f Foo) MarshalJSON() ([]byte, error) {
es := esFoo(f)
_json,err := json.Marshal(es)
if useESTags {
if err != nil {
goto end
}
var intf interface{}
err = json.Unmarshal(_json, &intf)
if err != nil {
goto end
}
m := intf.(map[string]interface{})
_m := make(map[string]interface{},len(m))
t := reflect.TypeOf(f)
i := 0
for _,v := range m {
tag, found := t.Field(i).Tag.Lookup("es")
if !found {
continue
}
_m[tag] = v
i++
}
_json,err = json.Marshal(_m)
}
end:
return _json,err
}
func main() {
f := &Foo{"Hello","World"}
data, _ := json.Marshal(f)
log.Println(string(data)) // -> {"test":"Hello","live":"World"}
data, _ = f.MarshalWithESTag()
log.Println(string(data)) // -> {"bar":"Hello","baz":"World"}
}