{ Soham Kamani }

AboutBlog GithubTwitter

How to Parse JSON in Golang (With Examples)

(Updated on )

In this post, we will learn how to work with JSON in Go, in the simplest way possible.

We will look at different types of data that we encounter in Go, from structured data like structs, arrays, and slices, to unstructured data like maps and empty interfaces.

JSON is used as the de-facto standard for data serialization, and by the end of this post, you’ll get familiar with how to parse and encode JSON in Go

Parsing JSON strings

The json package provided in Go’s standard library provides us with all the functionality we need. For any JSON string, the standard way to parse it is:

import "encoding/json"
//...

// ... 
myJsonString := `{"some":"json"}`

// `&myStoredVariable` is the address of the variable we want to store our
// parsed data in
json.Unmarshal([]byte(myJsonString), &myStoredVariable)
//...

What we will discuss in this post, is the different options you have for the type of myStoredVariable, and when you should use them.

There are two types of data you will encounter when working with JSON:

  1. Structured data
  2. Unstructured data

Structured data

Since this is much easier, let’s deal with it first. This is the sort of data where you know the structure beforehand. For example, let’s say you have a bird object, where each bird has a species field and a description field :

{
  "species": "pigeon",
  "decription": "likes to perch on rocks"
}

To work with this kind of data, create a struct that mirrors the data you want to parse. In our case, we will create a bird struct which has a Species and Description attribute:

type Bird struct {
  Species string
  Description string
}

And unmarshal it as follows:

birdJson := `{"species": "pigeon","description": "likes to perch on rocks"}`
var bird Bird	
json.Unmarshal([]byte(birdJson), &bird)
fmt.Printf("Species: %s, Description: %s", bird.Species, bird.Description)
//Species: pigeon, Description: likes to perch on rocks

Try it here

By convention, Go uses the same title cased attribute names as are present in the case insensitive JSON properties. So the Species attribute in our Bird struct will map to the species, or Species or sPeCiEs JSON property.

JSON Arrays

So what happens when you have an array of birds?

[
  {
    "species": "pigeon",
    "decription": "likes to perch on rocks"
  },
  {
    "species":"eagle",
    "description":"bird of prey"
  }
]

Since each element of the array is actually a Bird, you can actually unmarshal this, by just creating an array of birds :

birdJson := `[{"species":"pigeon","decription":"likes to perch on rocks"},{"species":"eagle","description":"bird of prey"}]`
var birds []Bird
json.Unmarshal([]byte(birdJson), &birds)
fmt.Printf("Birds : %+v", birds)
//Birds : [{Species:pigeon Description:} {Species:eagle Description:bird of prey}]

Try it here

Embedded objects

Now, consider the case when you have a property called Dimensions, that measures the Height and Length of the bird in question:

{
  "species": "pigeon",
  "decription": "likes to perch on rocks"
  "dimensions": {
    "height": 24,
    "width": 10
  }
}

As with our previous examples, we need to mirror the structure of the object in question in our Go code. To add an embedded dimensions object, lets create a dimensions struct :

type Dimensions struct {
  Height int
  Width int
}

Now, the Bird struct will include a Dimensions field:

type Bird struct {
  Species string
  Description string
  Dimensions Dimensions
}

And can be unmarshaled using the same method as before:

birdJson := `{"species":"pigeon","description":"likes to perch on rocks", "dimensions":{"height":24,"width":10}}`
var birds Bird
json.Unmarshal([]byte(birdJson), &birds)
fmt.Printf(bird)
// {pigeon likes to perch on rocks {24 10}}

Try it here

Primitives

We mostly deal with complex objects or arrays when working with JSON, but it’s easy to forget that data like 3, 3.1412 and "birds" are also perfectly valid JSON strings. We can unmarshal these values to their corresponding data type in Go:

numberJson := "3"
floatJson := "3.1412"
stringJson := `"bird"`

var n int
var pi float64
var str string

json.Unmarshal([]byte(numberJson), &n)
fmt.Println(n)
// 3

json.Unmarshal([]byte(floatJson), &pi)
fmt.Println(pi)
// 3.1412

json.Unmarshal([]byte(stringJson), &str)
fmt.Println(str)
// bird

Try it out

Custom attribute names

I mentioned earlier that Go uses convention to ascertain the attribute name it should map a property to. Many times though, you want a different attribute name than the one provided in your JSON data.

{
  "birdType": "pigeon",
  "what it does": "likes to perch on rocks"
}

