I'm having a tough time moving json over the wire.
There is a jsonb
field in a Postgres db that I need to add to a struct before sending it over the wire as an http response.
If the struct's payload field is a string
, marshaling escapes the json like "{\"id\": \"3aa5fff0-ad91-41b1-84f0-d97f38e0e0f4\", \"user\": 1 }
.
If the struct's payload field is a json.RawMessage
, marshalling escapes the json as (what I imagine to be) a sequence of base64 encoded bytes.
This is the struct that I am marshaling and writing to the http response stream:
type NestJobReturn struct { Status string `json:"status"` Nest json.RawMessage `json:"nest"` }
I build a ret
instance of this struct and print it out. If I use %v
it shows bytes, and %s
shows it as the proper, un-escaped json string:
log("Value of ret.Nest: %v", ret.Nest) // Value of ret.Nest: [123 34 105 ... log("Value of ret.Nest as a string: %s", ret.Nest) // Value of ret.Nest as a string: {"id": "f053...
Marshaling and i/o is done thusly:
js, _ := json.Marshal(ret) res.Header().Set("Content-Type", "application/json") res.Write(js)
The client currently receives the entire message looking kinda like this:
{"status":"ok","nest":"eyJpZCI6ICJmMD..."}
... but the intended value of "nest" is the valid json from my jsonb
column in the database.
Any ideas?
You need to define the nested field as a pointer
to json.RawMessage
, e.g.
type NestJobReturn struct {
Status string `json:"status"`
Nest *json.RawMessage `json:"nest"`
}
jsonStr := `{"type": "Object", "desc": "Simple nested object"}`
raw := json.RawMessage(jsonStr)
ret := NestJobReturn {
Status: "DONE",
Nest: &raw,
}
A working example https://play.golang.org/p/Ju7kgbawss
You need to marshal the pointer to your ret instead of the ret itself
simply changejs, _ := json.Marshal(ret)
tojs, _ := json.Marshal(&ret)
and it should start working.
playground link
Better yet, you can make your struct idiot proof by either going with I Putu Susila's answer or replacing json.RawMessage with this very slightly tweaked version
type RawMessage []byte
// MarshalJSON returns m as the JSON encoding of m.
func (m RawMessage) MarshalJSON() ([]byte, error) {
return m, nil
}
// UnmarshalJSON sets *m to a copy of data.
func (m *RawMessage) UnmarshalJSON(data []byte) error {
if m == nil {
return errors.New("json.RawMessage: UnmarshalJSON on nil pointer")
}
*m = append((*m)[0:0], data...)
return nil
}
Then your struct will always Marshal itself correctly.
playground link
This is the fix that was suggested in the github issue for this quirk (link courtesy of I Putu Susila's comment), but the consensus is that even though this is how RawMessage ought to behave, they can't change it now due to the go standard library's compatibility guarantee. Fortunately, just because they couldn't fix it in the standard library doesn't mean you can't fix it in your own codebase.