author
Kevin Kelche

Getting Started with JSON Parsing in Golang: A Comprehensive Guide


Introduction

JSON (JavaScript Object Notation) is a simple data-interchange format. Humans can read and write it, and machines can parse and generate it with ease. JSON is a subset of JavaScript that is commonly used for asynchronous browser-server communication and storage, including as a replacement for XML in some AJAX-style systems. JSON’s simplicity and ubiquity across the web, as well as its support by the majority of programming languages, make it an excellent choice for data interchange in modern applications.

In Golang, JSON is a first-class citizen (of course). It is used as a data interchange format in web applications, microservices, and other types of applications. In this article, we will learn how to work with JSON in Golang. We will cover encoding and decoding JSON, the encoding/json package, marshaling, error handling, custom data types, and more.

encoding/json Package

The encoding/json package provides functions and types for encoding and decoding JSON data. It is a standard library package and is included in the Golang distribution. It is the most commonly used package for working with JSON in Golang.

The encoding/json package is simple and easy to use, making it a popular choice for working with JSON data in Go applications. It provides a robust set of features for working with JSON data, while also being flexible and customizable enough to handle a wide range of use cases.

Encoding and Decoding JSON in Golang

Encoding involves converting Golang’s data structures such as structs, slices, and maps into JSON data. Decoding involves converting JSON data into Golang’s data structures. The encoding/json package provides functions for encoding and decoding JSON data.

Encoding JSON

The process of encoding JSON data in Golang is called marshaling. The Marshal function is used to marshal Golang data structures into JSON data.

The Marshal function takes a Golang data structure as an argument and returns a byte slice and an error. The byte slice contains the JSON data, and the returned error is nil if the marshaling was successful.

Let’s look at an example of encoding JSON data in Golang.

main.go
package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    type Student struct {
        Name string
        Age  int
    }

    student := Student{
        Name: "John",
        Age:  21,
    }

    json, err := json.Marshal(student)

    if err != nil {
        fmt.Println(err)
    }

    fmt.Println(string(json))
}

Copied!

The Marshal function takes a Student struct as an argument and returns a byte slice and an error. The byte slice the JSON data, which is then converted to a string using the string function.

The output of the program is:

output
{"Name":"John","Age":21}

Copied!

The Marshal function can also be used to marshal a slice of structs and a map of structs.

main.go
package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    type Student struct {
        Name string
        Age  int
    }

    fmt.Println("Marshaling a slice of structs")

    students := []Student{
        {
            Name: "John",
            Age:  21,
        },
        {
            Name: "Jane",
            Age:  22,
        },
    }

    jsonSlice, err := json.Marshal(students)

    if err != nil {
        fmt.Println(err)
    }

    fmt.Println(string(jsonSlice))

    fmt.Println("Marshaling a map of structs")

    studentsMap := map[string]Student{
        "John": {
            Name: "John",
            Age:  21,
        },
        "Jane": {
            Name: "Jane",
            Age:  22,
        },
    }

    jsonMap, err := json.Marshal(studentsMap)

    if err != nil {
        fmt.Println(err)
    }

    fmt.Println(string(jsonMap))
}

Copied!

The output of the program is:

output
Marshaling a slice of structs
[{"Name":"John","Age":21},{"Name":"Jane","Age":22}]
Marshaling a map of structs
{"John":{"Name":"John","Age":21},"Jane":{"Name":"Jane","Age":22}}

Copied!

Decoding JSON

The process of decoding JSON data in Golang is called unmarshaling. The Unmarshal function is used to unmarshal JSON data into Golang data structures.

The Unmarshal function takes a byte slice and a pointer to a Golang data structure as arguments and returns an error. The byte slice contains the JSON data, and the returned error is nil if the unmarshaling was successful.

Let’s look at an example of decoding JSON data in Golang.

main.go
package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    type Student struct{
        Name string
        Age  int
    }

    jsonData := []byte(`{"Name":"John","Age":21}`)

    var student Student

    err := json.Unmarshal(jsonData, &student)

    if err != nil {
        fmt.Println(err)
    }

    fmt.Println(student)
}

Copied!

The Unmarshal function takes a byte slice of JSON data and a pointer to a Student struct as arguments and returns an error. The returned error is nil if the unmarshaling was successful.

The output of the program is:

output
{John 21}

Copied!

The Unmarshal function can also be used to unmarshal JSON data into a slice of structs and a map of structs.

main.go
package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    type Student struct {
        Name string
        Age  int
    }

    fmt.Println("Unmarshaling a slice of structs")

    jsonDataSlice := []byte(`[{"Name":"John","Age":21},{"Name":"Jane","Age":22}]`)
    var students []Student

    err := json.Unmarshal(jsonDataSlice, &students)

    if err != nil {
        fmt.Println(err)
    }

    fmt.Println(students)

    fmt.Println("Unmarshaling a map of structs")

    jsonDataMap := []byte(`{"John":{"Name":"John","Age":21},"Jane":{"Name":"Jane","Age":22}}`)

    var studentsMap map[string]Student

    err = json.Unmarshal(jsonDataMap, &studentsMap)

    if err != nil {
        fmt.Println(err)
    }

    fmt.Println(studentsMap)
}

