Parcialmente JSON unmarshal en un mapa en Go

98

Mi servidor websocket recibirá y eliminará datos JSON. Estos datos siempre se incluirán en un objeto con pares clave / valor. La cadena de claves actuará como identificador de valor, indicándole al servidor de Go qué tipo de valor es. Al saber qué tipo de valor, puedo proceder a descomponer el valor en JSON en el tipo correcto de estructura.

Cada objeto json puede contener varios pares clave / valor.

Ejemplo JSON:

{
    "sendMsg":{"user":"ANisus","msg":"Trying to send a message"},
    "say":"Hello"
}

¿Existe alguna forma sencilla de utilizar el "encoding/json"paquete para hacer esto?

package main

import (
    "encoding/json"
    "fmt"
)

// the struct for the value of a "sendMsg"-command
type sendMsg struct {
    user string
    msg  string
}
// The type for the value of a "say"-command
type say string

func main(){
    data := []byte(`{"sendMsg":{"user":"ANisus","msg":"Trying to send a message"},"say":"Hello"}`)

    // This won't work because json.MapObject([]byte) doesn't exist
    objmap, err := json.MapObject(data)

    // This is what I wish the objmap to contain
    //var objmap = map[string][]byte {
    //  "sendMsg": []byte(`{"user":"ANisus","msg":"Trying to send a message"}`),
    //  "say": []byte(`"hello"`),
    //}
    fmt.Printf("%v", objmap)
}

¡Gracias por cualquier tipo de sugerencia / ayuda!

ANiso
fuente

Respuestas:

194

Esto se puede lograr desagrupando en un map[string]json.RawMessage.

var objmap map[string]json.RawMessage
err := json.Unmarshal(data, &objmap)

Para analizar aún más sendMsg, puede hacer algo como:

var s sendMsg
err = json.Unmarshal(objmap["sendMsg"], &s)

Porque say, puede hacer lo mismo y desarmar en una cadena:

var str string
err = json.Unmarshal(objmap["say"], &str)

EDITAR: Tenga en cuenta que también necesitará exportar las variables en su estructura sendMsg para desarmar correctamente. Entonces su definición de estructura sería:

type sendMsg struct {
    User string
    Msg  string
}

Ejemplo: https://play.golang.org/p/OrIjvqIsi4-

Stephen Weinberg
fuente
6
¡Perfecto! He echado de menos cómo te vendría bien RawMessage. Exactamente lo que necesitaba. Acerca de say, en realidad todavía lo quiero como json.RawMessage, porque la cadena aún no está decodificada (encapsulado "y \ncaracteres de escape , etc.), así que también lo desarmaré.
ANiso
1
Arreglé mi respuesta para que coincidiera con lo que hiciste. Gracias
Stephen Weinberg
3
El tipo debe ser map [string] * json.RawMessage en su lugar porque los métodos Unmarshal / Marshal no están implementados en json.RawMessage.
albert
1
@albert, funciona para mí: play.golang.org/p/XYsozrJrSl . Sin embargo, tiene razón en que usar un puntero sería mejor. El inverso de mi código no funciona correctamente: play.golang.org/p/46JOdjPpVI . El uso de un puntero lo corrige: play.golang.org/p/ZGwhXkYUT3 .
Stephen Weinberg
3
Después de actualizar a * json.RawMessage, ahora debe eliminar la referencia en las llamadas a json.Unmarshal.
Kyle Lemons
2

Además de la respuesta de Stephen Weinberg, desde entonces he implementado una práctica herramienta llamada iojson , que ayuda a completar datos en un objeto existente fácilmente, así como a codificar el objeto existente en una cadena JSON. También se proporciona un middleware iojson para trabajar con otros middlewares. Se pueden encontrar más ejemplos en https://github.com/junhsieh/iojson

Ejemplo:

func main() {
    jsonStr := `{"Status":true,"ErrArr":[],"ObjArr":[{"Name":"My luxury car","ItemArr":[{"Name":"Bag"},{"Name":"Pen"}]}],"ObjMap":{}}`

    car := NewCar()

    i := iojson.NewIOJSON()

    if err := i.Decode(strings.NewReader(jsonStr)); err != nil {
        fmt.Printf("err: %s\n", err.Error())
    }

    // populating data to a live car object.
    if v, err := i.GetObjFromArr(0, car); err != nil {
        fmt.Printf("err: %s\n", err.Error())
    } else {
        fmt.Printf("car (original): %s\n", car.GetName())
        fmt.Printf("car (returned): %s\n", v.(*Car).GetName())

        for k, item := range car.ItemArr {
            fmt.Printf("ItemArr[%d] of car (original): %s\n", k, item.GetName())
        }

        for k, item := range v.(*Car).ItemArr {
            fmt.Printf("ItemArr[%d] of car (returned): %s\n", k, item.GetName())
        }
    }
}

Salida de muestra:

car (original): My luxury car
car (returned): My luxury car
ItemArr[0] of car (original): Bag
ItemArr[1] of car (original): Pen
ItemArr[0] of car (returned): Bag
ItemArr[1] of car (returned): Pen
Jun Xie
fuente
1

Aquí hay una forma elegante de hacer algo similar. Pero, ¿por qué JSON desarma parcialmente? Eso no tiene sentido.

  1. Crea tus estructuras para el chat.
  2. Decodifica json en Struct.
  3. Ahora puede acceder a todo en Struct / Object fácilmente.

Mire a continuación el código de trabajo. Cópialo y pégalo.

import (
   "bytes"
   "encoding/json" // Encoding and Decoding Package
   "fmt"
 )

var messeging = `{
"say":"Hello",
"sendMsg":{
    "user":"ANisus",
    "msg":"Trying to send a message"
   }
}`

type SendMsg struct {
   User string `json:"user"`
   Msg  string `json:"msg"`
}

 type Chat struct {
   Say     string   `json:"say"`
   SendMsg *SendMsg `json:"sendMsg"`
}

func main() {
  /** Clean way to solve Json Decoding in Go */
  /** Excellent solution */

   var chat Chat
   r := bytes.NewReader([]byte(messeging))
   chatErr := json.NewDecoder(r).Decode(&chat)
   errHandler(chatErr)
   fmt.Println(chat.Say)
   fmt.Println(chat.SendMsg.User)
   fmt.Println(chat.SendMsg.Msg)

}

 func errHandler(err error) {
   if err != nil {
     fmt.Println(err)
     return
   }
 }

Ir al patio de recreo

Taban Cosmos
fuente
1
la desordenación parcial es necesaria cuando se trabaja con estructuras de varios cientos de campos anidados como objetos temporales. Por ejemplo, obtenga json del servidor, actualice campos individuales y publíquelo de nuevo en el servidor.
Artem Baskakov