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

时间:2016-01-06 17:08:49

标签: json go

我想使用自定义标记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

谢谢:)

1 个答案:

答案 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——之后我将解释代码。 (如果我的假设不正确,我也会解决这个问题):

  1. 您不能向 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"}
    }
    
  2. 接下来您需要将 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
    }
    
  3. {"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
  4. 既然我们有 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
    }
    
  5. 接下来我们处理和操作 {"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)
    }
    
  6. 我们的下一个挑战是访问标签。可以使用 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)
        }
    }
    
  7. 既然我们已经涵盖了所有构建块,我们可以将它们整合到一个可行的解决方案中。唯一值得一提的是创建了一个与 fabricante modelo año 长度相同的新地图变量 _m,以允许我们使用 m 标签存储值:

    es
  8. 然而,还有一个细节没有完成。使用上述所有代码,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

结语

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

  2. 最后,我不会提到反射可能很慢,并且这个例子每个对象多次使用反射来实现你想要的输出。对于许多用例,以这种方式处理 JSON 所需的时间并不重要。然而,对于许多其他用例,执行时间可能是一个交易杀手。一些评论说你应该以不同的方式处理这个问题;如果性能很重要和/或使用更多 idiomatic Go 的方法很重要,您可能需要认真考虑他们的建议。