Copied!

The output of the program is:

output
Unmarshaling a slice of structs
[{John 21} {Jane 22}]
Unmarshaling a map of structs
map[John:{John 21} Jane:{Jane 22}]

Copied!

MarshalIndent Function

The MarshalIndent function is used to marshal a Golang data structure into JSON data with a specified indentation.

The MarshalIndent function takes a Golang data structure, a prefix string, and an indentation string as arguments and returns a byte slice and an error.

Let’s look at an example of using the MarshalIndent function.

main.go
package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    type Student struct {
        Name string
        Age  int
    }

    student := Student{
        Name: "John",
        Age:  21,
    }

    jsonData, err := json.MarshalIndent(student, "", "    ")

    if err != nil {
        fmt.Println(err)
    }

    fmt.Println(string(jsonData))
}

Copied!

The MarshalIndent function takes a Student struct, an empty string, and a string containing four spaces as arguments and returns a byte slice and an error. The returned error is nil if the marshaling was successful.

The output of the program is:

output
{
    "Name": "John",
    "Age": 21
}

Copied!

Unmarshal vs NewDecoder

json.Unmarshal and json.NewDecoder are two functions that can be used to unmarshal JSON data into a Golang data structure. They are both found in the encoding/json package. However, there are some differences between them. Let’s look at them.

  1. Input source:
    json.Unmarshal takes a byte slice of JSON data as an argument. json.NewDecoder takes an io.Reader as an argument. This means that json.Unmarshal can only be used to unmarshal JSON data from a byte slice. json.NewDecoder can be used to unmarshal JSON data from a byte slice, a file, a network connection, etc.
  2. Output destination:
    json.Unmarshal requires a pointer to a Golang data structure as an argument to store the decoded JSON data. json.NewDecoder on the other hand, returns a json.Decoder struct that can be used to decode individual JSON values.
  3. Performance:
    json.Unmarshal reads the entire JSON data into memory, decodes it, and stores it in the Golang data structure. This is a one-time operation that may be slower than json.NewDecoder if the JSON data is large. json.NewDecoder on the other hand, decodes the JSON data in chunks and stores it in the Golang data structure. This is a streaming operation that may be faster than json.Unmarshal if the JSON data is large, but for small JSON data, it may be slower due to the overhead of creating json.Decoder struct and reading the JSON data one by one.
  4. Functionality:
    json.Unmarshal provides an easy way to unmarshal complete JSON strings into a Golang data structure. json.NewDecoder on the other hand, provides a more flexible and efficient way to read values from a JSON stream and unmarshal them into a Golang data structure, but requires more code to be written.

Let’s look at an example of using json.NewDecoder to unmarshal JSON data into a Golang data structure.

main.go
package main

import (
    "encoding/json"
    "fmt"
    "strings"
)


func main() {
    type Student struct{
        Name string
        Age int
    }

    jsonStr := `{"Name":"John","Age":21}`

    decoder := json.NewDecoder(strings.NewReader(jsonStr))

    var student Student

    err := decoder.Decode(&student)

    if err != nil {
        fmt.Println(err)
    }

    fmt.Println(student)
}

Copied!

In conclusion, the choice between json.Unmarshal and json.NewDecoder depends on the use case. If you are dealing with small JSON data, json.Unmarshal is the best choice. If you are dealing with large JSON data, json.NewDecoder is the best choice.

Decoding From a File

To decode JSON data from a file, we can use the NewDecoder function. First we need to open the file using ioutil package or os package. Then we can use the NewDecoder function to create a Decoder object. The Decoder object has a Decode method that can be used to decode JSON data from a file into a Golang data structure.

Example json file:

data.json
[
  {
    "Name": "John",
    "Age": 21
  },
  {
    "Name": "Jane",
    "Age": 22
  }
]

Copied!

Let’s look at an example of decoding JSON data from a file.

main.go
package main

import (
  "encoding/json"
  "fmt"
  "io/ioutil"
  "strings"
)

type Student struct {
  Name string
  Age  int
}

func main() {
  data, err := ioutil.ReadFile("data.json")
  if err != nil {
    fmt.Println(err)
    return
  }

  var student []Student

  err = json.NewDecoder(strings.NewReader(string(data))).Decode(&student)
  if err != nil {
    fmt.Println(err)
    return
  }

  fmt.Println(student)
}

Copied!

The output of the program is:

output
[{John 21} {Jane 22}]

Copied!

Working with JSON Tags

The encoding/json package provides a way to customize the JSON keys that are used when encoding and decoding JSON data. This is done by using the json tag.

The json tag is added to the struct field and contains the JSON key that will be used when encoding and decoding JSON data. The json tag can also be used to specify whether a field should be ignored when encoding and decoding JSON data.

