In today's post, we will walk through several scenarios to parse json
files in Golang. JSON (JavaScript Object Notation) is a simple data interchange format. Syntactically it resembles the objects and lists of JavaScript. It is most commonly used for communication between web back-ends and JavaScript programs running in the browser, but it is used in many other places, too. Its home page, json.org, provides a wonderfully clear and concise definition of the standard.
With the json package it’s a snap to read and write JSON data from your Go programs using Unmarshal()
.
Package json implements encoding and decoding of JSON as defined in RFC 7159. The mapping between JSON and Go values is described in the documentation for the Marshal and Unmarshal functions.
Golang JSON Unmarshal() Examples
Example-1: Parse structured JSON data
func Unmarshal(data []byte, v any) error
: Unmarshal parses the JSON-encoded data and stores the result in the value pointed to by v. If v is nil or not a pointer, Unmarshal returns an InvalidUnmarshalError
.
Here's an example of parsing a json
file to a struct.
json
file:
{
"name": "Harry Potter",
"score": 9.5
}
Code:
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
)
type Student struct {
Name string `json:"name"`
Score float64 `json:"score"`
}
func main() {
// open json file
jsonFile, err := os.Open("student.json")
// if return error, print out it
if err != nil {
fmt.Println(err)
}
fmt.Println("Successfully open student.json")
// defer the closing of json file
defer jsonFile.Close()
// Unmarshal() function accepts []byte as param
byteValue, _ := ioutil.ReadAll(jsonFile)
var student Student
err = json.Unmarshal(byteValue, &student)
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", student)
}
Output:
Successfully open student.json
{Name:Harry Potter Score:9.5}
Example-2: Parsing Structured Complex JSON data
For example, we want to parse this json
file:
{
"id": "0001",
"type": "donut",
"name": "Cake",
"ppu": 0.55,
"batters": {
"batter": [
{
"id": "1001",
"type": "Regular"
},
{
"id": "1002",
"type": "Chocolate"
},
{
"id": "1003",
"type": "Blueberry"
},
{
"id": "1004",
"type": "Devil's Food"
}
]
},
"topping": [
{
"id": "5001",
"type": "None"
},
{
"id": "5002",
"type": "Glazed"
},
{
"id": "5005",
"type": "Sugar"
},
{
"id": "5007",
"type": "Powdered Sugar"
},
{
"id": "5006",
"type": "Chocolate with Sprinkles"
},
{
"id": "5003",
"type": "Chocolate"
},
{
"id": "5004",
"type": "Maple"
}
]
}
We can see that we have to parse json
file to a struct which contains nested struct.
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
)
type Cake struct {
ID string `json:"id"`
Type string `json:"type"`
Name string `json:"name"`
Ppu float64 `json:"ppu"`
Batters struct {
Batter []Batter
} `json:"batters"`
Topping []struct {
ID string `json:"id"`
Type string `json:"type"`
} `json:"topping"`
}
type Batter struct {
ID string
Type string
}
func main() {
// open json file
jsonFile, err := os.Open("complex.json")
// if os.Open returns an error then print out it
if err != nil {
fmt.Println(err)
}
fmt.Println("Successfully Opened complex.json")
// defer the closing of the json file
defer jsonFile.Close()
byteValue, _ := ioutil.ReadAll(jsonFile)
var cake Cake
err = json.Unmarshal(byteValue, &cake)
if err != nil {
panic(err)
}
fmt.Printf("Type: %+v\n", cake.Type)
fmt.Printf("Name: %+v\n", cake.Name)
fmt.Printf("Batters: %+v\n", cake.Batters)
}
Output:
Successfully Opened complex.json
Type: donut
Name: Cake
Batters: {Batter:[{ID:1001 Type:Regular} {ID:1002 Type:Chocolate} {ID:1003 Type:Blueberry} {ID:1004 Type:Devil's Food}]}
Noted that:
- Struct's fields need to be exported (upper-case).
- Go uses convention to determine the attribute name for mapping JSON properties. If you want a different attribute name than the one provided in JSON data, you can specify mapping to field by specify json tag. To have the JSON parser/writer skip a field, just give it the name
"-"
. The JSON parser accepts a flag in the tag to indicate what to do if the field is empty. Theomitempty
flag instructs it not to include the JSON value in the output if it is the type's "zero-value." The "zero-value" for numbers is 0, the empty string for strings, and nil for maps, slices, and pointers. This is how theomitempty
flag is included. For example:
type Student struct {
Name string `json:"fullName,omitempty"`
Score float64 `json:"-"`
}
We can defined a nested struct instead of 2 structs like the above example:
type Cake struct {
ID string `json:"id"`
Type string `json:"type"`
Name string `json:"name"`
Ppu float64 `json:"ppu"`
Batters struct {
Batter []struct {
ID string `json:"id"`
Type string `json:"type"`
} `json:"batter"`
} `json:"batters"`
Topping []struct {
ID string `json:"id"`
Type string `json:"type"`
} `json:"topping"`
}
Example-3: Parsing Unstructured Data
In some cases, we do not know the structure of your JSON properties beforehand, so we cannot define structs to unmarshal
your data. To deal with these cases we have to create a map of strings to empty interfaces. Let's take a look at the below example:
json
file:
{
"id":123,
"name":"GoLinux Cloud",
"address":{
"street":"Summer",
"city":"San Jose",
},
"phoneNumber": 1234567890,
"role":"Admin",
"someField": "unstructed"
}
Code:
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
)
func main() {
// open json file
jsonFile, err := os.Open("unstructed.json")
// if return error, print out it
if err != nil {
fmt.Println(err)
}
fmt.Println("Successfully open unstructed.json")
// defer the closing of json file
defer jsonFile.Close()
byteValue, _ := ioutil.ReadAll(jsonFile)
var dev map[string]interface{}
err = json.Unmarshal(byteValue, &dev)
if err != nil {
panic(err)
}
//iterate through the map
for key, value := range dev {
fmt.Println(key, value)
}
}
Output:
Successfully open unstructed.json
id 123
name GoLinux Cloud
address map[city:San Jose street:Summer]
phoneNumber 1.23456789e+09
role Admin
someField unstructed
Summary
In this tutorial, we have show you some example of using Unmarshal()
function to parse both struct and unstructured data. Go is a strongly typed language, and therefore working with JSON is always going to be tricky. Check the err
parameter returned by Marshal
and Unmarshal
at all times. It's how you know if there is a syntax error in the JSON you're parsing. If you don't check it, your program will continue to run with the zeroed-out struct you've already created, potentially leading to unexpected behavior.
References
https://pkg.go.dev/encoding/json#Marshal
https://pkg.go.dev/encoding/json