Parcialmente JSON unmarshal em um mapa em Go

98

Meu servidor websocket receberá e desempacotará dados JSON. Esses dados sempre serão agrupados em um objeto com pares de chave / valor. A string de chave atuará como identificador de valor, informando ao servidor Go que tipo de valor é. Ao saber que tipo de valor, posso prosseguir para JSON desempacotar o valor no tipo correto de estrutura.

Cada objeto json pode conter vários pares de chave / valor.

JSON de exemplo:

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

Existe alguma maneira fácil de usar o "encoding/json"pacote para fazer isso?

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

Obrigado por qualquer tipo de sugestão / ajuda!

ANisus
fonte

Respostas:

194

Isso pode ser feito por Unmarshaling em a map[string]json.RawMessage.

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

Para analisar melhor sendMsg, você poderia fazer algo como:

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

Pois say, você pode fazer a mesma coisa e desempacotar em uma string:

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

EDIT: Lembre-se de que você também precisará exportar as variáveis ​​em sua estrutura sendMsg para desempacotar corretamente. Portanto, sua definição de estrutura seria:

type sendMsg struct {
    User string
    Msg  string
}

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

Stephen Weinberg
fonte
6
Perfeito! Eu perdi como você poderia usar RawMessage. Exatamente o que eu precisava. Sobre say, na verdade ainda quero isso como json.RawMessage, porque a string ainda não foi decodificada ( caracteres de quebra "e escape \n, etc.), então vou desempacotar também.
ANisus
1
Eu fixei minha resposta para corresponder ao que você fez. Obrigado
Stephen Weinberg
3
O tipo deve ser map [string] * json.RawMessage, porque os métodos Unmarshal / Marshal não são implementados em json.RawMessage.
Albert
1
@albert, funciona para mim: play.golang.org/p/XYsozrJrSl . No entanto, você está correto ao dizer que usar um ponteiro seria melhor. O inverso do meu código não funciona corretamente: play.golang.org/p/46JOdjPpVI . Usar um ponteiro corrige isso: play.golang.org/p/ZGwhXkYUT3 .
Stephen Weinberg de
3
Depois de atualizar para * json.RawMessage, agora você precisa desreferenciá-los nas chamadas para json.Unmarshal.
Kyle Lemons
2

Além da resposta de Stephen Weinberg, desde então implementei uma ferramenta útil chamada iojson , que ajuda a popular dados para um objeto existente facilmente, bem como codificar o objeto existente para uma string JSON. Um middleware iojson também é fornecido para funcionar com outros middlewares. Mais exemplos podem ser encontrados em https://github.com/junhsieh/iojson

Exemplo:

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())
        }
    }
}

Saída de amostra:

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
fonte
1

Aqui está uma maneira elegante de fazer coisas semelhantes. Mas por que parcialmente JSON desmarca? Isso não faz sentido.

  1. Crie suas estruturas para o Chat.
  2. Decodifique json para Struct.
  3. Agora você pode acessar tudo em Struct / Object facilmente.

Veja abaixo o código de trabalho. Copie e cole.

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 playground

Taban Cosmos
fonte
1
o desempacotamento parcial é necessário quando você está trabalhando com estruturas de várias centenas de campos aninhados como objetos temporários. Por exemplo, obtenha json do servidor, atualize campos únicos e poste-o de volta no servidor.
Artem Baskakov