Let’s look at an example of using the json tag.

main.go
package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    type Student struct {
        Name string `json:"name"`
        Age  int    `json:"age"`
    }

    student := Student{
        Name: "John",
        Age:  21,
    }

    json, err := json.Marshal(student)

    if err != nil {
        fmt.Println(err)
    }

    fmt.Println(string(json))
}

Copied!

The json tag is added to the Name and Age fields. The json tag contains the JSON key that will be used when encoding and decoding JSON data.

The output of the program is:

output
{"name":"John","age":21}

Copied!

The json tag can also be used to specify whether a field should be ignored when encoding and decoding JSON data. This is done by adding the - character to the json tag.

Let’s look at an example of using the json tag to ignore a field when encoding and decoding JSON data.

main.go
package main

import (
    "encoding/json"
    "fmt"
)

func main() {
    type Student struct {
        Name string `json:"name"`
        Age  int    `json:"-"`
    }

    student := Student{
        Name: "John",
        Age:  21,
    }

    json, err := json.Marshal(student)

    if err != nil {
        fmt.Println(err)
    }

    fmt.Println(string(json))
}

Copied!

The json tag is added to the Name field. The json tag contains the JSON key that will be used when encoding and decoding JSON data. The json tag is also added to the Age field. The json tag contains the - character, which specifies that the Age field should be ignored when encoding and decoding JSON data.

The output of the program is:

output
{"name":"John"}

Copied!

Handling JSON Errors in Golang

When working with JSON data there are some common errors that you may encounter. Let’s look at them.

  1. Unmarshal type error This error occurs when you try to unmarshal JSON data into a Golang data structure and the JSON data contains a field that is of a different type than the Golang data structure.

    In the example below, the Age field in the JSON data is of type string, but the Age field in the Student struct is of type int. This error can be fixed by changing the Age field in the Student struct to a type string.

    main.go
    package main
    
    import (
        "encoding/json"
        "fmt"
    )
    
    func main() {
        type Student struct {
            Name string
            Age  int
        }
    
        jsonStr := `{"Name":"John","Age":"21"}`
        var student Student
    
        err := json.Unmarshal([]byte(jsonStr), &student)
    
        if err != nil {
            fmt.Println(err)
        }
    
        fmt.Println(student)
    }
    

    Copied!

    The output of the program is:

    output
    json: cannot unmarshal string into Go struct field Student.name of type int
    {John 0}
    

    Copied!

    It is important to note that the json.Unmarshal function will still unmarshal the JSON data into the Golang data structure, but the fields that are of a different type will be set to their zero value.

  2. Unmarshal field error This is not an error per se since no error is returned, but can be considered an error. This error occurs when you try to unmarshal JSON data into a Golang data structure and the JSON data contains a field that is not present in the Golang data structure.

    In the example below, the Age field in the JSON data is not present in the Student struct. This error can be fixed by adding the Age field to the Student struct.

    main.go
    package main
    
    import (
        "encoding/json"
        "fmt"
    )
    
    func main() {
        type Student struct {
            Name string
        }
    
        jsonStr := `{"Name":"John","Age":21}`
        var student Student
    
        err := json.Unmarshal([]byte(jsonStr), &student)
    
        if err != nil {
            fmt.Println(err)
        }
    
        fmt.Println(student)
    }
    

    Copied!

    The output of the program is:

    output
    {John}
    

    Copied!

    It is important to note that the json.Unmarshal function will still unmarshal the JSON data into the Golang data structure, but the fields that are not present in the Golang data structure will be ignored.

  3. Empty JSON string This error occurs when you try to unmarshal an empty JSON string into a Golang data structure.

    main.go
    package main
    
    import (
        "encoding/json"
        "fmt"
    )
    
    func main() {
        type Student struct {
            Name string
            Age  int
        }
    
        jsonStr := ``
        var student Student
    
        err := json.Unmarshal([]byte(jsonStr), &student)
    
        if err != nil {
            fmt.Println(err)
        }
    
        fmt.Println(student)
    }
    

    Copied!

    The output of the program is:

    output
    unexpected end of JSON input
    { 0}
    

    Copied!

    As you can see, the json.Unmarshal function did unmarshal the JSON data into the Golang data structure, but the fields were set to their zero value. and a unexpected end of JSON input error was returned.

These are some of the common errors that you may encounter when working with JSON data in Golang. There are many more errors that you may encounter, but these are the most common ones (at least in my experience).

Conclusion

In conclusion, JSON is a widely used data interchange format that is well-supported in Golang through the encoding/json package. With its support for encoding and decoding JSON data into and from Go data structures, Golang provides a simple and efficient way to exchange data between different systems and applications. By understanding the key takeaways, such as the use of json.Marshal and json.Unmarshal or json.NewDecoder, developers can effectively work with JSON data in Golang and exchange data with ease and confidence.

Subscribe to my newsletter

Get the latest posts delivered right to your inbox.