Unmarshal JSON with some known, and some unknown field names

huangapple go评论102阅读模式
英文:

Unmarshal JSON with some known, and some unknown field names

问题

你好!要将未知字段解析为map[string]interface{}类型,你可以使用json.RawMessage作为中间类型。以下是一个示例代码:

import (
    "encoding/json"
    "fmt"
)

type Foo struct {
    A int `json:"a"`
    B int `json:"b"`
    X map[string]interface{} `json:"-"`
}

func (f *Foo) UnmarshalJSON(data []byte) error {
    type Alias Foo
    aux := &struct {
        X json.RawMessage `json:"-"`
        *Alias
    }{
        Alias: (*Alias)(f),
    }
    if err := json.Unmarshal(data, &aux); err != nil {
        return err
    }
    if err := json.Unmarshal(aux.X, &f.X); err != nil {
        return err
    }
    return nil
}

func main() {
    data := []byte(`{"a":1, "b":2, "?":1, "??":1}`)
    var foo Foo
    if err := json.Unmarshal(data, &foo); err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(foo)
}

在这个示例中,我们定义了一个Foo结构体,其中A和B是已知字段,X是未知字段的map。在UnmarshalJSON方法中,我们使用json.RawMessage作为中间类型来解析未知字段。首先,我们将整个JSON数据解析到一个辅助结构体中,其中X字段使用json.RawMessage类型来存储未知字段的原始JSON数据。然后,我们将aux.X解析为map[string]interface{}类型,并将结果存储在Foo结构体的X字段中。

希望这可以帮助到你!如果你有任何其他问题,请随时问我。

英文:

I have the following JSON

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

I know that it has the "a" and "b" fields, but I don't know the names of other fields. So I want to unmarshal it in the following type:

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.
}

How do I do that?

答案1

得分: 71

反序列化两次

一种选项是进行两次反序列化:一次将其解析为类型为Foo的值,一次将其解析为类型为map[string]interface{}的值,并删除键"a""b"

type Foo struct {
	A int                    `json:"a"`
	B int                    `json:"b"`
	X map[string]interface{} `json:"-"`
}

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:"-"`
}

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]}
英文:

Unmarshal twice

One option is to unmarshal twice: once into a value of type Foo and once into a value of type map[string]interface{} and removing the keys "a" and "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)
}

Output (try it on the Go Playground):

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

Unmarshal once and manual handling

Another option is to unmarshal once into an map[string]interface{} and handle the Foo.A and Foo.B fields manually:

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)
}

Output is the same (Go Playground):

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

答案2

得分: 28

这不是很好,但你可以通过实现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的存在是为了在解码时避免递归。

英文:

It's not nice, but you could to it by implementing 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
}

The type _Foo is necessary to avoid recursion while decoding.

答案3

得分: 23

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

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 示例

英文:

Simplest way is to use an interface like this:

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

得分: 19

我使用接口来解析不确定类型的 JSON。

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

I use interface to unmarshal uncertain type 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

得分: 17

几乎是单次遍历,使用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

英文:

Almost single pass, uses json.RawMessage

We can unmarshal into map[string]json.RawMessage, and then unmarshal each field separately.

JSON will be tokenized twice, but that's quite cheap.

The following helper function can be used:

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
}

Here is the complete code on Go Playground - http://play.golang.org/p/EtkJUzMmKt

答案6

得分: 9

单次通过使用Marshmallow

我们使用marshmallow来解决这个问题。它不需要任何显式的编码,这使得你的代码比其他解决方案更清晰、更易于维护,同时它还提供了最佳的性能(比其他解决方案快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]
}

Playground链接

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

英文:

Single Pass With Marshmallow

We use marshmallow to solve exactly that problem. It requires no explicit coding of any kind which keeps your code cleaner and more maintainable than other solutions, but it also provides the best performance (up to x3 faster than other solutions provided here, see benchmarks and results in the repo).

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

Playground link

Marshmallow is used internally at PerimeterX for some time and we've recently decided to open-source it. We also wrote a blog post about how it helped us trim 70% of our JSON parsing costs in production.

答案7

得分: 6

单次遍历,使用github.com/ugorji/go/codec

当将数据解组为map时,encoding/json会清空该map,但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)
}
英文:

Single pass, use github.com/ugorji/go/codec

When unmarshaling into a map, encoding/json empties the map, but ugorji/go/codec doesn't. It also attempts to fill existing values, so we can put pointers to foo.A, foo.B into 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)
}

答案8

得分: 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) {
    // 将json解码为map
    foomap := make(map[string]interface{})
    json.Unmarshal(input, &foomap)

    // 创建一个mapstructure解码器
    var md mapstructure.Metadata
    decoder, err := mapstructure.NewDecoder(
        &mapstructure.DecoderConfig{
            Metadata: &md,
            Result:   result,
        })
    if err != nil {
        return nil, err
    }

    // 将解码后的map解码到给定的结构体中
    if err := decoder.Decode(foomap); err != nil {
        return nil, err
    }

    // 复制并返回未使用的字段
    unused := map[string]interface{}{}
    for _, k := range md.Unused {
        unused[k] = foomap[k]
    }
    return unused, nil
}

type Foo struct {
    // 已知字段
    A int
    B int
    // 未知字段
    X map[string]interface{} // 其余字段应放在这里
}

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) // 输出 {1 2 map[?:3 ??:4]}
}
英文:

Use Hashicorp's map-to-struct decoder, which keeps track of unused fields: https://godoc.org/github.com/mitchellh/mapstructure#example-Decode--Metadata

It's two-pass, but you don't have to use known field names anywhere.

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

huangapple
  • 本文由 发表于 2015年10月30日 21:06:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/33436730.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定