带有自定义标签的 Golang Marshal/Unmarshal json

Max*_*ayt 7 json go

我想使用自定义标签编组/解组 Golang 对象 (json)。

喜欢

type Foo struct {
    Bar string `json:"test" es:"bar"`
}

data, _ := json.MarshalWithESTag(Foo{"Bar"})
log.Println(string(data)) // -> {"foo":"bar"}
Run Code Online (Sandbox Code Playgroud)

换句话说,我想在这里使用带有不同标签的encoding/json库: https: //github.com/golang/go/blob/master/src/encoding/json/encode.go#L1033

谢谢 :)

Mik*_*kel 8

我认为您编写示例的方式可能有点不正确?

\n

当我使用Marshal()inplace of运行您的代码时MarshalWithESTag(),我得到的结果{"test":"Bar"}并不{"foo":"test"}像您的示例所暗示的那样。\n是在 Go Playground 中运行的代码来说明输出:

\n
package main\n\nimport (\n    "encoding/json"\n    "fmt"\n)\ntype Foo struct {\n    Bar string `json:"test" es:"bar"`\n}\nfunc main() {\n    data, _ := json.Marshal(Foo{"Bar"})\n    fmt.Println(string(data))\n}\n
Run Code Online (Sandbox Code Playgroud)\n

假设我对您想要的内容是正确的,那么这意味着您真正想要的是{"bar":"Bar"}您调用时的输出json.MarshalWithESTag()

\n

基于这个假设,您可以使用以下代码 \xe2\x80\x94 来完成,您可以在 Go Playground \xe2\x80\x94 中看到它,之后我将解释该代码。(如果我的假设不正确,我也会解决这个问题):

