Częściowo JSON unmarshal do mapy w Go

98

Mój serwer Websocket będzie odbierał i nierzeczywiste dane JSON. Te dane będą zawsze opakowane w obiekt z parami klucz / wartość. Ciąg klucza będzie działał jako identyfikator wartości, informując serwer Go o rodzaju wartości. Wiedząc, jaki typ wartości, mogę następnie przejść do JSON, aby usunąć wartość do odpowiedniego typu struktury.

Każdy obiekt json może zawierać wiele par klucz / wartość.

Przykład JSON:

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

Czy można "encoding/json"to zrobić w łatwy sposób ?

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

Dzięki za wszelkiego rodzaju sugestie / pomoc!

ANisus
źródło

Odpowiedzi:

195

Można to osiągnąć poprzez Unmarshaling do pliku map[string]json.RawMessage.

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

Aby dokładniej przeanalizować sendMsg, możesz zrobić coś takiego:

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

Dla say, można zrobić to samo i unmarshal na ciąg znaków:

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

EDYTUJ: Pamiętaj, że będziesz musiał również wyeksportować zmienne w swojej strukturze sendMsg, aby poprawnie je usunąć. Więc twoja definicja struktury wyglądałaby tak:

type sendMsg struct {
    User string
    Msg  string
}

Przykład: https://play.golang.org/p/OrIjvqIsi4-

Stephen Weinberg
źródło
6
Idealny! Brakowało mi, jak możesz użyć RawMessage. Dokładnie to, czego potrzebowałem. O say, właściwie nadal chcę tego jako json.RawMessage, ponieważ ciąg nadal nie jest dekodowany (zawijanie "i \nznaki ucieczki itp.), Więc też go cofnę.
ANisus,
1
Poprawiłem odpowiedź, żeby pasowała do tego, co zrobiłeś. Dzięki
Stephen Weinberg
3
Zamiast tego należy wpisać map [string] * json.RawMessage, ponieważ metody Unmarshal / Marshal nie są zaimplementowane w json.RawMessage.
Albert
1
@albert, pracuje dla mnie: play.golang.org/p/XYsozrJrSl . Jednak masz rację, że użycie wskaźnika byłoby lepsze. Odwrotność mojego kodu nie działa poprawnie: play.golang.org/p/46JOdjPpVI . Użycie wskaźnika rozwiązuje ten problem : play.golang.org/p/ZGwhXkYUT3 .
Stephen Weinberg
3
Po zaktualizowaniu do * json.RawMessage musisz teraz usunąć je w wywołaniach json.Unmarshal.
Kyle Lemons
2

W nawiązaniu do odpowiedzi Stephena Weinberga, od tego czasu zaimplementowałem poręczne narzędzie o nazwie iojson , które pomaga w łatwym wypełnianiu danych do istniejącego obiektu, a także w kodowaniu istniejącego obiektu do łańcucha JSON. Udostępniono również oprogramowanie pośredniczące iojson do współpracy z innymi programami pośredniczącymi. Więcej przykładów można znaleźć pod adresem https://github.com/junhsieh/iojson

Przykład:

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

Przykładowe dane wyjściowe:

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
źródło
1

Oto elegancki sposób na zrobienie podobnej rzeczy. Ale dlaczego częściowo JSON jest nieważny? To nie ma sensu.

  1. Utwórz swoje struktury dla czatu.
  2. Dekoduj json do Struct.
  3. Teraz możesz łatwo uzyskać dostęp do wszystkiego w Struct / Object.

Spójrz poniżej na działający kod. Skopiuj i wklej.

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

Idź na plac zabaw

Taban Cosmos
źródło
1
częściowe anulowanie wyszukiwania jest konieczne, gdy pracujesz ze strukturami kilkuset zagnieżdżonych pól jako obiektami tymczasowymi. Na przykład pobierz plik json z serwera, zaktualizuj pojedyncze pola i prześlij go z powrotem na serwer.
Artem Baskakov