December 30, 2020

How to Work with Arbitrary JSON in Go?

Many times you come across JSON which has very arbitrary in structure, Learn how you can handle them to get to in Go

Abstract

Mostly, when you are working with data you know the schema in which you will receive that data, but there come times when you don’t know exactly what will be the structure you will end up receiving. We talk about how you can handle JSON data which are arbitrary in Go.

JSON with a Fixed Structure

When you receive JSON for which you already know the structure, it is pretty easy to work with. You create a struct and unmarshal the bytes to that struct and the JSON package will take care of the rest. Suppose you receive information about today’s dinner in JSON and everyday dinner has the following structure.

type Dinner struct {
    MainCourse   string  `json:"main_course"`
    Beverage     string  `json:"beverage"`
    HasDessert   bool    `json:"has_dessert"`
}

And you receive the following JSON value:

food := []byte(`
{
  "main_course": "Biryani",
  "beverage": "Coca Cola",
  "has_dessert": true
}
`)

You just need to create a variable which can hold the value from JSON.

var dinner Dinner

Eventually, decode the values to the dinner variable like this:

json.Unmarshal(food, &dinner)

Now, you can access those values through the fields on the struct. Lucky you, you have dessert for today 😋.

dinner.MainCourse   // Biryani
dinner.HasDessert   // true

JSON That You Are Not Aware

Let’s diverge our mind from food to shopping list :). Now assume every weekend you go to purchase some grocery and the items in the list keep changing each week. If you have to represent that into a struct, you got no luck!

Like almost everything in Go where so much randomness is evolved, you have to use interfaces. More particularly an empty interface. You can represent your shopping list like this:

var shoppingList interface{}

Because an empty interface can satisfy any type you can use that to do Unmarshaling.

listJSON := []byte(`
{
  "food": {
    "ketchup": "1pc",
    "noodles": "1pc",
    "rice": "2Kg"
  },
  "utensils": {
    "knives": 2,
    "forks": 3
  }
}
`)

json.Unmarshal(listJSON, &shoppingList)

At this point shoppingList looks like this:

shoppingList = map[string]interface{
    "food": map[string]interface{
        "ketchup": "1pc",
        "noodles": "1pc",
        "rice": "2Kg"
    },
    "utensils": map[string]interface{
        "knives": 2,
        "forks": 3
    }
}

To access the data, you can make a type assertion to map[string]interface{}.

list := shoppingList.(map[string]interface{})

After that, you can access the values like a regular map. But for the values that are nested, you need to do a type assertion again.

utensils := list["utensils"].(map[string]interface{})

utensils["forks"] // 3

Conclusion

You can handle arbitrary JSON with the help of interfaces. You just need to pass an empty interface to the Unmarshal function and the JSON data. After a type assertion, you are good to use the arbitrary fields and their values.