153

My websocket server will receive and unmarshal JSON data. This data will always be wrapped in an object with key/value pairs. The key-string will act as value identifier, telling the Go server what kind of value it is. By knowing what type of value, I can then proceed to JSON unmarshal the value into the correct type of struct.

Each json-object might contain multiple key/value pairs.

Example JSON:

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

Is there any easy way using the "encoding/json" package to do this?

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

Thanks for any kind of suggestion/help!

ANisus
  • 74,460
  • 29
  • 162
  • 158

4 Answers4

264

This can be accomplished by Unmarshaling into a map[string]json.RawMessage.

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

To further parse sendMsg, you could then do something like:

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

For say, you can do the same thing and unmarshal into a string:

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

EDIT: Keep in mind you will also need to export the variables in your sendMsg struct to unmarshal correctly. So your struct definition would be:

type sendMsg struct {
    User string
    Msg  string
}

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

Stephen Weinberg
  • 51,320
  • 14
  • 134
  • 113
  • 6
    Perfect! I've missed how you could use `RawMessage`. Exactly what I needed. About `say`, I actually still want it as `json.RawMessage`, because the string is still not decoded (wrapping `"` and escaped `\n`-characters, etc), so I will unmarshal it too. – ANisus Jun 17 '12 at 08:00
  • 1
    I fixed my answer to match what you did. Thanks – Stephen Weinberg Jun 17 '12 at 15:32
  • 3
    The type should be map[string]*json.RawMessage instead because Unmarshal/Marshal methods are not implemented on json.RawMessage. – albert Jan 20 '13 at 04:18
  • 1
    @albert, works for me: http://play.golang.org/p/XYsozrJrSl. However, you are correct that using a pointer would be better. The inverse of my code doesn't work correctly: http://play.golang.org/p/46JOdjPpVI. Using a pointer fixes it: http://play.golang.org/p/ZGwhXkYUT3. – Stephen Weinberg Jan 20 '13 at 15:47
  • 3
    After you updated to *json.RawMessage, you now need to dereference them in the calls to json.Unmarshal. – Kyle Lemons Feb 18 '13 at 17:31
  • can we use `map[string] []interface{}` for this purpose? you could typecast things to types that you know will be for each key. – DevX Jun 25 '18 at 13:31
  • Unmarshaling of sendMsg doesn't work in my case while unmarshaling of say works fine – a.dibacco Mar 14 '19 at 21:59
  • Yeah, I should update my answer. sendMsg needs to have exported fields. So type sendMsg struct {User, Msg string} – Stephen Weinberg Mar 15 '19 at 04:20
  • MarshalJSON is a method of RawMessage, not *RawMessage, so there is no reason to use a pointer to RawMessage. Non-pointers are much more convenient for further decoding because they are assignable to []byte. The [Unmarshal example](https://golang.org/pkg/encoding/json/#example_RawMessage_unmarshal) doesn't use a pointer either. The code at https://play.golang.org/p/46JOdjPpVI works as expected (at least with current Go versions). I have updated the answer accordingly. – Peter Mar 02 '20 at 12:05
5

Here is an elegant way to do similar thing. But why do partly JSON unmarshal? That doesn't make sense.

  1. Create your structs for the Chat.
  2. Decode json to the Struct.
  3. Now you can access everything in Struct/Object easily.

Look below at the working code. Copy and paste it.

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

Go playground

Magellan
  • 1,224
  • 9
  • 16
  • 12
    partial unmarshalling is necessary when you're working with structures of several hundred nested fields as temp objects. For example, get json from server, update single fields and post it back on server. – Artem Baskakov Aug 12 '20 at 18:43
  • 4
    Partial unmarshilng is also useful if the json contents are not stable – David Oct 15 '21 at 21:35
2

Further to Stephen Weinberg's answer, I have since implemented a handy tool called iojson, which helps to populate data to an existing object easily as well as encoding the existing object to a JSON string. A iojson middleware is also provided to work with other middlewares. More examples can be found at https://github.com/junhsieh/iojson

Example:

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

Sample output:

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 Hsieh
  • 1,574
  • 13
  • 9
0

Simple flat json to map: For example json:

{
 "was": {
  "username": "userwas",
  "password": "pwdwas",
  "secret": "secretwas"
 },
 "ynb": {
  "username": "userynb",
  "password": "pwdynb",
  "secret": "secterynb"
 }
}

Ok, create struct:

type HostData struct {
    Username string `json:"username"`
    Password string `json:"password"`
    Secret   string `json:"secret"`
}

and main func:

func main() {
    // Example as []byte
    data := []byte(
        `{
        "ynb": {
            "username": "userynb",
            "password": "pwdynb",
            "secret": "secterynb"
        },
        "was":{
            "username": "userwas",
            "password": "pwdwas",
            "secret": "secretwas"
        }
    }`)

    var objmap map[string]json.RawMessage
    err := json.Unmarshal(data, &objmap)
    if err != nil {
        log.Fatal(err)
    }

    var hostDataMap map[string]HostData = make(map[string]HostData)
    // find keys and fill out the map
    for key := range objmap {
        var hd HostData
        err = json.Unmarshal(objmap[key], &hd)
        if err != nil {
            log.Fatal(err)
            return
        }
        hostDataMap[key] = hd
    }

    // Output map:
    fmt.Println(hostDataMap)
    // Output:
    // map[was:{userwas pwdwas secretwas} ynb:{userynb pwdynb secterynb}]
    / or if:
    //fmt.Println(hostDataMap["was"].Username)
    // Output: userwas
}

Full code with back map to json: https://go.dev/play/p/qocCW_rAnAL