【问题标题】:Unmarshal JSON with some known, and some unknown field names使用一些已知和一些未知的字段名称解组 JSON
【发布时间】:2016-01-30 22:08:51
【问题描述】:

我有以下 JSON

{"a":1, "b":2, "?":1, "??":1}

我知道它有“a”和“b”字段,但我不知道其他字段的名称。所以我想将它解组为以下类型:

type Foo struct {
  // Known fields
  A int `json:"a"`
  B int `json:"b"`
  // Unknown fields
  X map[string]interface{} `json:???` // Rest of the fields should go here.
}

我该怎么做?

【问题讨论】:

    标签: json go


    【解决方案1】:

    解组两次

    一种选择是解组两次:一次转换为Foo 类型的值,一次转换为map[string]interface{} 类型的值,然后删除键"a""b"

    type Foo struct {
        A int                    `json:"a"`
        B int                    `json:"b"`
        X map[string]interface{} `json:"-"` // Rest of the fields should go here.
    }
    
    func main() {
        s := `{"a":1, "b":2, "x":1, "y":1}`
        f := Foo{}
        if err := json.Unmarshal([]byte(s), &f); err != nil {
            panic(err)
        }
    
        if err := json.Unmarshal([]byte(s), &f.X); err != nil {
            panic(err)
        }
        delete(f.X, "a")
        delete(f.X, "b")
    
        fmt.Printf("%+v", f)
    }
    

    输出(在Go Playground 上试试):

    {A:1 B:2 X:map[x:1 y:1]}
    

    解组一次并手动处理

    另一种选择是解组一次到 map[string]interface{} 并手动处理 Foo.AFoo.B 字段:

    type Foo struct {
        A int                    `json:"a"`
        B int                    `json:"b"`
        X map[string]interface{} `json:"-"` // Rest of the fields should go here.
    }
    
    func main() {
        s := `{"a":1, "b":2, "x":1, "y":1}`
        f := Foo{}
        if err := json.Unmarshal([]byte(s), &f.X); err != nil {
            panic(err)
        }
        if n, ok := f.X["a"].(float64); ok {
            f.A = int(n)
        }
        if n, ok := f.X["b"].(float64); ok {
            f.B = int(n)
        }
        delete(f.X, "a")
        delete(f.X, "b")
    
        fmt.Printf("%+v", f)
    }
    

    输出相同(Go Playground):

    {A:1 B:2 X:map[x:1 y:1]}
    

    【讨论】:

    • 无论如何要自动处理 A 和 B?当处理具有 ~20 而不是 2 个字段的结构时,这将导致非常冗长的代码。
    【解决方案2】:

    这不是很好,但你可以通过实现Unmarshaler

    type _Foo Foo
    
    func (f *Foo) UnmarshalJSON(bs []byte) (err error) {
        foo := _Foo{}
    
        if err = json.Unmarshal(bs, &foo); err == nil {
            *f = Foo(foo)
        }
    
        m := make(map[string]interface{})
    
        if err = json.Unmarshal(bs, &m); err == nil {
            delete(m, "a")
            delete(m, "b")
            f.X = m
        }
    
        return err
    }
    

    _Foo 类型是必要的,以避免在解码时递归。

    【讨论】:

    • 为什么只使用 Foo 会导致递归?
    • 顺便说一句,我使用它取得了一些成功。此处示例:play.golang.org/p/WLeEJIESg6
    • 我也认为这种利用 bson 模块的解决方案很好,特别是如果您已经将它作为依赖项包含在内:devel.io/2013/08/19/go-handling-arbitrary-json
    • "为什么只使用 Foo 会导致递归?" - @Chris json 包检查一个类型是否定义了UnmarshalJSON(),如果是,它调用该实现。所以我们到达函数的顶部,然后我们在f上调用Unmarshal(),json包检查Foo是否定义了UnmarshalJSON(),它确实如此,所以它调用它,依此类推,以无限递归。 _Foo 的目的是成为一种实现UnmarshalJSON()的类型,以打破循环。
    • Chris 到 devel.io 的链接现在已经失效。为了节省您在回程机器上的搜索,可以在这里找到:web.archive.org/web/20161019055501/http://devel.io/2013/08/19/…
    【解决方案3】:

    最简单的方法是使用这样的接口:

    var f interface{}
    s := `{"a":1, "b":2, "x":1, "y":1}`
    
    if err := json.Unmarshal([]byte(s), &f); err != nil {
        panic(err)
    }
    

    Go Playground example

    【讨论】:

    • 它以这种方式解组,但之后如何访问这些值?
    【解决方案4】:

    我使用接口来解组不确定类型的 json。

    bytes := []byte(`{"name":"Liam","gender":1, "salary": 1}`)
    var p2 interface{}
    json.Unmarshal(bytes, &p2)
    m := p2.(map[string]interface{})
    fmt.Println(m)
    

    【讨论】:

      【解决方案5】:

      几乎单程,使用json.RawMessage

      我们可以解组到map[string]json.RawMessage,然后分别解组每个字段。

      JSON 会被标记两次,但这很便宜。

      可以使用以下辅助函数:

      func UnmarshalJsonObject(jsonStr []byte, obj interface{}, otherFields map[string]json.RawMessage) (err error) {
          objValue := reflect.ValueOf(obj).Elem()
          knownFields := map[string]reflect.Value{}
          for i := 0; i != objValue.NumField(); i++ {
              jsonName := strings.Split(objValue.Type().Field(i).Tag.Get("json"), ",")[0]
              knownFields[jsonName] = objValue.Field(i)
          }
      
          err = json.Unmarshal(jsonStr, &otherFields)
          if err != nil {
              return
          }
      
          for key, chunk := range otherFields {
              if field, found := knownFields[key]; found {
                  err = json.Unmarshal(chunk, field.Addr().Interface())
                  if err != nil {
                      return
                  }
                  delete(otherFields, key)
              }
          }
          return
      }
      

      这是 Go Playground 上的完整代码 - http://play.golang.org/p/EtkJUzMmKt

      【讨论】:

      • 这仅适用于对象,不适用于数组,也不适用于字符串,所有这些都是有效的 json
      【解决方案6】:

      单通,使用github.com/ugorji/go/codec

      当解组到 map 时,encoding/json 会清空地图,但 ugorji/go/codec 不会。它还尝试填充现有值,因此我们可以将指向 foo.A、foo.B 的指针放入 foo.X:

      package main
      
      import (
          "fmt"
          "github.com/ugorji/go/codec"
      )
      
      type Foo struct {
          A int
          B int
          X map[string]interface{}
      }
      
      func (this *Foo) UnmarshalJSON(jsonStr []byte) (err error) {
          this.X = make(map[string]interface{})
          this.X["a"] = &this.A
          this.X["b"] = &this.B
          return codec.NewDecoderBytes(jsonStr, &codec.JsonHandle{}).Decode(&this.X)
      }
      
      func main() {
          s := `{"a":1, "b":2, "x":3, "y":[]}`
          f := &Foo{}
          err := codec.NewDecoderBytes([]byte(s), &codec.JsonHandle{}).Decode(f)
          fmt.Printf("err = %v\n", err)
          fmt.Printf("%+v\n", f)
      }
      

      【讨论】:

        【解决方案7】:

        使用 Hashicorp 的 map-to-struct 解码器,它会跟踪未使用的字段:https://godoc.org/github.com/mitchellh/mapstructure#example-Decode--Metadata

        这是两遍,但您不必在任何地方使用已知的字段名称。

        func UnmarshalJson(input []byte, result interface{}) (map[string]interface{}, error) {
            // unmarshal json to a map
            foomap := make(map[string]interface{})
            json.Unmarshal(input, &foomap)
        
            // create a mapstructure decoder
            var md mapstructure.Metadata
            decoder, err := mapstructure.NewDecoder(
                &mapstructure.DecoderConfig{
                    Metadata: &md,
                    Result:   result,
                })
            if err != nil {
                return nil, err
            }
        
            // decode the unmarshalled map into the given struct
            if err := decoder.Decode(foomap); err != nil {
                return nil, err
            }
        
            // copy and return unused fields
            unused := map[string]interface{}{}
            for _, k := range md.Unused {
                unused[k] = foomap[k]
            }
            return unused, nil
        }
        
        type Foo struct {
            // Known fields
            A int
            B int
            // Unknown fields
            X map[string]interface{} // Rest of the fields should go here.
        }
        
        func main() {
            s := []byte(`{"a":1, "b":2, "?":3, "??":4}`)
        
            var foo Foo
            unused, err := UnmarshalJson(s, &foo)
            if err != nil {
                panic(err)
            }
        
            foo.X = unused
            fmt.Println(foo) // prints {1 2 map[?:3 ??:4]}
        }
        

        【讨论】:

          猜你喜欢
          • 2016-11-10
          • 1970-01-01
          • 2019-07-14
          • 1970-01-01
          • 1970-01-01
          • 2013-02-21
          相关资源
          最近更新 更多