web-dev-qa-db-ja.com

不明なフィールドを持つJSONの非整列化

私は次の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.
}

それ、どうやったら出来るの?

37
Abyx

二度アンマーシャル

1つのオプションは、2回マーシャリング解除することです。1回はFoo型の値に、もう1回は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.AおよびFoo.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]}
21
icza

ニースではありませんが、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は、デコード中の再帰を避けるために必要です。

20
0x434D53

最も簡単な方法は、次のようなインターフェイスを使用することです。

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

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

プレイグラウンドに移動する例

9
Ariel Monaco

ほぼシングルパス、json.RawMessageを使用

map[string]json.RawMessageに非整列化してから、各フィールドを個別に非整列化できます。

JSONは2回トークン化されますが、それは非常に安価です。

次のヘルパー関数を使用できます。

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

8
Abyx

未使用フィールドを追跡するHashicorpのmap-to-structデコーダーを使用します。 https://godoc.org/github.com/mitchellh/mapstructure#example-Decode--Metadata

2パスですが、既知のフィールド名をどこでも使用する必要はありません。

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]}
}
2

シングルパス、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)
}
2
Abyx

インターフェイスを使用して、不確実なタイプのJSONを非整列化します。

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