Json in Golang
Table of Contents
When you start a project which interacts with the outer world, it requires data exchange. To make a project successful this interaction must be simple and efficient.
What is Data Exchange? #
Data exchange is the process of taking data structured under a source schema and transforming it into data structured under a target schema, so that the target data is an accurate representation of the source data. - Wikipedia
In simple terms, convert the data in the format which the receiver understands.
There are many data exchange formats like XML, JSON, HTML, CSV etc. Among all, JSON has become ubiquitous for web applications.
What is JSON? #
JSON (JavaScript Object Notation) is a lightweight format that is used for data interchanging. It is based on a subset of Javascript language. An object is an unordered set of name/value pairs.
An Example of JSON
{
"title": "How to use JSON in golang?",
"type": "Tutorial",
"publisher": "codesource.io",
"tags": ["golang", "json"],
"published": true
}
Now, we know what is JSON. It is time to use it with golang. In this tutorial, we will explore different golang types like string, int, struct, array, slice, maps.
Parse JSON in Golang ⚙️ #
JSON is JavaScript Object Notation and golang can’t use it in its original format.
For this, golang has provided an encoding/json package in the
standard library.
In golang, we use struct to represent json.
For example: In golang
type Employer struct {
Name string
Employee []int
}
In JSON
{
"name": "string",
"employee": []
}
We will use encode/json package to convert json into struct and struct into json. For this we will use 2 functions:
- Marshal (Encode) : Convert golang
structintojsonformat. - Unmarshal (Decode): Convert
jsoninto golangstruct
It is more like mapping the parameters than conversion. The default Go types for decoding and encoding JSON are
- bool for JSON boolean
- string for JSON string
- int/float64 for JSON number
- nil for JSON null
Encode (Marshal) 👨💻 #
To encode JSON data we use the Marshal function.
func Marshal(v interface{}) ([]byte, error)
Marshal function accepts an empty interface and returns an array of byte and error message.
package main
import (
"encoding/json"
"fmt"
"log"
)
func main() {
type Person struct {
Name string
Age int64
Location string
}
person := Person{
"Jon", 27, "London",
}
// encode into JSON
b, err := json.Marshal(person)
if err != nil {
log.Fatalf("Unable to encode")
}
// Marshal returns []byte
fmt.Println(string(b))
}
Output
{ "Name": "Jon", "Age": 27, "Location": "London" }
We are encoding Person struct in JSON format. First, create a new object of Person as person.
Then, encode the person in JSON using json.Marshal.
If everything goes well, then the err will be nil and b is the representation of person in []byte format.
Only data structures that can be represented as valid JSON will be encoded:
- JSON objects only support strings as keys; to encode a Go map type it must be of the form map[string]T (where T is any Go type supported by the json package).
- Only the exported fields (those that begin with an uppercase letter) of the struct can be encoded in JSON.
- Cyclic data structures are not supported; they will cause Marshal to go into an infinite loop.
- Pointers will be encoded as the values they point to (or ’null’ if the pointer is nil).
In golang Uppercase represent that a field is exported or public.
Take a look at the Person struct,
type Person struct {
Name string
Age int64
Location string
}
Change the Location field to lowercase location.
type Person struct {
Name string
Age int64
location string
}
Output
{ "Name": "Jon", "Age": 27 }
To map the struct field to the json tag. #
For example, you have struct field as Name but you want to map it as firstName in json.
To do this, you can tag the struct field.
Syntax
FieldName type `json:"tagname"`
⚠️ Don’t give any space between
json:"tagname", else it will throw an error.struct field tag json: "firstName" not compatible with reflect.StructTag.Get: bad syntax for struct tag value
Tag the Name field as firstName.
type Person struct {
Name string `json:"firstName"`
Age int64
Location string
}
Output
{ "firstName": "Jon", "Age": 27, "Location": "London" }
Omitempty #
We have a special json tag as omitempty. If a field is set as omitempty then it will not encode that field to json if it is empty.
For example, set Location field as omitempty.
type Person struct {
Name string `json:"firstName"`
Age int64
Location string `json:"location, omitempty"`
}
person := Person{
Name: "Jon",
Age: 27,
}
Output
{"firstName":"Jon","Age":27}
Decode (Unmarshal) 👨💻 #
To decode JSON data we use the Unmarshal function.
func Unmarshal(data []byte, v interface{}) error
Unmarshal accepts an array of byte and an interface and returns the error message. This interface is the struct to which the JSON decode.
package main
import (
"encoding/json"
"fmt"
"log"
)
func main() {
type Person struct {
Name string
Age int64
Location string
}
j := []byte(`{"name":"Jon","age":27,"location":"London"}`)
var p Person
err := json.Unmarshal(j, &p)
if err != nil {
log.Fatalf("Unable to decode the json")
}
fmt.Println(p)
}
Output
{Jon 27 London}
To store the decoded data in the struct,
Unmarshalwill look the json case-insensitive keys in the exported struct fields. As in the above case,nameis in lowercase and is mapped to the exportedNamefield.
Similar, to encoding you can add json tag to the struct field. For example:
Tag the Name field with json:"firstName". Now, it will map firstName to the Name.
type Person struct {
Name string `json:"firstName"`
Age int64
Location string
}
Decoding the arbitrary JSON 👨💻 #
In the above examples, we knew the JSON structure and we mapped it to the struct.
What if you don’t know the JSON structure? 🤔
In the json all the keys must be string. It means we can use the map type for arbitrary data.
The encoding/json package uses
map[string]interface{}to store the arbitrary JSON objects[]interface{}to store the arbitrary JSON arrays.
In map[string]interface{} the keys are string and values are interface{}. It is an empty interface. The interface{} (empty interface) type describes an interface with zero methods. In short, it can accept all the types.
The default Go types are:
- bool for JSON booleans,
- float64 for JSON numbers,
- string for JSON strings, and
- nil for JSON null.
Consider the JSON object as
{
"name": "Jon",
"location": {
"country": "England",
"city": "London"
},
"hobbies": ["photography", "writing"]
}
The Unmarshal function will parse it into a map whose keys are string and values are empty interface.
map[string]interface{} {
"Name": "Jon",
"Location": map[string]interface{} {
"Country": "England",
"City": "London",
},
"Hobbies": []interface{} {
"photography",
"writing",
},
}
All the values are of type interface{}. To access the underlying type of interface, we have to use type assertion.
For example:
var name interface{}
name = "Jon"
result := name.(string)
fmt.PrintF("Type is %T, value is %s", result, result")
package main
import (
"encoding/json"
"fmt"
"log"
)
func main() {
jsonObj := `{
"name": "Jon",
"age" : 27,
"location": {
"country": "England",
"city": "London"
},
"hobbies": [ "photography", "writing"]
}`
var person map[string]interface{}
err := json.Unmarshal([]byte(jsonObj), &person)
if err != nil {
log.Fatalf("Unable to encode")
}
for k, v := range person {
switch v := v.(type) {
case string:
fmt.Println(k, v)
case float64:
fmt.Println(k, v)
case map[string]interface{}:
for i, ival := range v {
fmt.Println(i, ival)
}
case []interface{}:
for i, ival := range v {
fmt.Println(i, ival)
}
default:
fmt.Println(k, v)
}
}
}
Output
country England
city London
0 photography
1 writing
name Jon
age 27
Map is not indexed, so the order will always be different.
Streaming Encoders and Decoders #
The encoding/json provides Decoder and Encoder types to support the common operation of reading and writing streams of JSON data.
func NewDecoder(r io.Reader) *Decoder
func NewEncoder(w io.Writer) *Encoder
In the below example,
- It will read the stream of JSON data from an
io.Reader - removes the
Agefield from each object - writes the objects to an
io.Writer
package main
import (
"encoding/json"
"log"
"os"
"strings"
)
func main() {
jsonStream := `{"Name":"Jon", "Age":27, "Location": "London"}
{"Name":"Bruce", "Age":35, "Location": "Gotham"}`
reader := strings.NewReader(jsonStream)
writer := os.Stdout
dec := json.NewDecoder(reader)
enc := json.NewEncoder(writer)
for {
var v map[string]interface{}
if err := dec.Decode(&v); err != nil {
log.Println(err)
return
}
for k := range v {
if k == "Age" {
delete(v, k)
}
}
if err := enc.Encode(&v); err != nil {
log.Println(err)
}
}
}
Output
{"Location":"London","Name":"Jon"}
{"Location":"Gotham","Name":"Bruce"}
2009/11/10 23:00:00 EOF
Code Walkthrough #
reader := strings.NewReader(jsonStream)
Create a type of io.Reader using the
strings package.
dec := json.NewDecoder(reader)
enc := json.NewEncoder(writer)
Create a new decoder which reads the data from the reader.
Create a new encoder which writes the data to the writer.
Rest of the code is self-explanatory.
Decode the arbitrary json in map[string]interface{}.
Conclusion #
In this tutorial, we explored the json package. There are many other amazing functions provided by the encoding/json. Like MarshalIndent, it will indent the encoded json. Please check out the official website to
learn more.