\n
    \n
  1. 您无法将MarshalWithESTag()方法添加到包中json,因为 Go 不允许安全的 猴子修补。但是,您可以向结构添加一个MarshalWithESTag()方法Foo,此示例还向您展示了如何调用它:

    \n
    func (f Foo) MarshalWithESTag() ([]byte, error) {\n    data, err := json.Marshal(f)\n    return data,err\n}\n\nfunc main()  {\n    f := &Foo{"Bar"}\n    data, _ := f.MarshalWithESTag()\n    log.Println(string(data)) // -> {"bar":"Bar"}\n}\n
    Run Code Online (Sandbox Code Playgroud)\n
  2. \n
  3. 接下来,您需要向结构添加一个MarshalJSON()方法Foojson.Marshal()当您调用并向其传递一个实例时,它将被调用Foo

    以下是一个简单的示例,它对 的返回值进行了硬编码{"hello":"goodbye"},以便您可以在 Playground 中看到添加 aMarshalJSON()会如何Foo影响json.Marshal(Foo{"Bar"})

    \n
    func (f Foo) MarshalJSON() ([]byte, error) {\n    return []byte(`{"hello":"goodbye"}`),nil\n}\n
    Run Code Online (Sandbox Code Playgroud)\n

    其输出将是:

    \n
    {"hello":"goodbye"}\n
    Run Code Online (Sandbox Code Playgroud)\n
  4. \n
  5. 在方法内部MarshalJSON(),我们需要使用es 标签而不是json标签生成 JSON,这意味着我们需要在方法内生成 JSON,因为 Go 不向我们提供 JSON;它期望我们生成它。

    在 Go 中生成 JSON 最简单的方法是使用json.Marshal(). 然而,如果我们使用json.Marshal(f)where是一个在调用时作为接收者传递的f实例,它将最终陷入无限递归循环!解决方案是基于 的现有类型创建一个新的结构类型,除了其标识外,该类型与现有类型相同。基于以下内容创建新类型非常简单:FooMarshalJson()

    FooesFooFoo

    \n
    type esFoo Foo\n
    Run Code Online (Sandbox Code Playgroud)\n
  6. \n
  7. 既然我们已经有了,esFoo我们现在可以将我们的实例转换Foo为类型esFoo以打破与我们的自定义的关联MarshalJSON()。这是有效的,因为我们的方法特定于具有标识的类型Foo,而不是类型esFooesFoo传递to的实例json.Marshal()允许我们使用从 Go 获得的默认 JSON 编组。\n

    为了说明这一点,您可以在此处看到一个示例,该示例使用esFoo并设置其Bar属性来为"baz"我们提供输出{"test":"baz"} (您还可以看到它在 Go 中运行)操场):

    \n
    type esFoo Foo\nfunc (f Foo) MarshalJSON() ([]byte, error) {\n    es := esFoo(f)\n    es.Bar = "baz"\n    _json,err := json.Marshal(es)\n    return _json,err\n}\n
    Run Code Online (Sandbox Code Playgroud)\n

    其输出将是:

    \n
    {"test":"baz"}\n
    Run Code Online (Sandbox Code Playgroud)\n
  8. \n
  9. 接下来我们处理和操作里面的JSON MarshalJSON()。这可以通过使用变量来完成,然后我们可以使用类型断言将该变量视为映射。这是一个与前面的示例无关的独立示例,通过打印说明了这一点(您再次可以看到它在 Go Playground 中工作):json.Unmarshal()interface{}

    map[maker:Chevrolet model:Corvette year:2021]

    \n
    package main\n\nimport (\n    "encoding/json"\n    "fmt"\n)\ntype Car struct {\n    Maker string `json:"maker" es:"fabricante"`\n    Model string `json:"model" es:"modelo"`\n    Year  int    `json:"year"  es:"a\xc3\xb1o"`    \n}\nvar car = Car{\n    Maker:"Chevrolet",\n    Model:"Corvette",\n    Year:2021,\n}\n\nfunc main() {\n    _json,_ := json.Marshal(car)\n    var intf interface{}\n    _ = json.Unmarshal(_json, &intf)\n    m := intf.(map[string]interface{})      \n    fmt.Printf("%v",m)\n}\n
    Run Code Online (Sandbox Code Playgroud)\n

    其输出将是:

    \n
    map[maker:Chevrolet model:Corvette year:2021]\n
    Run Code Online (Sandbox Code Playgroud)\n
  10. \n
  11. 我们的下一个挑战是访问标签。可以使用Reflection访问标签。Go 在标准反射包中提供了反射功能。\n

    使用Car上面的结构,这是一个简单的示例,说明了如何使用反射。它使用该reflect.TypeOf()函数来检索类型作为值,然后内省该类型以检索每个字段的标签。检索每个标签的代码是 t.Field(i).Tag.Lookup("es"),希望它是不言自明的(再次,请在 Go Playground 中查看):

    \n
    func main() {\n    t := reflect.TypeOf(car)    \n    for i:=0; i<t.NumField();i++{\n        tag, _ := t.Field(i).Tag.Lookup("es")\n        fmt.Printf("%s\\n",tag)\n    }\n}\n
    Run Code Online (Sandbox Code Playgroud)\n

    其输出将是:

    \n
    fabricante\nmodelo\na\xc3\xb1o\n
    Run Code Online (Sandbox Code Playgroud)\n
  12. \n
  13. 现在我们已经涵盖了所有构建块,我们可以将它们整合到一个可行的解决方案中。唯一值得一提的补充是创建一个_m长度相同的新映射变量,以便m我们可以使用标签存储值es

    \n
    func (f Foo) MarshalJSON() ([]byte, error) {\n    es := esFoo(f)\n    _json,err := json.Marshal(es)\n    {\n        if err != nil {\n            goto end\n        }\n        var intf interface{}\n        err = json.Unmarshal(_json, &intf)\n        if err != nil {\n            goto end\n        }\n        m := intf.(map[string]interface{})\n        _m := make(map[string]interface{},len(m))\n        t := reflect.TypeOf(f)\n        i := 0\n        for _,v := range m {\n            tag, found := t.Field(i).Tag.Lookup("es")\n            if !found {\n                continue\n            }\n            _m[tag] = v\n            i++\n        }\n        _json,err = json.Marshal(_m)\n    }\nend:\n    return _json,err\n}\n
    Run Code Online (Sandbox Code Playgroud)\n
  14. \n
  15. 然而,还有一个细节尚未完成。上述所有代码f.MarshalWithESTag()将为es标签生成 JSON,但json.Marshal(f)我们也希望后者返回其对json标签的使用。\n

    因此,我们只需要:

    \n

    A。useESTags添加一个初始值为 的本地包变量false

    \n

    b. 修改f.MarshalWithESTag()为设置useESTagstrue调用前json.Marshal(),然后

    \n

    C。useESTags回到返回前的状态false,并且

    \n

    d. 最后修改MarshalJSON()为仅执行标签所需的逻辑,es如果useESTags设置为true:\n

    这将我们带到最终代码 \xe2\x80\x94 ,其中第二个属性Foo提供了更好的示例(最后,您当然可以看到在Go Playground 中):

    \n
    package main\n\nimport (\n    "encoding/json"\n    "log"\n    "reflect"\n)\n\ntype Foo struct {\n    Foo string `json:"test" es:"bar"`\n    Bar string `json:"live" es:"baz"`\n}\ntype esFoo Foo\nvar useESTags = false\nfunc (f Foo) MarshalWithESTag() ([]byte, error) {\n    useESTags = true\n    data, err := json.Marshal(f)\n    useESTags = false\n    return data,err\n}\nfunc (f Foo) MarshalJSON() ([]byte, error) {\n    es := esFoo(f)\n    _json,err := json.Marshal(es)\n    if useESTags {\n        if err != nil {\n            goto end\n        }\n        var intf interface{}\n        err = json.Unmarshal(_json, &intf)\n        if err != nil {\n            goto end\n        }\n        m := intf.(map[string]interface{})\n        _m := make(map[string]interface{},len(m))\n        t := reflect.TypeOf(f)\n        i := 0\n        for _,v := range m {\n            tag, found := t.Field(i).Tag.Lookup("es")\n            if !found {\n                continue\n            }\n            _m[tag] = v\n            i++\n        }\n        _json,err = json.Marshal(_m)\n    }\nend:\n    return _json,err\n}\n\nfunc main()  {\n    f := &Foo{"Hello","World"}\n    data, _ := json.Marshal(f)\n    log.Println(string(data)) // -> {"test":"Hello","live":"World"}\n    data, _ = f.MarshalWithESTag()\n    log.Println(string(data)) // -> {"bar":"Hello","baz":"World"}\n}\n
    Run Code Online (Sandbox Code Playgroud)\n
  16. \n
\n

结语

\n
    \n
  1. 如果我的假设是错误的,我想我至少可以假设我提供的这段代码足以让您实现您的目标。如果根据所显示的技术确实是您想要的,那么您应该能够交换输出中的键和值。如果没有,请评论寻求帮助。

    \n
  2. \n
  3. 最后,如果不提及反射可能很慢,并且此示例对每个对象使用多次反射来实现所需的输出,那就太失职了。对于许多用例来说,以这种方式处理 JSON 所需的时间并不重要。然而,对于许多其他用例来说,执行时间可能会成为交易杀手。一些人评论说你应该以不同的方式处理这个问题;如果性能很重要和/或使用更惯用的 Go方法很重要,您可能需要认真考虑他们的建议。

    \n
  4. \n
\n