Learn Go: Marshal & Unmarshal JSON in Golang #21

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

Gopher

Lets go!!

Published · Jan 31 2020



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

. . .

Marshalling JSON

Let’s start off by taking a look at how we can Marshal JSON in Go. Marshalling effectively allows us to convert our Go objects into JSON strings.


A Simple Example

Let’s have a look at a simple example of this. Say we had a Book struct defined in our Go code.
package main

import (
"fmt"
)

type Book struct {
Title string `json:"title"`
Author string `json:"author"`
}

func main() {
// an instance of our Book struct
book := Book{Title: "Learning Go", Author: "Gopher Go"}
fmt.Println("book: ", book)

// To print key:value use %+v
fmt.Printf("book: %+v", book)
}


If we wanted to convert an instance of a Book struct into JSON, we could do that by using the encoding/json go package.
byteArray, err := json.Marshal(book) if err != nil { fmt.Println(err) } fmt.Println(string(byteArray))

Below program Marshal simple struct:
package main

import (
"encoding/json"
"fmt"
)

type Book struct {
Title string `json:"title"`
Author string `json:"author"`
}

func main() {
// an instance of our Book struct
book := Book{Title: "Learning Go", Author: "Gopher Go"}

byteArray, err := json.Marshal(book)
if err != nil {
fmt.Println(err)
}

fmt.Println(string(byteArray))
}


Now, when we run this within our program, we should see the following output:
{"title":"Learning Go","author":"Gopher Go"}



Advanced Example - Nested Structs

Now that we’ve got the basics of Marshalling down, let’s take a look at a more complex example that features nested structs.
package main

import (
"fmt"
)

type Book struct {
Title string `json:"title"`
Author Author `json:"author"`
}

type Author struct {
Sales int `json:"book_sales"`
Age int `json:"age"`
Developer bool `json:"is_developer"`
}

func main() {
author := Author{Sales: 3, Age: 25, Developer: true}
book := Book{Title: "Learning Concurrency in Python", Author: author}

fmt.Printf("book: %+v", book)
}


So, we’ve defined a more complex struct which features a nested struct this time. Within the definition of the struct, we’ve defined the JSON tags that map the fields of our structs directly to the fields in our marshalled JSON.
byteArray, err := json.Marshal(book) if err != nil { fmt.Println(err) } fmt.Println(string(byteArray))

Below program Marshal nested struct:
package main

import (
"encoding/json"
"fmt"
)

type Book struct {
Title string `json:"title"`
Author Author `json:"author"`
}

type Author struct {
Sales int `json:"book_sales"`
Age int `json:"age"`
Developer bool `json:"is_developer"`
}

func main() {
author := Author{Sales: 3, Age: 25, Developer: true}
book := Book{Title: "Learning Concurrency in Python", Author: author}

byteArray, err := json.Marshal(book)
if err != nil {
fmt.Println(err)
}

fmt.Println(string(byteArray))
}


Now, when we run this within our program, we should see the following output:
{"title":"Learning Concurrency in Python","author":{"book_sales":3,"age":25,"is_developer":true}}


Indentation

If you want to print out your JSON in a way that it more readable, then you can try using the json.MarshalIndent() function instead of the regular json.Marshal() function.
author := Author{Sales: 3, Age: 25, Developer: true} book := Book{Title: "Learning Concurrency in Python", Author: author} byteArray, err := json.MarshalIndent(book, "", " ") if err != nil { fmt.Println(err) }

You’ll notice that we’ve passed in two additional arguments to MarshalIndent, these are the prefix string and the indent string. You can add a deeper indentation by changing the length of the indent string.
package main

import (
"encoding/json"
"fmt"
)

type Book struct {
Title string `json:"title"`
Author Author `json:"author"`
}

type Author struct {
Sales int `json:"book_sales"`
Age int `json:"age"`
Developer bool `json:"is_developer"`
}

func main() {
author := Author{Sales: 3, Age: 25, Developer: true}
book := Book{Title: "Learning Concurrency in Python", Author: author}

byteArray, err := json.MarshalIndent(book, "", " ")
if err != nil {
fmt.Println(err)
}

fmt.Println(string(byteArray))
}


When we now go to run this, we should see that our outputted JSON string looks a lot nicer:
{ "title": "Learning Concurrency in Python", "author": { "book_sales": 3, "age": 25, "is_developer": true } }

. . .

Unmarshalling JSON

Now that we’ve covered marshalling our structs into JSON, let’s try and go the other way. We want to be able to take in a JSON string and unmarshal that string into a struct that we can then work with just as we would a normal struct in Go.

