Unmarshal 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.
}
Как это сделать?
Ответы
Ответ 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]}
Unmarshal один раз и ручная обработка
Другой вариант - размонтировать один раз в 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]}
Ответ 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
необходим, чтобы избежать рекурсии при декодировании.
Ответ 3
Самый простой способ - использовать такой интерфейс:
var f interface{}
s := `{"a":1, "b":2, "x":1, "y":1}`
if err := json.Unmarshal([]byte(s), &f); err != nil {
panic(err)
}
Перейти к примерам на игровой площадке
Ответ 4
Почти один проход, использует 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
Ответ 5
Одиночный проход, используйте github.com/ugorji/go/codec
При unmarshaling в 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)
}
Ответ 6
Используйте Hashicorp map-to-struct decoder, который отслеживает неиспользуемые поля: 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]}
}
Ответ 7
Я использую интерфейс для демарширования неопределенного типа JSON.
bytes := []byte('{"name":"Liam","gender":1, "salary": 1}')
var p2 interface{}
json.Unmarshal(bytes, &p2)
m := p2.(map[string]interface{})
fmt.Println(m)