In the JSON data above, I would prefer birdType to remain as the Species attribute in my Go code. It is also not possible for me to provide a suitable attribute name for a key like "what it does".

To solve this, we make use of struct field tags:

type Bird struct {
  Species string `json:"birdType"`
  Description string `json:"what it does"`
}

Now, we can explicitly tell our code which JSON property to map to which attribute.

birdJson := `{"birdType": "pigeon","what it does": "likes to perch on rocks"}`
var bird Bird
json.Unmarshal([]byte(birdJson), &bird)
fmt.Println(bird)
// {pigeon likes to perch on rocks}

Try it here

Unstructured data (using maps)

If you have data whose structure or property names you are not certain of, you cannot use structs to unmarshal your data. Instead you can use maps. Consider some JSON of the form:

{
  "birds": {
    "pigeon":"likes to perch on rocks",
    "eagle":"bird of prey"
  },
  "animals": "none"
}

There is no struct we can build to represent the above data for all cases since the keys corresponding to the birds can change, which will change the structure.

To deal with this case we create a map of strings to empty interfaces:

birdJson := `{"birds":{"pigeon":"likes to perch on rocks","eagle":"bird of prey"},"animals":"none"}`
var result map[string]interface{}
json.Unmarshal([]byte(birdJson), &result)

// The object stored in the "birds" key is also stored as 
// a map[string]interface{} type, and its type is asserted from
// the interface{} type
birds := result["birds"].(map[string]interface{})

for key, value := range birds {
  // Each value is an interface{} type, that is type asserted as a string
  fmt.Println(key, value.(string))
}

Try it here

Each string corresponds to a JSON property, and its mapped interface{} type corresponds to the value, which can be of any type. The type is asserted from this interface{} type as is needed in the code. These maps can be iterated over, so a variable number of keys can be handled by a simple for loop.

Encoding JSON from Go data

The same rules that are used to decode a JSON string can be applied to encoding as well.

Encoding structured data

Let’s consider our Go struct from before, and see the code required to get a JSON string from data of its type:

package main

import (
	"encoding/json"
	"fmt"
)

// The same json tags will be used to encode data into JSON
type Bird struct {
	Species     string `json:"birdType"`
	Description string `json:"what it does"`
}

func main() {
	pigeon := &Bird{
		Species:     "Pigeon",
		Description: "likes to eat seed",
	}

	// we can use the json.Marhal function to
	// encode the pigeon variable to a JSON string
	data, _ := json.Marshal(pigeon)
	// data is the JSON string represented as bytes
	// the second parameter here is the error, which we
	// are ignoring for now, but which you should ideally handle
	// in production grade code

	// to print the data, we can typecast it to a string
	fmt.Println(string(data))
}

This will give the output:

{"birdType":"Pigeon","what it does":"likes to eat seed"}

Try it out

Encoding Arrays and Slices

This isn’t much different from structs. We just need to pass the slice or array to the json.Marshal function, and it will encode data like you expect:

pigeon := &Bird{
  Species:     "Pigeon",
  Description: "likes to eat seed",
}

// Now we pass a slice of two pigeons
data, _ := json.Marshal([]*Bird{pigeon, pigeon})
fmt.Println(string(data))

This will give the output:

[{"birdType":"Pigeon","what it does":"likes to eat seed"},{"birdType":"Pigeon","what it does":"likes to eat seed"}]

Try it out

Encoding maps

We can use maps to encode unstructured data.

The keys of the map need to be strings, or a type that can convert to strings. The values can be any serializable type.

package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	// The keys need to be strings, the values can be
	// any serializable value
	birdData := map[string]interface{}{
		"birdSounds": map[string]string{
			"pigeon": "coo",
			"eagle":  "squak",
		},
		"total birds": 2,
	}

	// JSON encoding is done the same way as before	
	data, _ := json.Marshal(birdData)
	fmt.Println(string(data))
}

Output (Try it out):

{"birdSounds":{"eagle":"squak","pigeon":"coo"},"total birds":2}

What to use (Structs vs maps)

As a general rule of thumb, if you can use structs to represent your JSON data, you should use them. The only good reason to use maps would be if it were not possible to use structs due to the uncertain nature of the keys or values in the data.

If we use maps, we will either need each of the keys to have the same data type, or use a generic type and convert it later.


Like what I write? Join my mailing list, and I'll let you know whenever I write another post

Comments

Soham Kamani

Written by Soham Kamani, an author,and a full-stack developer who has extensive experience in the JavaScript ecosystem, and building large scale applications in Go. He is an open source enthusiast and an avid blogger. You should follow him on Twitter