Unmarshal JSON con campos desconocidos

Tengo el siguiente JSON

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

Sé que tiene los campos “a” y “b”, pero no sé los nombres de otros campos. Entonces quiero desempaquetarlo en el siguiente tipo:

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

¿Cómo puedo hacer eso?

No es agradable, pero podrías hacerlo implementando 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") fX = m } return err } 

El tipo _Foo es necesario para evitar la recursión durante la deencoding.

Unmarshal dos veces

Una opción es desempatar dos veces: una en un valor de tipo Foo y otra en un valor de tipo map[string]interface{} y eliminar las teclas "a" y "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(fX, "a") delete(fX, "b") fmt.Printf("%+v", f) } 

Salida (pruébalo en el área de juegos Go ):

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

Unmarshal una vez y manejo manual

Otra opción es desempatar una vez en un map[string]interface{} y manejar manualmente los campos Foo.A y 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 := fX["a"].(float64); ok { fA = int(n) } if n, ok := fX["b"].(float64); ok { fB = int(n) } delete(fX, "a") delete(fX, "b") fmt.Printf("%+v", f) } 

La salida es la misma ( Go Playground ):

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

La forma más simple es usar una interfaz como esta:

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

Ejemplo de Go Playground

Casi un solo pase, usa json.RawMessage

Podemos desempaquetar en el map[string]json.RawMessage , y luego desempaquetar cada campo por separado.

JSON será tokenizado dos veces, pero eso es bastante barato.

Se puede usar la siguiente función auxiliar:

 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 } 

Aquí está el código completo en Go Playground – http://play.golang.org/p/EtkJUzMmKt

Utilice el decodificador map-to-struct de Hashicorp, que realiza un seguimiento de los campos no utilizados: https://godoc.org/github.com/mitchellh/mapstructure#example-Decode–Metadata

Es de dos pasos, pero no tiene que usar nombres de campos conocidos en ninguna parte.

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

Pase único, use github.com/ugorji/go/codec

Al desasignar el map , la encoding/json vacía el mapa, pero ugorji/go/codec no lo hace. También intenta llenar los valores existentes, por lo que podemos poner los punteros a foo.A, foo.B en 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) }