I'm trying to demarshal the JSON requests of Google Actions. These have arrays of tagged unions like this:
{
"requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf",
"inputs": [{
"intent": "action.devices.QUERY",
"payload": {
"devices": [{
"id": "123",
"customData": {
"fooValue": 74,
"barValue": true,
"bazValue": "foo"
}
}, {
"id": "456",
"customData": {
"fooValue": 12,
"barValue": false,
"bazValue": "bar"
}
}]
}
}]
}
{
"requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf",
"inputs": [{
"intent": "action.devices.EXECUTE",
"payload": {
"commands": [{
"devices": [{
"id": "123",
"customData": {
"fooValue": 74,
"barValue": true,
"bazValue": "sheepdip"
}
}, {
"id": "456",
"customData": {
"fooValue": 36,
"barValue": false,
"bazValue": "moarsheep"
}
}],
"execution": [{
"command": "action.devices.commands.OnOff",
"params": {
"on": true
}
}]
}]
}
}]
}
etc.
Obviously I can demarshal this to an interface{}
and use fully dynamic type casts and everything to decode it, but Go has decent support for decoding to structs. Is there a way to do this elegantly in Go (like you can in Rust for example)?
I feel like you could almost do it by reading demarshalling to this initially:
type Request struct {
RequestId string
Inputs []struct {
Intent string
Payload interface{}
}
}
However once you have the Payload interface{}
there doesn't seem to be any way to deserialise that into a struct
(other than serialising it and deserialising it again which sucks. Is there any good solution?
Instead of unmarshaling Payload
to an interface{}
you can store it as a json.RawMessage
and then unmarshal it based on the value of Intent. This is shown in the example in the json docs:
https://golang.org/pkg/encoding/json/#example_RawMessage_unmarshal
Using that example with your JSON and struct your code becomes something like this:
type Request struct {
RequestId string
Inputs []struct {
Intent string
Payload json.RawMessage
}
}
var request Request
err := json.Unmarshal(j, &request)
if err != nil {
log.Fatalln("error:", err)
}
for _, input := range request.Inputs {
var payload interface{}
switch input.Intent {
case "action.devices.EXECUTE":
payload = new(Execute)
case "action.devices.QUERY":
payload = new(Query)
}
err := json.Unmarshal(input.Payload, payload)
if err != nil {
log.Fatalln("error:", err)
}
// Do stuff with payload
}