
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.
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))
}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:
{"Name":"John","Age":21}The Marshal function can also be used to marshal a slice of structs and a map of structs.
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))
}The output of the program is:
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}}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.
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)
}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:
{John 21}The Unmarshal function can also be used to unmarshal JSON data into a slice of structs and a map of structs.
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)
}The output of the program is:
Unmarshaling a slice of structs
[{John 21} {Jane 22}]
Unmarshaling a map of structs
map[John:{John 21} Jane:{Jane 22}]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.
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))
}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:
{
    "Name": "John",
    "Age": 21
}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.
- Input source:json.Unmarshaltakes a byte slice of JSON data as an argument.json.NewDecodertakes anio.Readeras an argument. This means thatjson.Unmarshalcan only be used to unmarshal JSON data from a byte slice.json.NewDecodercan be used to unmarshal JSON data from a byte slice, a file, a network connection, etc.
- Output destination:json.Unmarshalrequires a pointer to a Golang data structure as an argument to store the decoded JSON data.json.NewDecoderon the other hand, returns ajson.Decoderstruct that can be used to decode individual JSON values.
- Performance:json.Unmarshalreads 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 thanjson.NewDecoderif the JSON data is large.json.NewDecoderon 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 thanjson.Unmarshalif the JSON data is large, but for small JSON data, it may be slower due to the overhead of creatingjson.Decoderstruct and reading the JSON data one by one.
- Functionality:json.Unmarshalprovides an easy way to unmarshal complete JSON strings into a Golang data structure.json.NewDecoderon 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.
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)
}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:
[
  {
    "Name": "John",
    "Age": 21
  },
  {
    "Name": "Jane",
    "Age": 22
  }
]Let’s look at an example of decoding JSON data from a file.
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)
}The output of the program is:
[{John 21} {Jane 22}]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.
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))
}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:
{"name":"John","age":21}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.
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))
}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:
{"name":"John"}Handling JSON Errors in Golang
When working with JSON data there are some common errors that you may encounter. Let’s look at them.
- 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 - Agefield in the JSON data is of type string, but the- Agefield in the- Studentstruct is of type int. This error can be fixed by changing the- Agefield in the- Studentstruct 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) }- The output of the program is: - output - json: cannot unmarshal string into Go struct field Student.name of type int {John 0}- It is important to note that the - json.Unmarshalfunction 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.
- 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 - Agefield in the JSON data is not present in the- Studentstruct. This error can be fixed by adding the- Agefield to the- Studentstruct.- 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) }- The output of the program is: - output - {John}- It is important to note that the - json.Unmarshalfunction 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.
- 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) }- The output of the program is: - output - unexpected end of JSON input { 0}- As you can see, the - json.Unmarshalfunction 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 inputerror 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.

