Descompensación de objetos JSON anidados

122

Hay unas pocas preguntas sobre el tema , pero ninguno de ellos parecen cubrir mi caso, por lo tanto estoy creando una nueva.

Tengo JSON como el siguiente:

{"foo":{ "bar": "1", "baz": "2" }, "more": "text"}

¿Hay alguna forma de desarmar la propiedad de la barra anidada y asignarla directamente a una propiedad de estructura sin crear una estructura anidada?

La solución que estoy adoptando ahora mismo es la siguiente:

type Foo struct {
    More String `json:"more"`
    Foo  struct {
        Bar string `json:"bar"`
        Baz string `json:"baz"`
    } `json:"foo"`
    //  FooBar  string `json:"foo.bar"`
}

Esta es una versión simplificada, ignore la verbosidad. Como puede ver, me gustaría poder analizar y asignar el valor a

//  FooBar  string `json:"foo.bar"`

He visto gente usando un mapa, pero ese no es mi caso. Básicamente, no me importa el contenido de foo(que es un objeto grande), excepto por algunos elementos específicos.

¿Cuál es el enfoque correcto en este caso? No estoy buscando trucos extraños, así que si este es el camino a seguir, estoy de acuerdo con eso.

Simone Carletti
fuente

Respuestas:

67

¿Hay alguna forma de desarmar la propiedad de la barra anidada y asignarla directamente a una propiedad de estructura sin crear una estructura anidada?

No, encoding / json no puede hacer el truco con "> some> deep> childnode" como puede hacer encoding / xml. Las estructuras anidadas son el camino a seguir.

Volker
fuente
1
¿Por qué esto es diferente a encoding / xml?
Caleb Hearth
1
@CalebThompson La estructura de XML y JSON es completamente diferente, incluso si los casos simples se parecen. El contenido de una etiqueta XML es un poco: (Un mapa ordenado de sub-etiquetas O Texto) Y un mapa desordenado de atributos. JSON es mucho más parecido a una estructura Go. Entonces, mapear JSON a estructuras es mucho más simple: simplemente modele la estructura después de su JSON.
Volker
en mi caso, la estructura de JSON no está realmente decidida, así que puedo crear una estructura y cuando la analizo usando el mapa de la interfaz [cadena] {}, tengo problemas con los elementos anidados. Qué se puede hacer.?
viveksinghggits
Pero, ¿por qué no podemos deshacernos de la estructura dentro de la estructura?
Vitaly Zdanevich
29

Como mencionó Volker, las estructuras anidadas son el camino a seguir. Pero si realmente no desea estructuras anidadas, puede anular la función UnmarshalJSON.

https://play.golang.org/p/dqn5UdqFfJt

type A struct {
    FooBar string // takes foo.bar
    FooBaz string // takes foo.baz
    More   string 
}

func (a *A) UnmarshalJSON(b []byte) error {

    var f interface{}
    json.Unmarshal(b, &f)

    m := f.(map[string]interface{})

    foomap := m["foo"]
    v := foomap.(map[string]interface{})

    a.FooBar = v["bar"].(string)
    a.FooBaz = v["baz"].(string)
    a.More = m["more"].(string)

    return nil
}

Por favor ignore el hecho de que no estoy devolviendo un error adecuado. Dejé eso por simplicidad.

ACTUALIZACIÓN: Recuperando correctamente el valor "más".

rexposadas
fuente
3
Recibo & {FooBar: 1 FooBaz: 2 Más:}. Falta "Texto"
Guy Segev
@GuySegev Seguí adelante y actualicé mi respuesta para solucionar ese problema. Gracias por señalar eso.
rexposadas
22

Este es un ejemplo de cómo deshacer las respuestas JSON del servidor proxy Safebrowsing v4 API sbserver: https://play.golang.org/p/4rGB5da0Lt

// this example shows how to unmarshall JSON requests from the Safebrowsing v4 sbserver
package main

import (
    "fmt"
    "log"
    "encoding/json"
)

// response from sbserver POST request
type Results struct {
    Matches []Match     
}

// nested within sbserver response
type Match struct {
    ThreatType string 
    PlatformType string 
    ThreatEntryType string 
    Threat struct {
        URL string
    }
}

