Golang (also known as Go) is a statically typed compiled programming language with a C-like syntax. Go provides a minimal syntax for general-purpose programming, with only 25 keywords.
Today, programmers use Go to build developer tools, cloud computing tools, CLI applications, and desktop and web applications. Go is very popular for building high-performance software systems where concurrency plays a key role.
Go developers often need to work with JSON content. For example, we often read JSON files to populate Go objects and write JSON files from existing Go objects. Like other modern programming languages, Go provides a standard library module to handle JSON structures.
In this tutorial, I’ll explain how to handle JSON in Go through a practical example. In addition, I’ll explain advanced concepts such as custom JSON encoding and decoding.
Go encoding/JSON package
Go provides an Encoding/JSON package that handles JSON content through the encoding namespace of the standard library. The Encoding/JSON package provides API functions for generating JSON documents from Go objects — and populating Go objects from JSON documents. Additionally, it allows you to customize the JSON-to-Go and Go-to-JSON translation process.
The JSON specification supports both formatted and minified documents. Therefore, the Go Encoding/JSON package allows developers to generate formatted and minimized JSON documents at the same time.
Encoding. Convert the Go object to JSON
What is Marshaling in Go?
Encoding Go objects in JSON format is called marshaling. We can use the Marshal function to convert the Go object to JSON. The syntax for the Marshal function is as follows.
func Marshal(v interface{}) ([]byte, error)
Copy the code
It accepts a null interface. In other words, you can supply the function with any Go data type — integer, floating point, string, structure, map, and so on — because all Go data type definitions can be represented as empty interfaces. It returns two values: a jSON-encoded byte slice and error.
Loading simple objects
As mentioned above, we can generate JSON from the original Go data type. For example, you can convert a Go string to a JSON string.
But since transformation primitives are not helpful in real-world software development projects, let’s start by transforming some simple objects. The following code snippet encodes JSON from a Map data structure.
package main
import (
"fmt"
"encoding/json"
)
func main() {
fileCount := map[string]int{
"cpp": 10,
"js": 8,
"go": 10,
}
bytes, _ := json.Marshal(fileCount)
fmt.Println(string(bytes))
}
Copy the code
Here we use string() to convert bytes into strings. Go encodes map data structures as JSON key-value objects. Once you run the code above, you should get the output shown below.
You can also encode JSON from a structure, as shown in the sample code below.
package main
import (
"fmt"
"encoding/json"
)
type Book struct {
Title string
Author string
Year int
}
func main() {
myBook := Book{"Hello Golang", "John Mike", 2021}
bytes, _ := json.Marshal(myBook)
fmt.Println(string(bytes))
}
Copy the code
Here, we must start the structure field names with uppercase English letters so that the fields can be exported to other packages. If your structure contains a field that begins with a lowercase letter, the encoding/JSON package will not include that particular field during encoding and will not cause any errors.
The code above outputs the following JSON structure.
{"Title":"Hello Golang","Author":"John Mike","Year":2021}
Copy the code
Encoding complex objects
In the previous example, we encoded JSON from Go objects, such as simple maps and structs. If you try to encode arrays of integers, arrays of strings, and primitive variables, Go will produce simple JSON structures for these elements.
Most of the time, however, we have to generate JSON files from complex objects in the Go program, such as product lists, product details, and various nested data records.
First, let’s encode JSON from a list of products. Look at the sample code below.
package main
import (
"fmt"
"encoding/json"
)
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,
},
}
bytes, _ := json.Marshal(products)
fmt.Println(string(bytes))
}
Copy the code
The above code initializes a list of products with two items. The Product structure has a Seller structure as a nested object — all products are placed in one Product slice. Next, we send the final product list to the Marshal function, encoding it as a JSON structure.
Once you run the code snippet above, you will get 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}]
Copy the code
As you can see, Go can encode JSON from any complex Go data structure. But now, when we look at the output above, we have two questions.
- The keys of the output JSON structure always start with a capital English letter — how can we rename the JSON field?
- When we code large, complex structures, the output becomes almost unreadable — how can we beautify the JSON output?
The Go Encoding/JSON package answers these questions with additional library features.
The collection function
Go provides several features to improve and customize JSON output through additional API functions and structure tags.
Rename field
You must start the declaration of the structure fields with a capital English letter in order for the JSON package to access them. Therefore, you will always get uppercase English letters as JSON keys. The Go encoding/JSON package allows developers to rename JSON fields at will via JSON structure tags.
The following code snippet encodes the JSON of the product object and uses JSON keys in serpentine case.
package main
import (
"fmt"
"encoding/json"
)
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))
}
Copy the code
As you can see, the code above uses structure tags to rename each exported field. The structure tag is not a mandatory element during JSON encoding — it is an optional element that renames a particular structure field during JSON encoding.
Once you execute the code above, you should get the following output.
{"id":50,"name":"Writing Book","seller":{"id":1,"name":"ABC Company","country_code":"US"},"price":100}
Copy the code
Generate JSON with indentation (pretty-print)
The Marshal function generates minimal inline JSON content without any formatting. You can use the MarshalIndent function to encode readable JSON with indentation. The following code generates Prettified JSON for the previous structure object.
package main
import (
"fmt"
"encoding/json"
)
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.MarshalIndent(book, "", "\t")
fmt.Println(string(bytes))
}
Copy the code
Once you run the code above, it will print out a formatted JSON structure, as shown below.
Here we use the Tab character (\t) for indentation. You can format with four Spaces, two Spaces, eight Spaces, etc., according to your requirements.
Ignore specific fields in the JSON output
Earlier, we used structure tags to rename JSON keys. We can also use structure tags to omit specific fields. If we use JSON: “-” as the tag, the associated structure fields will not be used for encoding. Also, if we use omitEmpty in the structure tag name string, if the value of the related field is empty, it will not be used for encoding.
The following code omits the encoding of the product identifier. In addition, it omits the empty country code value from the output.
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))
}
Copy the code
The above code produces the following output. Note that it does not contain the product identifier and the country code key for the second entry.
Unmask. Convert JSON to Go objects
In the Go environment, the decoding of JSON documents is called unmarshaling. We can use the Unmarshal function to convert JSON into Go objects. The syntax for the Unmarshal function is as follows.
func Unmarshal(data []byte, v interface{}) error
Copy the code
It takes two parameters: a byte slice of JSON content and an empty interface reference. This function may return an error if an error occurs during decoding. The Unmarshal function does not create and return Go objects, so we must pass a reference to store the decoded content.
Unseal simple JSON structures
Similar to JSON marshaling, we can unpack Go’s raw data types, such as integers, strings, floats, and Booleans. But again, since raw data decryption has no real use case in most software development projects, let’s first decode the following key-value structure into a Go structure.
{
"width": 500,
"height": 200,
"title": "Hello Go!"
}
Copy the code
The following code decodes the ABOVE JSON structure into a structure.
package main import ( "fmt" "encoding/json" ) type Window struct { Width int `json:"width"` Height int `json:"height"` Title string `json:"title"` } func main() { jsonInput := `{ "width": 500, "height": 200, "title": "Hello Go!" }` var window Window err := json.Unmarshal([]byte(jsonInput), &window) if err ! = nil { fmt.Println("JSON decode error!" ) return } fmt.Println(window) // {500 200 Hello Go! }}Copy the code
The jsonInput variable stores the JSON content as a multi-line string. Therefore, we have to convert it to byte sheets before passing it to the Unmarshal function using byte[]() type conversion syntax. Here, we examine the value of the returned error object to detect parsing errors.
In this case, the JSON tags described above are optional, because Go encoding/JSON packages typically map JSON fields to structural fields and do case-insensitive matching.
Similarly, we can decode a JSON structure into a Go map. Look at the sample code below.
package main import ( "fmt" "encoding/json" ) func main() { jsonInput := `{ "apples": 10, "mangos": 20, "grapes": 20 }` var fruitBasket map[string] int err := json.Unmarshal([]byte(jsonInput), &fruitBasket) if err ! = nil { fmt.Println("JSON decode error!" ) return } fmt.Println(fruitBasket) // map[apples:10 grapes:20 mangos:20] }Copy the code
Unshackle complex data structures
The previous decryption example showed you how to decrypt a simple JSON structure. We often have to decode complex nested JSON structures in software development projects. The following example demonstrates how to populate a Go object from a JSON-formatted list of products.
package main import ( "fmt" "encoding/json" ) type Product struct { Id int `json:"id"` Name string `json:"name"` Seller struct { Id int `json:"id"` Name string `json:"name"` CountryCode string `json:"country_code"` } `json:"seller"` Price int `json:"price"` } func main() { jsonInput := `[ { "id":50, "name":"Writing Book", "seller":{ "id":1, "name":"ABC Company", "country_code":"US" }, "price":100 }, { "id":51, "name":"Kettle", "seller":{ "id":20, "name":"John Store", "country_code":"DE" }, "price":500 }] ` var products []Product err := json.Unmarshal([]byte(jsonInput), &products) if err ! = nil { fmt.Println("JSON decode error!" ) return } fmt.Println(products) // [{50 Writing Book {1 ABC Company US} 100} {51 Kettle {20 John Store DE} 500}] }Copy the code
As shown in the code above, we need to first define a structure by examining the JSON input. This process can be a time-consuming task when you’re dealing with large and complex JSON structures. Therefore, you can use an online tool like JSON-to-go to create a structure definition based on JSON input.
There is also a way to access the parsed value in Go without creating the structure. You can dynamically access any value by creating an object of type Map [String] Interface {} for a JSON object, but this approach results in very complex, poor-quality source code.
However, you can check access to dynamic JSON values for experimental purposes with the following sample code. However, do not use this approach in production software systems without having a proper Go structure in place, as it results in complex and hard-to-test code.
package main import ( "fmt" "encoding/json" ) func main() { jsonInput := `[ { "id":50, "name":"Writing Book", "seller":{ "id":1, "name":"ABC Company", "country_code":"US" }, "price":100 }, { "id":51, "name":"Kettle", "seller":{ "id":20, "name":"John Store", "country_code":"DE" }, "price":500 }] ` var objMap []map[string]interface{} err := json.Unmarshal([]byte(jsonInput), &objMap) if err ! = nil { fmt.Println("JSON decode error!" ) return } fmt.Println("Price of the second product:", objMap\[1\]["price"]) }Copy the code
The code above prints the price of the second product item without the Go structure.
Read the JSON file from the file system
We used hard-coded JSON strings to demonstrate this in the previous example. In practice, however, we load JSON strings from different sources: from the file system, over the Internet, through local network locations, and so on. Most programmers typically use the JSON format to store configuration details on the file system.
Let’s write some Go code to read and decode JSON data from a file and convert it into a Go object. First, create a file called config.json and enter the following.
{" timeout ": 50.30," pluginsPath ":" ~ / plugins/", "window" : {" width ": 500," height ": 200, the" x ": 500, the" y ": 500}}Copy the code
Now, run the following code to decode the above JSON file into the appropriate structure.
package main import ( "fmt" "io/ioutil" "encoding/json" ) type Config struct { Timeout float32 PluginsPath string Window struct { Width int Height int X int Y int } } func main() { bytes, err := ioutil.ReadFile("config.json") if err ! = nil { fmt.Println("Unable to load config file!" ) return } var config Config err = json.Unmarshal(bytes, &config) if err ! = nil { fmt.Println("JSON decode error!" Println(config) // {50.3 ~/plugins/ {500 200 500 500 500}}}Copy the code
The above code uses the ioutil.ReadFile function to read the contents of the JSON file into bytes and decode the data records into the Config structure.
Write the JSON file to the file system
In the previous example, we printed the encoded JSON content to the console using the Println function. We can now save these JSON strings to a file using the ioutil.WriteFile function, as shown below.
package main import ( "io/ioutil" "encoding/json" ) type Window struct { Width int `json:"width"` Height int `json:"height"` X int `json:"x"` Y int `json:"y"` } type Config struct { Timeout float32 `json:"timeout"` PluginsPath string `json:"pluginsPath"` Window Window `json:"window"` } func main() { config := Config { Timeout: 40.420, PluginsPath: "~ / plugins/etc", Window: Window {500, 200, 20, 20}, } bytes, _ := json.MarshalIndent(config, "", " ") ioutil.WriteFile("config.json", bytes, 0644)}Copy the code
The code above writes config.json by encoding the config object as a JSON object. Here we use two Spaces to indent and convert the structure field to a CAMEL capitals JSON key by using the structure tag.
Custom fetching and unfetching
The Go JSON package is very flexible in that it provides the ability to override the encoding and decoding process. These features are helpful when you need to convert JSON data records from one format to another during encoding/decoding.
Custom orchestration
Suppose you’re writing a contact management application in Go, and you provide all users with the ability to download a contact list in JSON format. Suppose, for security policy reasons, you cannot let non-administrator users see all E-mail ids. In this case, you can customize the JSON encoding process through the custom marshaling feature in the Go JSON package, as shown below.
package main
import (
"fmt"
"encoding/json"
"strings"
)
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"-"`
}
func main() {
persons := []Person {
Person {"James Henrick", 25, "[email protected]"},
Person {"David Rick", 30, "[email protected]"},
}
bytes, _ := json.MarshalIndent(persons, "", " ")
fmt.Println(string(bytes))
}
func (p *Person) MarshalJSON() ([]byte, error) {
type PersonAlias Person
return json.Marshal(&struct{
*PersonAlias
Email string `json:"email"`
}{
PersonAlias: (*PersonAlias)(p),
Email: strings.Repeat("*", 4) + "@mail.com", // alter email
})
}
Copy the code
The code above prints out all the contact information, but for security policy reasons, it changes the original E-mail address. Note that here we need to create another type (alias) from the Person type, because if we tried to call the Marshal function for the original Person type, the program would go into an infinite loop due to the recursive implementation of the coding process. Once you run the code snippet above, you should see the following output.
Custom decryption
The Go JSON package also lets you customize the JSON decoding process. Suppose you need to work with a JSON configuration file and need to convert some values during decoding. Suppose a configuration field says the temperature in Kelvin, but you need to store the number in degrees Celsius.
Take a look at the code below, which implements custom decryption.
package main import ( "fmt" "encoding/json" ) type Config struct { FunctionName string Temperature float32 } func main() { jsonInput := `{ "functionName": "triggerModule", "temperature": 4560.32} 'var config config err := json.Unmarshal([]byte(jsonInput), &config) if err! = nil { fmt.Println("JSON decode error!" Println(config) // {triggerModule 4287.17}} func (c * config) UnmarshalJSON(data []byte) error {type ConfigAlias Config tmp := struct { Temperature float32 *ConfigAlias }{ ConfigAlias: (*ConfigAlias)(c), } if err := json.Unmarshal(data, &tmp); err ! = nil {return err} c.temperature = tmp.temperature-273.15 return nil}Copy the code
The above code interprets THE JSON by converting the temperature field value from Kelvin to Celsius. Here we also need to create another type (alias) to avoid an infinite loop, similar to the custom marshaling.
conclusion
In this tutorial, we discussed JSON encoding (marshaling) and decoding (unmarshaling) in Go through practical examples. JSON is a widely used language-independent encoding format. As a result, almost all go-based networking frameworks handle JSON encoding and decoding internally. For example, the GinHTTP framework allows you to use JSON packages to send a structure directly to an API function without manually marshaling.
However, you can use the Go JSON package in your Go applications without consuming third-party libraries, as the JSON package is part of the standard library. Also, there are faster alternatives to the Go JSON package (based on this benchmark). However, the JSON package is part of the standard library and is maintained by the Go development team. Therefore, the Go development team will improve the performance of encoding/JSON packages in upcoming releases.
The postUsing JSON in Go: A guide with examples appears on The LogRocket blog.