You’ll typically find yourself implementing just this if you have a Go service which consumes other APIs, as these APIs that you are interacting with will typically return responses as JSON strings.

In this example we’ll take a JSON string that comes from a small Battery sensor and we’ll attempt to unmarshal this JSON string into a struct.
{ "name": "battery sensor", "capacity": 40, "time": "2019-01-21T19:07:28Z" }

Now the first thing we’ll want to do for this particular example is define a struct that has the same fields as our JSON string.
package main

import (
"fmt"
)

type SensorReading struct {
Name string `json:"name"`
Capacity int `json:"capacity"`
Time string `json:"time"`
}

func main() {
jsonString := `{"name": "battery sensor", "capacity": 40, "time": "2019-01-21T19:07:28Z"}`

fmt.Println(jsonString)
}


You’ll notice that, for each of our key-value pairs in our JSON string, we’ve defined a field on our SensorReading struct which matches that key name. We’ve also added tags to each of our fields within our struct that look like this: json:"KEY". These tags are there to indicate which JSON key matches to which struct field value.

Now that we’ve defined a struct, we can proceed with the task of unmarshalling our JSON string into a struct using the Unmarshal function:
jsonString := `{"name": "battery sensor", "capacity": 40, "time": "2019-01-21T19:07:28Z"}` var reading SensorReading err := json.Unmarshal([]byte(jsonString), &reading) fmt.Printf("%+v\n", reading)

You’ll notice that we’ve cast our jsonString which contains our JSON, to a byte array when passing it into our json.Unmarshal function call. We’ve also passed in a reference to the struct that we want to unmarshal our JSON string into with &reading.
package main

import (
"encoding/json"
"fmt"
)

type SensorReading struct {
Name string `json:"name"`
Capacity int `json:"capacity"`
Time string `json:"time"`
}

func main() {
jsonString := `{"name": "battery sensor", "capacity": 40, "time": "2019-01-21T19:07:28Z"}`

var reading SensorReading
err := json.Unmarshal([]byte(jsonString), &reading)
if err != nil {
fmt.Println(err)
}
fmt.Printf("%+v\n", reading)
}


When we then go to run this, we should see the following output:
{Name:battery sensor Capacity:40 Time:2019-01-21T19:07:28Z}

We’ve been able to successfully unmarshal our JSON string into a struct with minimal fuss! We can now work with that populated struct just as we normally would in our Go programs.
. . .

Unstructured Data

Sometimes, you might not have knowledge of the structure of the JSON string that you are reading. You may not be able to generate a pre-defined struct that you can subsequently unmarshal your JSON into.

If this is the case, then there is an alternative approach that you can utilize which is to use map[string]interface{} as the type you unmarshal into.
str := `{"name": "battery sensor", "capacity": 40, "time": "2019-01-21T19:07:28Z"}` var reading map[string]interface{} err = json.Unmarshal([]byte(str), &reading) fmt.Printf("%+v\n", reading)

Here, we have modified our existing SensorReading code from above and we’ve changed the type of reading to this new map[string]interface{} type.

When we now go to run this, we should see that our JSON string has been successfully converted into a map of strings and elements:
package main

import (
"encoding/json"
"fmt"
)

func main() {
jsonString := `{"name": "battery sensor", "capacity": 40, "time": "2019-01-21T19:07:28Z"}`

var reading map[string]interface{}
err := json.Unmarshal([]byte(jsonString), &reading)
if err != nil {
fmt.Println(err)
}
fmt.Printf("%+v\n", reading)
}


This can be a handy tip if you are in a tight squeeze, however, if you do know the structure of your JSON then it is highly recommended that you define the structs explicitly.
. . .

Ignoring Empty Fields

In some cases, we would want to ignore a field in our JSON output, if its value is empty. We can use the omitempty property for this purpose.

For example, if the Author field is missing for the Book object, the key will not appear in the encoded JSON string incase we set this property:
package main

import (
"encoding/json"
"fmt"
)

type Book struct {
Title string `json:"title,omitempty"`
Author string `json:"author,omitempty"`
}

func main() {
// an instance of our Book struct
book := Book{Title: "Learning Go", Author: ""}

byteArray, err := json.Marshal(book)
if err != nil {
fmt.Println(err)
}

fmt.Println(string(byteArray))
}

This will give us the output:
{"title":"Learning Go"}

. . .

Conclusion

Hopefully, you enjoyed this tutorial and found it useful, if you have any suggestions as to how I can make this better, I’d love to hear them in the suggestions box below!
. . .

. . .
References:
Snehal Kumar

Dec 5 2019

Snehal Kumar

Jul 28 2019

Write your response...

On a mission to build Next-Gen Community Platform for Developers