2

So I'm trying to get the string representation of a JSON message in Golang. I just want to receive the messagepack encoded JSON, modify some values and send it back.

I haven't found an easy way to do it. Most of the times, I can't know in advance what is the structure of the JSON (apart from having JSON structure), so want I want to do is to receive the binary message. Decode it as a JSON, print it as a string to the standard output, modify the content, convert it to MessagePack again and send it back.

I've been looking at these two packages, but I don't know how to properly use them for a simple task like that:

So I will receive something like this:

DF 00 00 00 01 A7 6D 65 73 73 61 67 65 A3 48 69 21

I want to print this:

{"message": "Hi!"}

Modify the "Hi!":

{"message": "Hello Sir!"}

Send it as messagepack:

DF 00 00 00 01 A7 6D 65 73 73 61 67 65 AA 48 65 6C 6C 6F 20 53 69 72 21

Current Python code I'm trying to port to Golang:

def decode_msgpack(jsonData):
    packedStuff = 0
    for key in jsonData.keys():
        if type(jsonData[key]) is bytes:
            packedStuff += 1
            try:
                jsonData[key] = umsgpack.unpackb(jsonData[key])
            except umsgpack.InvalidStringException:
                try:
                    jsonData[key] = umsgpack.unpackb(jsonData[key], allow_invalid_utf8=True)
                except umsgpack.InsufficientDataException:
                    print("[!] InsufficientDataException")
                    jsonData[key] = base64.b64encode(jsonData[key]).decode('utf-8')
                else:
                    jsonData[key] = base64.b64encode(jsonData[key]).decode('utf-8')

    if packedStuff > 0:
        return decode_msgpack(jsonData)
    else:
        return jsonData

2 Answers2

2

Using the codec library and assuming that {"message": "Hi"} is a map, the code would look something like this.

package main

import (
        "fmt"

        "github.com/ugorji/go/codec"
)

func main() {
        var data []byte
        original := map[string]string{"message": "Hi!"}
        enc := codec.NewEncoderBytes(&data, new(codec.MsgpackHandle))
        if err := enc.Encode(&original); err != nil {
                panic(err)
        }
        fmt.Printf("Encoded: ")
        for _, b := range data {
                fmt.Printf("%X ", b)
        }
        fmt.Printf("\n")
        decoded := make(map[string]string)
        dec := codec.NewDecoderBytes(data, new(codec.MsgpackHandle))
        if err := dec.Decode(&decoded); err != nil {
                panic(err)
        }
        fmt.Printf("Decoded: %v\n", decoded)
        decoded["message"] = "Hello Sir!"
        /* reinitialize the encoder */
        enc = codec.NewEncoderBytes(&data, new(codec.MsgpackHandle))
        if err := enc.Encode(&decoded); err != nil {
                panic(err)
        }
        fmt.Printf("Encoded: ")
        for _, b := range data {
                fmt.Printf("%X ", b)
        }
        fmt.Printf("\n")
}

That said, if you get {"message": "Hi"} as a JSON string, you can use codec to decode the JSON into a map, update the map and then re-encode it as msgpack.

Mad Wombat
  • 14,490
  • 14
  • 73
  • 109
  • Thanks for the answer Mad Wombat! This looks like what I need, but I have a problem when I get something like this: ``{"user": "nobody", "message": {"number": 003, "content": "Hi!"}}`` Check my original question for how I'm doing it on Python3 – CapitanShinChan Nov 29 '18 at 21:22
  • If you know what the JSON structure is going to be, your best bet is to define a custom struct type with the right fields and then use it with codec instead of a map to encode/decode/update things. – Mad Wombat Nov 29 '18 at 21:25
  • I am going to update the code to do custom type instead of a map – Mad Wombat Nov 29 '18 at 21:27
  • The problem is that I don't know all the different types of messages that can be. I will in the future, but for now it's not an option. – CapitanShinChan Nov 29 '18 at 21:33
  • How are you going to update the message if you don't know what fields are in it? – Mad Wombat Nov 29 '18 at 21:35
  • Well, in Python I do something like this (recursively to verify all the dictionaries inside the main dictionary (according to Python names): `if "message" in completeJSON.keys(): completeJSON["message"] = "Test"` There are what we could call "control messages" that don't have the "message" key – CapitanShinChan Nov 29 '18 at 21:41
  • Actually, your example would be perfect if there is a way to do something like what I'm doing in Python: `if type(jsonData[key]) is bytes:` But as far as I know, strings are slices of bytes in Go, so there isn't an easy way to check that :\ (Probably I'm wrong, because my knowledge of Go is very limited, so sorry for that). – CapitanShinChan Nov 29 '18 at 21:53
  • If you do not know what your message structure is, you could use map[string]interface{} to decode your data, but then you would have to have a type to cast the values to if you wanted to manipulate them. – Mad Wombat Nov 29 '18 at 22:00
  • @MadWombat What if one does not know the incoming JSON structure, can we still encode it? – Volatil3 Apr 06 '20 at 17:59
  • @Volatil3 I have to say that in the last two years I got as bit disenchanted with Go language. And lack of generics is one of the reasons (any kind of generics would *really* help in this case). That said, you can always make `map[string]interface{}` and try to parse it yourself – Mad Wombat Apr 30 '20 at 06:27
1

The best way is to first decode it, make your changes via Go structs and then re-encode it.

data := []byte(`{"message": "Hi!"}`)
var p map[string]interface{}

// Decode into Struct
if err := json.Unmarshal(data, &p); err != nil {
    // TODO: handle err
}

// Modify contents
p["message"] = "Hello Sir!"

// Encode from struct
newData, err := json.Marshal(p)
if err != nil {
    // TODO: Handle err
}

fmt.Println(string(newData))
poy
  • 10,063
  • 9
  • 49
  • 74
  • This solution, as far as I understand, requires that I know in advance the format of the JSON, or am I wrong? Because according to your example, "Payload" has to be an Struct with the proper fields to accommodate the decoded message. Maybe I'm misunderstanding it :P – CapitanShinChan Nov 29 '18 at 21:11
  • I guess it depends on how much you know about the formatting. I will update my answer with something a little more dynamic to see if that helps. – poy Nov 29 '18 at 22:44
  • The only I know is that follows the JSON RFC (https://tools.ietf.org/html/rfc8259), and according to messagepack specification, if you have something like: `{"user": "nobody", "message": {"type": "init", "content":"Hi Sir!"}}` first the inside object is converted to its messagepack representation in bytes, and then, the whole JSON. – CapitanShinChan Nov 30 '18 at 00:08