How to use JSON in Golang?
JSON is among the most popular data exchange formats, while Go is mainly used for software development.
In Go, the encoding/json package
provides built-in support for encoding and decoding JSON data.
With this package, Go programs can easily read and write JSON data to communicate with external systems, such as web services or databases, or to exchange data between different components of the same program.
This article will cover the basics of encoding and decoding JSON data in Go, including how to encode Go data structures to JSON and decode JSON data into Go structures.
Additionally, it will cover advanced topics, such as customizing the encoding and decoding process, handling errors, and encoding and decoding large JSON data sets.
Table Of Contents
- Installing JSON Package
- Converting Go Objects to JSON
- Converting JSON data to Go
- Custon Marshaling and Unmarshaling
Installing JSON Package
JSON (JavaScript Object Notation) is a lightweight data-interchange format that uses human-readable text to transmit data objects consisting of attribute–value pairs.
It is used primarily to transmit data between a server and web application.
When you install a Golang compiler, the JSON package is available as one of its components. Go features the marshal and unmarshal functions. This allows JSON objects to be converted to Golang and vice-versa.
Install json package for Golang here.
Converting Go objects to JSON
1. Marshaling
Go has a marshal function for converting its objects to JSON format (encoding). Marshal function syntax is as follows -
func Marshal(v interface{}) ([]byte, error)
The interface accepts any data type - be it an integer, struct, float, string, map, etc. When you add such an interface, marshal returns two values: one - the byte slice
of encoded data and two, an error
.
The default Golang data types for decoding and encoding JSON are as follows:
- bool for JSON booleans
- float64 for JSON numbers
- string for JSON strings
- nil for JSON null
- array as JSON array
- map or struct as JSON Object
Let’s discuss an example to get that a bit more clear for you. We will use a code snippet to encode json from a map data here.
package main
import (
"encoding/json"
"fmt"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
person := Person{Name: "John", Age: 30}
jsonBytes, err := json.Marshal(person)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(jsonBytes))
}
In this example, we define a Person
truct with fields corresponding to the JSON keys. We then define a Person value and use json.Marshal
to encode it into a JSON-encoded byte slice. We can then convert the byte slice to a string and print it out.
You will get an output like this -
The string()
we used here converts Go bytes to JSON string.
Now we will try to encode a bit more complex objects. Since in everyday transactions, we will mostly be dealing with data like product lists, details of clients, other nested data, etc.
type Seller struct {
Id int
Name string
CountryCode string
}
type Product struct {
Id int
Name string
Seller Seller
Price int
}
func main() {
products := []Product{
Product {
Id: 50,
Name: "Writing Book",
Seller: Seller {1, "ABC Company", "US"},
Price: 100,
},
Product {
Id: 51,
Name: "Kettle",
Seller: Seller {20, "John Store", "DE"},
Price: 500,
},
Product {
Id: 52,
Name: "Jug",
Seller: Seller {20, "John Store", "DE"},
Price: 200,
},
}
bytes, _ := json.Marshal(products)
fmt.Println(string(bytes))
}
The above code shows a product list containing three articles with all the items placed within the product slice. The product()
struct has a nested seller()
struct. Now, marshaling them will give us the following output:
[
{
"Id": 50,
"Name": "Writing Book",
"Seller": {
"Id": 1,
"Name": "ABC Company",
"CountryCode": "US"
},
"Price": 100
},
{
"Id": 51,
"Name": "Kettle",
"Seller": {
"Id": 20,
"Name": "John Store",
"CountryCode": "DE"
},
"Price": 500
},
{
"Id": 52,
"Name": "Jug",
"Seller": {
"Id": 20,
"Name": "John Store",
"CountryCode": "DE"
},
"Price": 200
}
]
When you look at this output, you realize that json package does an amazing job of encoding Go objects into JSON format (even complex ones). But they are cumbersome and unreadable. Also, json key’s always start with capital letters!
So, let’s learn how to rename json fields and make json output pretty and readable.
2. Renaming JSON Fields
The dependence of json package on Capitalized first letters for declarations returns uppercase letters for JSON keys as well. To customize lowercase letter keys, use struct tags (This is totally optional).
Adding a struct tag to the above-mentioned code will give us something like this -
type Seller struct {
Id int `json:"id"`
Name string `json:"name"`
CountryCode string `json:"country_code"`
}
type Product struct {
Id int `json:"id"`
Name string `json:"name"`
Seller Seller `json:"seller"`
Price int `json:"price"`
}
func main() {
book := Product{
Id: 50,
Name: "Writing Book",
Seller: Seller {1, "ABC Company", "US"},
Price: 100,
}
bytes, _ := json.Marshal(book)
fmt.Println(string(bytes))
}
Output:
{
"id": 50,
"name": "Writing Book",
"seller": {
"id": 1,
"name": "ABC Company",
"country_code": "US"
},
"price": 100
}
3. JSON Pretty-print
Marshal function generally creates JSON data without any indentation. But using json.MarshalIndent
function, we can prettify JSON data this way:
package main
import (
"encoding/json"
"fmt"
"log"
)
func formatJson(custInfo interface {}) (string, error) {
val, err := json.MarshalIndent(custInfo, "", " ")
if err != nil {
return "", err
}
return string(val), nil
}
type CustomerInfo struct {
FirstName string `json:"firstname"`
MiddleName string `json:"middlename"`
LastName string `json:"lastname"`
Country []string `json:"country"`
}
func main() {
customer := CustomerInfo {
FirstName: "Mary",
MiddleName: "Elizabeth",
LastName: "Smith",
Country: []string{"Spain", "Madrid" },
}
res, err := formatJson(customer)
if err != nil {
log.Fatal(err)
}
fmt.Println(res)
}
In this example, we use json.MarshalIndent
instead of json.Marshal
. The first argument is the value we want to encode, the second argument is a prefix to add to each line of the output, and the third argument is the indentation for each nesting level.
Output:
4. Omit Empty Codes
Struct tags can be used to omit specific unwanted fields. Using json:”-”
as tag or by inserting ,omitempty
as the name inside struct tag we can stop the unwanted fields from being encoded.
Example:
package main
import (
"fmt"
"encoding/json"
)
type Seller struct {
Id int `json:"id"`
Name string `json:"name"`
CountryCode string `json:"country_code,omitempty"`
}
type Product struct {
Id int `json:"-"`
Name string `json:"name"`
Seller Seller `json:"seller"`
Price int `json:"price"`
}
func main() {
products := []Product{
Product {
Id: 50,
Name: "Writing Book",
Seller: Seller {Id: 1, Name: "ABC Company", CountryCode: "US"},
Price: 100,
},
Product {
Id: 51,
Name: "Kettle",
Seller: Seller {Id: 20, Name: "John Store"},
Price: 500,
},
}
bytes, _ := json.MarshalIndent(products, "", "\t")
fmt.Println(string(bytes))
}
Output:
Converting JSON data to Go
1. Unmarshaling
Unmarshaling is the process of decoding json documents and converting them to Golang. Unmarshal function is used for this purpose.
Syntax
func Unmarshal(data []byte, v interface{}) error
You must notice that this function utilizes a byte slice and an empty interface. Also, the error in the syntax will output an error message if the codes for decoding contain any faults.
Here is an example of how to unmarshal a JSON object into a Go struct:
package main
import (
"encoding/json"
"fmt"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
jsonStr := `{"name":"John","age":30}`
var person Person
err := json.Unmarshal([]byte(jsonStr), &person)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(person.Name, person.Age)
}
The Person
struct is defined with two fields: Name of type string and Age of type int. The JSON struct tag is used to specify the corresponding keys in the JSON object for each field.
In this example, we define a Person
struct with fields corresponding to the JSON keys. The JSON package maps the JSON keys to struct field names using the struct tag json:"fieldname"
. We then define a JSON-encoded string and use json.Unmarshal to decode it into a Person value.
If the JSON object is an array of objects, we can decode it into a slice of structs like this:
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
jsonStr := `[{"name":"John","age":30},{"name":"Jane","age":25}]`
var people []Person
err := json.Unmarshal([]byte(jsonStr), &people)
if err != nil {
fmt.Println(err)
return
}
for _, person := range people {
fmt.Println(person.Name, person.Age)
}
}
In this example, we define a slice of Person
structs and use json.Unmarshal to decode a JSON array of objects into a slice of Person
values. We can then loop over the slice to print out each person's name and age.
Custom Marshaling and Unmarshaling
In Go, you can customize the marshaling and unmarshaling of a struct to and from JSON by implementing the json.Marshaler
and json.Unmarshaler
interfaces. These interfaces define two methods, MarshalJSON
and UnmarshalJSON
, which allow you to provide your own implementation for JSON encoding and decoding.
To demonstrate how to use custom marshaling and unmarshaling in Go, let's start with an example struct:
type Person struct {
Name string
Age int
Address Address
}
type Address struct {
Street string
City string
Country string
}
Here's an example of how to implement custom marshaling and unmarshaling for this struct.
1. Custom Marshaling
func (p Person) MarshalJSON() ([]byte, error) {
address := map[string]string{
"street": p.Address.Street,
"city": p.Address.City,
"country": p.Address.Country,
}
person := map[string]interface{}{
"name": p.Name,
"age": p.Age,
"address": address,
}
return json.Marshal(person)
}
In this implementation, we first create a map to hold the Address data, then another map to hold the Person
data. We then populate both maps and use json.Marshal
to encode the final result.
2. Custom Unmarshaling
func (p *Person) UnmarshalJSON(data []byte) error {
person := struct {
Name string `json:"name"`
Age int `json:"age"`
Address Address `json:"address"`
}{}
if err := json.Unmarshal(data, &person); err != nil {
return err
}
p.Name = person.Name
p.Age = person.Age
p.Address = person.Address
return nil
}
In this implementation, we first define an anonymous struct with fields corresponding to the JSON keys. We then unmarshal the JSON data into this struct and finally set the fields of the Person struct using the data from the anonymous struct.
With these implementations in place, we can now use json.Marshal
and json.Unmarshal
to encode and decode Person
values, and the custom methods we defined will be used instead of the default ones.
Here's an example of how to use these methods:
func main() {
person := Person{
Name: "John",
Age: 30,
Address: Address{
Street: "123 Main St",
City: "Anytown",
Country: "USA",
},
}
jsonBytes, err := json.Marshal(person)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(jsonBytes))
var decodedPerson Person
if err := json.Unmarshal(jsonBytes, &decodedPerson); err != nil {
fmt.Println(err)
return
}
fmt.Println(decodedPerson)
}
In this example, we first define a Person
value and use json.Marshal
to encode it into a JSON-encoded byte slice. We can then convert the byte slice to a string and print it out.
We then use json.Unmarshal
to decode the JSON data back into a Person
struct. If the unmarshaling process succeeds, we print out the resulting Person
value.
The output of the code will be:
{"name":"John","age":30,"address":{"street":"123 Main St","city":"Anytown","country":"USA"}}
{John 30 {123 Main St Anytown USA}}
Conclusion
JSON is a language-independent data exchange format. This feature attracts many developers to create and store data in JSON formats.
On the other hand, Golang is a statically-typed high-level programming language used chiefly for writing software.
This tutorial explains how JSON data can be adopted in Go and vice-versa. I hope that the examples given in here has cleared much of your doubts regarding encoding and decoding in Golang.
Monitor your Go Applications with Atatus
Atatus provides developers with insights into the performance of their Golang applications. By tracking requests and backend performance, Atatus helps identify bottlenecks in the application and enables developers to diagnose and fix errors more efficiently.
Go performance monitoring captures errors and exceptions that occur in Golang applications and provides a detailed stack trace, enabling developers to quickly pinpoint the exact location of the error and address it.
We provide flexible alerting options, such as email, Slack, PagerDuty, or webhooks, to notify developers of Golang errors and exceptions in real-time. This enables developers to address issues promptly and minimize any negative impact on the end-user experience.
#1 Solution for Logs, Traces & Metrics
APM
Kubernetes
Logs
Synthetics
RUM
Serverless
Security
More