Mastering JSON Parsing in Go: A Comprehensive Guide

Mastering JSON Parsing in Go: A Comprehensive Guide

Introduction

Parsing and storing data in JSON(JavaScript Object Notation) format has gained popularity due to its ease of use, representation of data in key-value pairs, and language independence. In this blog, we will explore how we can easily parse a JSON file using a "struct" in Go.

JSON example

In the following example, user data is represented as JSON, including various data types such as string, int, float, boolean, slice, and map.

{
  "userId": 1,
  "userName": "Alex",
  "isActive": true,
  "skills": {
    "languagesKnown": [
      "Python",
      "Go"
    ],
    "passion": [
      "Technical Program Management",
      "Automation"
    ]
  },
  "roles": [
    "project-lead",
    "billing-admin"
  ],
  "training-credit": 500.5
}

Steps to parse the JSON

We need to follow these steps to parse the JSON using a struct:

  1. Define a struct for the keys in JSON

  2. Read the JSON file

  3. Unmarshal the JSON using the Go standard library

  4. Access values from the struct variable.

1. Define a struct for the keys in JSON

"struct" is a powerful data type in Go that allows consolidating data from different data types into one single variable.

To parse the above JSON, we will need the following two structs.

All the keys from the JSON can be directly represented in the struct, except for the "skills" key, which has nested keys - "languageKnown" and "passion".

As a result, we need an additional struct for "skills", as shown below.

type SkillSet struct {
    LanguagesKnown []string
    Passion        []string
}

We have the following struct for the entire JSON, including the above SkillSet struct.

type UserData struct {
    UserId         int
    UserName       string
    IsActive       bool
    Roles          []string
    TrainingCredit float32 `json:"training-credit"` // since json file has different key(training-credit), hence we map to TrainingCredit
    Skills         SkillSet
}

With the UserData struct provided above, we have successfully mapped all the keys in the JSON to their corresponding data types.

We can now create a variable of type "UserData" using the below code:

var userInfo UserData

2. Read JSON file

We can read the JSON file using os.ReadFile() from the standard Go library. ReadFile() function returns a byte slice of the JSON file content and an error in case of issues when reading a file.

fileByte, err := os.ReadFile("jsonParsing/hr_data.json")

Refer https://pkg.go.dev/os#ReadFile for more details on os.ReadFile() function.

3. Unmarshalling

Unmarshalling is the process of converting external data (in our case, a byte slice of the JSON file) into a Go object (in our case, a struct).

Marshaling, on the other hand, is the process of converting Go objects into JSON format so that they can be read externally.

In Go, we can unmarshal using the standard Go library - json.Unmarshal(). This function takes a byte slice and a pointer variable to store the JSON data, as shown below:

err = json.Unmarshal(fileByte, &userInfo)

When there are no errors, all JSON data will be transferred to the userInfo variable.

Refer https://pkg.go.dev/encoding/json#Unmarshal for more details on json.Unmarshal() function.

4. Access values from the struct variable

After unmarshalling, values can be retrieved using userInfo.{key} as shown below:

    fmt.Printf("------Employee Details: %s------\n", userInfo.UserName)
    fmt.Println("User ID:", userInfo.UserId)
    fmt.Println("Is Active:", userInfo.IsActive)
    fmt.Println("Available training credit", userInfo.TrainingCredit)
    fmt.Println("Languages known:", userInfo.Skills.LanguagesKnown)
    fmt.Println("Passionate about:", userInfo.Skills.Passion)

So far, we have explored parsing JSON with a single record. By making a few changes to the above code, we could parse more records.

JSON example with multiple records

Here is a JSON file with multiple records.

[
  {
    "userId": 1,
    "userName": "Alex",
    "isActive": true,
    "skills": {
      "languagesKnown": [
        "Python",
        "Go"
      ],
      "passion": [
        "Technical Program Management",
        "Automation"
      ]
    },
    "roles": [
      "project-lead",
      "billing-admin"
    ],
    "training-credit": 500.5
  },
  {
    "userId": 2,
    "userName": "Tim",
    "isActive": true,
    "skills": {
      "languagesKnown": [
        "JAVA",
        "Node.js"
      ],
      "passion": [
        "Web Development",
        "DevOps"
      ]
    },
    "roles": [
      "project-member",
      "billing-viewer"
    ],
    "training-credit": 98.5
  },
  {
    "userId": 3,
    "userName": "Ram",
    "isActive": true,
    "skills": {
      "languagesKnown": [
        "Python",
        "ABAP"
      ],
      "passion": [
        "Web Services",
        "CI/CD Pipelines"
      ]
    },
    "roles": [
      "project-admin",
      "billing-viewer"
    ],
    "training-credit": 44.5
  },
  {
    "userId": 4,
    "userName": "Sam",
    "isActive": true,
    "skills": {
      "languagesKnown": [
        "Go"
      ],
      "passion": [
        "DevOps"
      ]
    },
    "roles": [
      "project-member"
    ],
    "training-credit": 128.5
  }
]

Let's create a slice variable of type "UserData" using the following code to accommodate more records.

var userInfo []UserData

We can use for loop to iterate over the records. Here is the code snippet:

package main

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

type SkillSet struct {
    LanguagesKnown []string
    Passion        []string
}

type UserData struct {
    UserId         int
    UserName       string
    IsActive       bool
    Roles          []string
    TrainingCredit float32 `json:"training-credit"` // json file contains different key(training-credit), hence we map to TrainingCredit
    Skills         SkillSet
}

func main() {
    var userInfo []UserData
    fileByte, err := os.ReadFile("jsonParsing/hr_data.json")
    if err != nil {
        fmt.Printf("Error occured while reading hr json. Err:%s", err)
    }
    err = json.Unmarshal(fileByte, &userInfo)
    if err != nil {
        fmt.Printf("Error occured while unmarshalling hr json. Err:%s", err)
    }
// for loop to iterate over the records
    for _, value := range userInfo {
        fmt.Printf("------Employee Details: %s------\n", value.UserName)
        fmt.Println("User ID:", value.UserId)
        fmt.Println("Is Active:", value.IsActive)
        fmt.Println("Available training credit", value.TrainingCredit)
        fmt.Println("Languages known:", value.Skills.LanguagesKnown)
        fmt.Println("Passionate about:", value.Skills.Passion)
    }
}

This blog provides an example of how to parse a JSON file using a struct in Go. It explains how to define a struct, read the JSON file, unmarshal it, and access values from the struct variable. It also provides an example of a JSON file with multiple records and a for loop to iterate over the records, printing out the user details.