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

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 Persontruct 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.

Try Atatus’s entire features free for 14 days.

Aiswarya S

Aiswarya S

Writes on SaaS products, the newest observability tools in the market, user guides and more.

Monitor your entire software stack

Gain end-to-end visibility of every business transaction and see how each layer of your software stack affects your customer experience.