Tomar una cadena JSON, desasignarla en un mapa interfaz {}, editarla y ordenarla en un byte parece ser más complicada de lo que debería ser

Estoy haciendo una manipulación JSON muy básica para aprender algo de Go, y funciona, excepto una cosa que parece no tener, tengo que escribir una asignación de .(map[string]interface{}) and .([]interface{}) para acceder entradas en el JSON, especialmente si son hijos de hijos de niños, etc.

Vea aquí (también en Go Playground: https://play.golang.org/p/Wd-pzHqTsU ):

 package main import ( "fmt" "encoding/json" ) func main() { JSON := []byte(`{"key1":"val1","key2":{"c1key1":"c1val1"},"key3":[{"c2key1":{"c3key1":"c3val1"}}]}`) fmt.Printf("%s\n", JSON) var d map[string]interface{} json.Unmarshal(JSON, &d) fmt.Println(d["key3"].([]interface{})[0].(map[string]interface{})["c2key1"].(map[string]interface{})["c3key1"]) d["key3"].([]interface{})[0].(map[string]interface{})["c2key1"].(map[string]interface{})["c3key1"] = "change1" fmt.Println(d["key3"].([]interface{})[0].(map[string]interface{})["c2key1"].(map[string]interface{})["c3key1"]) JSON, _ = json.Marshal(d) fmt.Printf("%s\n", JSON) } 

Que devuelve:

 {"key1":"val1","key2":{"c1key1":"c1val1"},"key3":[{"c2key1":{"c3key1":"c3val1"}}]} c3val1 change1 {"key1":"val1","key2":{"c1key1":"c1val1"},"key3":[{"c2key1":{"c3key1":"change1"}}]} 

Ahora en Python solo accedo a las claves / valores directamente en lugar de definir el tipo de lo que estoy accediendo cada vez, es decir, en lugar de fmt.Println(d["key3"].([]interface{})[0].(map[string]interface{})["c2key1"].(map[string]interface{})["c3key1"]) print d["key3"][0]["c2key1"]["c3key1"]

Ejemplo de Python:

 import json JSON = '{"key3": [{"c2key1": {"c3key1": "c3val1"}}], "key2": {"c1key1": "c1val1"}, "key1": "val1"}' print JSON d = json.loads(JSON) print d["key3"][0]["c2key1"]["c3key1"] d["key3"][0]["c2key1"]["c3key1"] = "change1" print d["key3"][0]["c2key1"]["c3key1"] JSON = json.dumps(d) print JSON 

Entonces, ¿estoy haciendo esto bien en Go? Y si es así, ¿cuál es el motivo de este diseño? O si no, ¿cómo debería hacerlo?

Prólogo: Optimicé y mejoré la solución que figura a continuación y la github.com/icza/dyno como biblioteca aquí: github.com/icza/dyno .


La forma más limpia sería crear tipos predefinidos (estructura struct ) que modelen su JSON y desempaquetar a un valor de ese tipo, y simplemente puede referirse a elementos usando selectores (para tipos de struct ) y expresiones de índice (para mapas y sectores) .

Sin embargo, si su entrada no es de una estructura predefinida, le sugiero las siguientes 2 funciones de ayuda: get() y set() . El primero accede (devuelve) un elemento arbitrario especificado por una ruta arbitraria (lista de claves de string de string y / o índices de división int ), el segundo cambia (establece) el valor especificado por una ruta arbitraria (las implementaciones de estas funciones auxiliares están en el final de la respuesta).

Solo debe incluir estas 2 funciones una vez en su proyecto / aplicación.

Y ahora, al utilizar estos ayudantes, las tareas que desea realizar se vuelven así de simples (al igual que la solución de python):

 fmt.Println(get(d, "key3", 0, "c2key1", "c3key1")) set("NEWVALUE", d, "key3", 0, "c2key1", "c3key1") fmt.Println(get(d, "key3", 0, "c2key1", "c3key1")) 

Salida:

 change1 NEWVALUE 

Pruebe su aplicación modificada en el patio de juegos Go .

Nota – Simplificación adicional:

Incluso puede guardar la ruta en una variable y reutilizarla para simplificar aún más el código anterior:

 path := []interface{}{"key3", 0, "c2key1", "c3key1"} fmt.Println(get(d, path...)) set("NEWVALUE", d, path...) fmt.Println(get(d, path...)) 

Y las implementaciones de get() y set() están debajo. Nota: se omite comprobar si la ruta es válida. Esta implementación utiliza interruptores de tipo :

 func get(m interface{}, path ...interface{}) interface{} { for _, p := range path { switch idx := p.(type) { case string: m = m.(map[string]interface{})[idx] case int: m = m.([]interface{})[idx] } } return m } func set(v interface{}, m interface{}, path ...interface{}) { for i, p := range path { last := i == len(path)-1 switch idx := p.(type) { case string: if last { m.(map[string]interface{})[idx] = v } else { m = m.(map[string]interface{})[idx] } case int: if last { m.([]interface{})[idx] = v } else { m = m.([]interface{})[idx] } } } } 

No, esta no es la forma más correcta de manejar datos estructurados de JSON en Go. En cambio, es mejor crear una “jerarquía de estructuras” y desestacionalizar su JSON en estructuras. P.ej

 type Data struct { Key1 string Key2 struct { C1Key1 string } Key3 []Key3 } type Key3 struct { C2Key1 struct { C3Key1 string } } 

Este enfoque:

  • le da más control sobre cómo se (des) marshaled sus datos (a través de las tags struct y json.Marshaler interfaces json.Unmarshaler y json.Marshaler )
  • te deshace de las aserciones de tipo
  • en cambio, te da más seguridad de tipo
  • tiene un mejor rendimiento, ya que el acceso de estructura es básicamente gratuito en comparación con el acceso al mapa.

Área de juegos: https://play.golang.org/p/9XIh8DX1Ms .