Go reading a nested JSON without structs the easy way…

Let’s face it sometimes all we need is just to get something out of a JSON response without actually needing to go all the way and create structs to reflect its internals.

The quick and dirty solution to this is a map with a string as a key and an interface{} (think Object in any other language) which can then be accessed with mymap[“key”]

and if needed cast on the fly like this: mymap[“key”].(string)

Problem is this all works super easy and out of the box with a one level json. The issue is when you would need to access something more internal. Let’s take this JSON example:


{
"data": {
"gid": "1140824417098551",
"resource_type": "task",
"created_at": "2019-09-20T07:17:05.161Z",
"modified_at": "2019-09-20T07:17:05.161Z",
"name": "test from postman",
"resource_subtype": "default_task",
"notes": "notes from postman",
"assignee": {
"gid": "1139046208196844",
"resource_type": "user",
"name": "Fabio Russo"
},
"completed": false,
"assignee_status": "inbox",
"completed_at": null,
"due_on": null,
"due_at": null,
"projects": [
{
"gid": "1139411274392065",
"resource_type": "project",
"name": "test"
}
],
"start_on": null,
"tags": [],
}
}

With some omissis this is a typical response when adding a task with the Asana API. Let’s say I just need to print out or store the task id (in this json “gid”).
If I just do it like this:


var response map[string]interface{}
respBody, err := ioutil.ReadAll(resp.Body)
if err = json.Unmarshal(respBody, &response); err != nil {
//error code here
}

Now let’s say I’d love to do something that other languages allow such as:  responseID := response["data"]["gid"]

But that’s asking too much. And if I do it in two steps: responseData := response["data"]

I can’t just do  responseGID := responseData["gid"]

because responseData is an interface{} and not a map[string]interface{} aka a json object we can inspect.
The solution is simple: just cast the responseData into what it really is: a map[string]interface{} because we know it is.

I end up with a response object that I can use like this:

responseData := response["data"].(map[string]interface{})
taskID := responseData["gid"].(string)

Which will totally work as long as we know the json structure in advance (which we would need to even in the case we’d be using structs for the job).