我想使用自定义标记Marshal / Unmarshal Golang对象(json)。
像
type Foo struct {
Bar string `json:"test" es:"bar"`
}
data, _ := json.MarshalWithESTag(Foo{"Bar"})
log.Println(string(data)) // -> {"foo":"bar"}
换句话说,我不想在这里使用带有不同标记的encoding / json库:https://github.com/golang/go/blob/master/src/encoding/json/encode.go#L1033
谢谢:)
答案 0 :(得分:0)
我认为您编写示例的方式可能有点不正确?
当我使用 Marshal()
代替 MarshalWithESTag()
运行您的代码时,我得到的是 {"test":"Bar"}
而不是 {"foo":"test"}
,正如我认为您的示例所暗示的那样。
Here 是在 Go Playground 中运行以说明输出的代码:
package main
import (
"encoding/json"
"fmt"
)
type Foo struct {
Bar string `json:"test" es:"bar"`
}
func main() {
data, _ := json.Marshal(Foo{"Bar"})
fmt.Println(string(data))
}
假设我对您想要的内容是正确的,那么这意味着您真正想要的是当您调用 {"bar":"Bar"}
时您的输出是 json.MarshalWithESTag()
。
基于这个假设,您可以使用以下代码来完成——Go Playground 中的 you can see——之后我将解释代码。 (如果我的假设不正确,我也会解决这个问题):
您不能向 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
的实例传递给它时被调用。
下面是一个简单的例子,它硬编码了 {{1} 的返回值}} 所以you can see in the playground如何将{"hello":"goodbye"}
添加到MarshalJSON()
会影响Foo
:
json.Marshal(Foo{"Bar"})
此输出将是:
func (f Foo) MarshalJSON() ([]byte, error) {
return []byte(`{"hello":"goodbye"}`),nil
}
在 {"hello":"goodbye"}
方法中,我们需要使用 MarshalJSON()
tags 而不是 es
标签生成 JSON,这意味着我们需要在方法中生成 JSON因为 Go 没有为我们提供 JSON;它希望我们生成它。
而在 Go 中生成 JSON 的最简单方法是使用 json
。但是,如果我们使用 json.Marshal()
,其中 json.Marshal(f)
是 f
的一个实例,它在调用 Foo
时作为 receiver 传递,它将以无限递归结束循环!
解决方案是将create a new struct type基于并等同于现有的MarshalJson()
类型,除了它的身份。创建基于 Foo
的新类型 esFoo
非常简单:
Foo
既然我们有 type esFoo Foo
,我们现在可以将 esFoo
的实例转换为 Foo
类型,以打破与自定义 esFoo
的关联。这是有效的,因为我们的方法特定于具有 MarshalJSON()
的 identity 而不是类型 Foo
的类型。将 esFoo
的实例传递给 esFoo
允许我们使用从 Go 获得的默认 JSON 编组。
为了说明,您可以在此处看到一个示例,该示例使用 json.Marshal()
并将其 esFoo
属性设置为 Bar
,从而为我们提供 "baz"
(您也可以在 Go 游乐场see it run):
{"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"}
中的 JSON。 This can be done 通过将 MarshalJSON()
用于 json.Unmarshal()
变量,然后我们可以使用 type assertion 将变量视为 map。
这是一个与前面的示例无关的独立示例,它通过打印 interface{}
(同样,您可以在 Go Playground 中see it work)来说明这一点:
map[maker:Chevrolet model:Corvette year:2021]
此输出将是:
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)
}
我们的下一个挑战是访问标签。可以使用 Reflection 访问标签。 Go 在标准的 reflect 包中提供反射功能。
使用上面的 map[maker:Chevrolet model:Corvette year:2021]
结构,这里有一个简单的例子来说明如何使用反射。它使用 Car
函数将类型作为值检索,然后内省该类型以检索每个字段的标签。检索每个标签的代码是 reflect.TypeOf()
,希望这在某种程度上是不言自明的(再次,Go Playground 中的 check it out):
t.Field(i).Tag.Lookup("es")
此输出将是:
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()
标签生成 JSON,但 es
也会如此,我们希望后者返回对 json.Marshal(f)
标签的使用。
所以我们只需要解决:
一个。添加本地包变量json
,初始值为useESTags
,
B.在调用false
之前修改f.MarshalWithESTag()
将useESTags
设置为true
,然后
c.在返回之前将 json.Marshal()
设置回 useESTags
,并且
d。最后修改 false
以仅在 MarshalJSON()
设置为 es
时执行 useESTags
标签所需的逻辑:
这给我们带来了最终的代码——在 true
中有第二个属性来提供一个更好的例子 (最后,你当然可以在 Go 中see here游乐场):
Foo
如果我的假设是错误的,我想我至少可以假设我提供的这段代码足以让您实现目标。您应该能够交换输出中的键和值,如果这确实是您想要的,给定所显示的技术。如果没有,请发表评论寻求帮助。
最后,我不会提到反射可能很慢,并且这个例子每个对象多次使用反射来实现你想要的输出。对于许多用例,以这种方式处理 JSON 所需的时间并不重要。然而,对于许多其他用例,执行时间可能是一个交易杀手。一些评论说你应该以不同的方式处理这个问题;如果性能很重要和/或使用更多 idiomatic Go 的方法很重要,您可能需要认真考虑他们的建议。