func main() {
    fmt.Println("Hello, playground")

    // sample POST request
    //   curl -X POST -H 'Content-Type: application/json' 
    // -d '{"threatInfo": {"threatEntries": [{"url": "http://testsafebrowsing.appspot.com/apiv4/ANY_PLATFORM/MALWARE/URL/"}]}}' 
    // http://127.0.0.1:8080/v4/threatMatches:find

    // sample JSON response
    jsonResponse := `{"matches":[{"threatType":"MALWARE","platformType":"ANY_PLATFORM","threatEntryType":"URL","threat":{"url":"http://testsafebrowsing.appspot.com/apiv4/ANY_PLATFORM/MALWARE/URL/"}}]}`

    res := &Results{}
    err := json.Unmarshal([]byte(jsonResponse), res)
        if(err!=nil) {
            log.Fatal(err)
        }

    fmt.Printf("%v\n",res)
    fmt.Printf("\tThreat Type: %s\n",res.Matches[0].ThreatType)
    fmt.Printf("\tPlatform Type: %s\n",res.Matches[0].PlatformType)
    fmt.Printf("\tThreat Entry Type: %s\n",res.Matches[0].ThreatEntryType)
    fmt.Printf("\tURL: %s\n",res.Matches[0].Threat.URL)
}
Franke
fuente
2
Gracias por demostrar que json.Unmarshal puede desagrupar datos json complejos y profundamente anidados. Mi problema fue que estaba leyendo JSON de un archivo y terminé con un relleno de cero. ¡Me alegra que hayas compartido esto!
Rohanthewiz
12

Si. Con gjson, todo lo que tienes que hacer ahora es:

bar := gjson.Get(json, "foo.bar")

barpodría ser una propiedad de estructura si lo desea. Además, no hay mapas.

cambio de arco iris
fuente
1
fastjson también permite el mismo truco: fastjson.GetString(json, "foo", "bar")
valyala
9

¿Qué pasa con los campos anónimos? No estoy seguro de si eso constituirá una "estructura anidada", pero es más limpio que tener una declaración de estructura anidada. ¿Qué sucede si desea reutilizar el elemento anidado en otro lugar?

type NestedElement struct{
    someNumber int `json:"number"`
    someString string `json:"string"`
}

type BaseElement struct {
    NestedElement `json:"bar"`
}
Rixarn
fuente
1

Asigne los valores de anidado jsona estructura hasta que sepa el tipo subyacente de claves json: -

package main

import (
    "encoding/json"
    "fmt"
)

// Object
type Object struct {
    Foo map[string]map[string]string `json:"foo"`
    More string `json:"more"`
}

func main(){
    someJSONString := []byte(`{"foo":{ "bar": "1", "baz": "2" }, "more": "text"}`)
    var obj Object
    err := json.Unmarshal(someJSONString, &obj)
    if err != nil{
        fmt.Println(err)
    }
    fmt.Println("jsonObj", obj)
}
Himanshu
fuente
0

Estaba trabajando en algo como esto. Pero está trabajando solo con estructuras generadas a partir de proto. https://github.com/flowup-labs/grpc-utils

en tu proto

message Msg {
  Firstname string = 1 [(gogoproto.jsontag) = "name.firstname"];
  PseudoFirstname string = 2 [(gogoproto.jsontag) = "lastname"];
  EmbedMsg = 3  [(gogoproto.nullable) = false, (gogoproto.embed) = true];
  Lastname string = 4 [(gogoproto.jsontag) = "name.lastname"];
  Inside string  = 5 [(gogoproto.jsontag) = "name.inside.a.b.c"];
}

message EmbedMsg{
   Opt1 string = 1 [(gogoproto.jsontag) = "opt1"];
}

Entonces tu salida será

{
"lastname": "Three",
"name": {
    "firstname": "One",
    "inside": {
        "a": {
            "b": {
                "c": "goo"
            }
        }
    },
    "lastname": "Two"
},
"opt1": "var"
}
Vladan Ryšavý
fuente
2
Agregue algunas líneas para explicar cómo esto responde a la pregunta. Si se elimina el repositorio, no queda ningún valor en la respuesta.
Ubercool
No creo que vuelva, amigos.
DevX
-1

La combinación de mapa y estructura permite desagrupar objetos JSON anidados donde la clave es dinámica. => mapa [cadena]

Por ejemplo: stock.json

{
  "MU": {
    "symbol": "MU",
    "title": "micro semiconductor",
    "share": 400,
    "purchase_price": 60.5,
    "target_price": 70
  },
  "LSCC":{
    "symbol": "LSCC",
    "title": "lattice semiconductor",
    "share": 200,
    "purchase_price": 20,
    "target_price": 30
  }
}

Ir aplicación

package main

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "os"
)

type Stock struct {
    Symbol        string  `json:"symbol"`
    Title         string  `json:"title"`
    Share         int     `json:"share"`
    PurchasePrice float64 `json:"purchase_price"`
    TargetPrice   float64 `json:"target_price"`
}
type Account map[string]Stock

func main() {
    raw, err := ioutil.ReadFile("stock.json")
    if err != nil {
        fmt.Println(err.Error())
        os.Exit(1)
    }
    var account Account
    log.Println(account)
}

La clave dinámica en el hash es manejar una cadena y el objeto anidado está representado por una estructura.

jvmvik
fuente
3
esto parece incompleto. raw no se
usa