Recibo datos anidados de mongo y quiero aplanarlos en una estructura para almacenarlos en un archivo csv.

Los datos se ven así:

{
    "_id" : "bec7bfaa-7a47-4f61-a463-5966a2b5c8ce",
    "data" : {
        "driver" : {
            "etaToStore" : 156
        },
        "createdAt" : 1532590052,
        "_id" : "07703a33-a3c3-4ad5-9e06-d05063474d8c"
    }
} 

Y la estructura que quiero obtener debería ser algo como esto

type EventStruct struct {
    Id                  string      `bson:"_id"`
    DataId              string      `bson:"data._id"`
    EtaToStore          string      `bson:"data.driver.etaToStore"`
    CreatedAt           int         `bson:"data.createdAt"`
}

Esto no funciona, así que siguiendo algunas respuestas SO lo dividí en varias estructuras:

// Creating a structure for the inner struct that I will receive from the query
type DriverStruct struct {
    EtaToStore  int     `bson:"etaToStore"`
}

type DataStruct struct {
    Id          string `bson:"_id"`
    Driver      DriverStruct `bson:"driver"`
    CreatedAt   int `bson:"createdAt"`
}
// Flattenning out the structure & getting the fields we need only
type EventStruct struct {
    Id                  string      `bson:"_id"`
    Data                DataStruct  `bson:"data"`
}

Esto obtiene todos los datos del resultado de la consulta de Mongo pero está anidado:

{
  "Id": "bec7bfaa-7a47-4f61-a463-5966a2b5c8ce",
  "Data": {
     "Id": a33-a3c3-4ad5-9e06-d05063474d8c,
     "Driver": {
        "EtaToStore": 156
     },
     "CreatedAt": 1532590052
  }
}

Lo que quiero terminar es:

{
  "Id": "bec7bfaa-7a47-4f61-a463-5966a2b5c8ce",
  "DataId": "a33-a3c3-4ad5-9e06-d05063474d8c",
  "EtaToStore": 156,
  "CreatedAt": 1532590052
}

Estoy segura de que hay una manera fácil de hacer esto, pero no puedo entenderlo, ¡ayuda!

1
Naguib Ihab 17 sep. 2018 a las 09:52

3 respuestas

La mejor respuesta

Puede implementar la interfaz json.Unmarshaler para agregar un método personalizado para deshacer el json. Luego, en ese método, puede usar el formato de estructura anidada, pero devolver el aplanado al final.

func (es *EventStruct) UnmarshalJSON(data []byte) error {
    // define private models for the data format

    type driverInner struct {
        EtaToStore int `bson:"etaToStore"`
    }

    type dataInner struct {
        ID        string      `bson:"_id" json:"_id"`
        Driver    driverInner `bson:"driver"`
        CreatedAt int         `bson:"createdAt"`
    }

    type nestedEvent struct {
        ID   string    `bson:"_id"`
        Data dataInner `bson:"data"`
    }

    var ne nestedEvent

    if err := json.Unmarshal(data, &ne); err != nil {
        return err
    }

    // create the struct in desired format
    tmp := &EventStruct{
        ID:         ne.ID,
        DataID:     ne.Data.ID,
        EtaToStore: ne.Data.Driver.EtaToStore,
        CreatedAt:  ne.Data.CreatedAt,
    }

    // reassign the method receiver pointer
    // to store the values in the struct
    *es = *tmp
    return nil
}

Ejemplo ejecutable: https://play.golang.org/p/83VHShfE5rI

2
Zak 17 sep. 2018 a las 07:28

Esta pregunta tiene un año y medio de antigüedad, pero la encontré hoy mientras reaccionaba a una actualización de la API que me puso en la misma situación, así que aquí está mi solución (que, ciertamente, no he probado con bson , pero supongo que las implementaciones del lector de etiquetas de campo json y bson las manejan de la misma manera)

Los campos incrustados (a veces denominados anónimos) pueden capturar JSON, por lo que puede componer varias estructuras en una estructura compuesta que se comporte como una estructura única.

{
    "_id" : "bec7bfaa-7a47-4f61-a463-5966a2b5c8ce",
    "data" : {
        "driver" : {
            "etaToStore" : 156
        },
        "createdAt" : 1532590052,
        "_id" : "07703a33-a3c3-4ad5-9e06-d05063474d8c"
    }
} 
type DriverStruct struct {
    EtaToStore          string      `bson:"etaToStore"`

type DataStruct struct {
    DriverStruct                    `bson:"driver"`
    DataId              string      `bson:"_id"`
    CreatedAt           int         `bson:"createdAt"`
}

type EventStruct struct {
    DataStruct                      `bson:"data"`
    Id                  string      `bson:"_id"`
}

Puede acceder a los campos anidados de una estructura incrustada como si la estructura principal contuviera un campo equivalente, por ejemplo, EventStructInstance.EtaToStore es una forma válida de llegar a ellos.

Beneficios:

  • No tiene que implementar las interfaces Marshaller o Unmarshaller, lo cual es un poco excesivo para este problema
  • No requiere ningún campo de copia entre estructuras intermedias
  • Maneja la clasificación y la clasificación de forma gratuita

Lea más sobre los campos incrustados aquí.

1
Wug 12 mar. 2020 a las 16:22

Puedes usar básicamente la misma lógica que:

package utils

// FlattenIntegers flattens nested slices of integers
func FlattenIntegers(slice []interface{}) []int {
    var flat []int

    for _, element := range slice {
        switch element.(type) {
        case []interface{}:
            flat = append(flat, FlattenIntegers(element.([]interface{}))...)
        case []int:
            flat = append(flat, element.([]int)...)
        case int:
            flat = append(flat, element.(int))
        }
    }

    return flat
}

(Fuente: https://gist.github.com/Ullaakut/cb1305ede48f2391090d57cde355074f

Adaptándolo para lo que hay en su JSON. Si desea que sea genérico, deberá admitir todos los tipos que puede contener.

1
Ullaakut 17 sep. 2018 a las 06:56