用未知字段解组JSON

Aby*_*byx 41 json go

我有以下JSON

{"a":1, "b":2, "?":1, "??":1}
Run Code Online (Sandbox Code Playgroud)

我知道它有"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.
}
Run Code Online (Sandbox Code Playgroud)

我怎么做?

icz*_*cza 24

解散两次

一种选择是解组两次:一次进入类型值,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)
}
Run Code Online (Sandbox Code Playgroud)

输出(在Go Playground上试试):

{A:1 B:2 X:map[x:1 y:1]}
Run Code Online (Sandbox Code Playgroud)

Unmarshal一次和手动处理

另一种选择是一次解组并手动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)
}
Run Code Online (Sandbox Code Playgroud)

输出相同(Go Playground):

{A:1 B:2 X:map[x:1 y:1]}
Run Code Online (Sandbox Code Playgroud)

  • 无论如何要自动化处理 A 和 B 吗?当处理具有 ~20 个字段而不是 2 个字段的结构时,这将导致代码非常冗长。 (3认同)

0x4*_*D53 22

这不好,但你可以通过实施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
}
Run Code Online (Sandbox Code Playgroud)

该类型_Foo对于避免解码时的递归是必要的.

  • “为什么只使用 Foo 会导致递归?” - @Chris json 包检查类型是否定义了 `UnmarshalJSON()`,如果是,则调用该实现。所以我们到达函数的顶部,然后我们在“f”上调用“Unmarshal()”,json包检查“Foo”是否定义了“UnmarshalJSON()”,并且它确实定义了,所以它调用它,并且依此类推,无限递归。`_Foo` 的目的是成为一个不实现 `UnmarshalJSON()` 的类型,以打破循环。 (3认同)
  • Chris 的 devel.io 链接现已失效。为了节省您在回程机器上的搜索,可以在这里找到:https://web.archive.org/web/20161019055501/http://devel.io/2013/08/19/go-handling- Arbitration- json/ (2认同)

小智 16

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

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


Ari*_*aco 10

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

var f interface{}
s := `{"a":1, "b":2, "x":1, "y":1}`

if err := json.Unmarshal([]byte(s), &f); err != nil {
    panic(err)
}
Run Code Online (Sandbox Code Playgroud)

去游乐场的例子

  • 它以这种方式解组,但是之后如何访问这些值呢? (2认同)

Aby*_*byx 9

几乎单通,用途 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
}
Run Code Online (Sandbox Code Playgroud)

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


Avi*_*rmi 9

单程含棉花糖

我们用棉花糖来解决这个问题。它不需要任何类型的显式编码,这使您的代码比其他解决方案更干净、更易于维护,但它也提供了最佳性能(比此处提供的其他解决方案快 3 倍,请参阅存储库中的基准测试和结果

type Foo struct {
    A int `json:"a"`
    B int `json:"b"`
}

func main() {
    s := `{"a":1, "b":2, "x":1, "y":1}`
    f := Foo{}
    result, err := marshmallow.Unmarshal([]byte(s), &f)
    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", f)      // {A:1 B:2}
    fmt.Printf("%+v\n", result) // map[a:1 b:2 x:1 y:1]
}
Run Code Online (Sandbox Code Playgroud)

游乐场链接

Marshmallow 在 PerimeterX 内部使用了一段时间,我们最近决定将其开源。我们还写了一篇博客文章,介绍它如何帮助我们在生产中削减 70% 的 JSON 解析成本。


Aby*_*byx 5

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

当解组为 a 时mapencoding/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)
}
Run Code Online (Sandbox Code Playgroud)


Cir*_*all 5

使用 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]}
}
Run Code Online (Sandbox Code Playgroud)