In this article, I will show you the basics of how to parse and return JSON in Go while also providing you with an understanding of the process.
First, let’s look at how JSON is parsed in other languages specifically dynamically typed ones, I’m going to use python as an example.
############## RETURNING JSON ############## import jsona Python object (dict):
p_dict = {
“name”: “John”,
“age”: 30,
“city”: “New York”
}convert into JSON:
y = json.dumps(p_dict)
the result is a JSON string:
print(y)
##############
CONSUMING JSON
##############
import jsonsome JSON:
j_str = ‘{ “name”:“John”, “age”:30, “city”:“New York”}’
parse x:
y = json.loads(j_str)
the result is a Python dictionary:
print(y[“age”])
The code looks relatively simple, in the case of returning JSON we have a python dictionary “p_dict” which we can convert to a JSON string using json.dumps(p_dict)
. When consuming JSON, we have a JSON string “j_str” which we can convert to a python diction using json.loads(j_str)
.
Now parsing a format like JSON in a statically typed language like Go presents a bit of a problem. Go is statically typed which means the type of every variable in the program needs to be known at compile time. This means that you as the programmer must specify what type each variable is. Other languages (python) offer some form of type inference, the capability of the type system to deduce the type of a variable. The main advantage of a statically typed language is that all kinds of checking can be done by the compiler, and therefore a lot of trivial bugs are caught at a very early stage. A simple code example may clarify.
PYTHON
x = 3
GOLANG
var x int = 3
When parsing the JSON anything could show up in the JSON body, so how does the compiler know how to set up memory given it doesn’t know the types?
There are two answers to this. One answer when you know what your data will look and one answer for when you don’t. We will cover the first option initially as this is the most common situation and leads to the best practices.
When the data types in the JSON is known you should parse the JSON into a struct you’ve defined. Any field which doesn’t fit in the struct will just be ignored. We’ll explore this option in both returning and consuming JSON.
Let’s say we want to return the following JSON object.
{
“key1”: “value 1”,
“key2”: “value 2”
}
The code below shows how this is done, let’s talk through it.
package mainimport (
“encoding/json”
“fmt”
)type JSONResponse struct {
Value1 stringjson:"key1"
Value2 stringjson:"key2"
}func main() {
jsonResponse := JSONResponse{ Value1: "Test value 1", Value2: "Test value 2", } fmt.Printf("The struct returned before marshalling\n\n") fmt.Printf("%+v\n\n\n\n", jsonResponse) // The MarshalIndent function only serves to pretty print, json.Marshal() is what would normally be used byteArray, err := json.MarshalIndent(jsonResponse, "", " ") if err != nil { fmt.Println(err) } fmt.Printf("The JSON response returned when the struct is marshalled\n\n") fmt.Println(string(byteArray))
}
json: "key1"
which let’s Go know what the associated key is in the JSON object.Please run the code here to see the results, play with the code also it’s the best way to learn https://play.golang.org/p/gaBMvz21LiA.
Now let’s look at a more complex JSON object with nested items
{
“key1”: “value 1”,
“key2”: “value 2”,
“nested”: {
“nestkey1”: “nest value 1”,
“nestkey2”: “nest value 2”
}
}
The code below shows how this is done, let’s discuss what’s changed.
package mainimport (
“encoding/json”
“fmt”
)type JSONResponse struct {
Value1 stringjson:"key1"
Value2 stringjson:"key2"
Nested Nestedjson:"nested"
}type Nested struct {
NestValue1 stringjson:"nestkey1"
NestValue2 stringjson:"nestkey2"
}func main() {
nested := Nested{ NestValue1: "nest value 1", NestValue2: "nest value 2", } jsonResponse := JSONResponse{ Value1: "value 1", Value2: "value 2", Nested: nested, } // Try uncommenting the section below and commenting out lines 21-30 the result will be the same meaning you can declare inline // jsonResponse := JSONResponse{ // Value1: "value 1", // Value2: "value 2", // Nested: Nested{ // NestValue1: "nest value 1", // NestValue2: "nest value 2", // }, // } fmt.Printf("The struct returned before marshalling\n\n") fmt.Printf("%+v\n\n\n\n", jsonResponse) // The MarshalIndent function only serves to pretty print, json.Marshal() is what would normally be used byteArray, err := json.MarshalIndent(jsonResponse, "", " ") if err != nil { fmt.Println(err) } fmt.Printf("The JSON response returned when the struct is marshalled\n\n") fmt.Println(string(byteArray))
}
Those are the key differences between this example and the previous one. Again run the code here to test it and make some of the changes suggested in the comments https://play.golang.org/p/GcRceKe1jC-.
Finally, let’s look at returning a JSON object that contains an array.
{
“key1”: “value 1”,
“key2”: “value 2”,
“nested”: {
“nestkey1”: “nest value 1”,
“nestkey2”: “nest value 2”
},
“arrayitems”: [
{
“iteminfokey1”: “item info 1 array index 0”,
“iteminfokey2”: “item info 2 array index 0”
},
{
“iteminfokey1”: “item info 1 array index 1”,
“iteminfokey2”: “item info 2 array index 1”
}
]
}
The code below shows how this is done, again let’s discuss what has changed.
package mainimport (
“encoding/json”
“fmt”
)type JSONResponse struct {
Value1 stringjson:"key1"
Value2 stringjson:"key2"
Nested Nestedjson:"nested"
ArrayItems []ArrayItemjson:"arrayitems"
}type Nested struct {
NestValue1 stringjson:"nestkey1"
NestValue2 stringjson:"nestkey2"
}type ArrayItem struct {
ItemInfo1 stringjson:"iteminfokey1"
ItemInfo2 stringjson:"iteminfokey2"
}func main() {
arrayItems := []ArrayItem{ ArrayItem{ ItemInfo1: "item info 1 array index 0", ItemInfo2: "item info 2 array index 0", }, ArrayItem{ ItemInfo1: "item info 1 array index 1", ItemInfo2: "item info 2 array index 1", }, } nested := Nested{ NestValue1: "nest value 1", NestValue2: "nest value 2", } jsonResponse := JSONResponse{ Value1: "value 1", Value2: "value 2", Nested: nested, ArrayItems: arrayItems, } fmt.Printf("The struct returned before marshalling\n\n") fmt.Printf("%+v\n\n\n\n", jsonResponse) // The MarshalIndent function only serves to pretty print, json.Marshal() is what would normally be used byteArray, err := json.MarshalIndent(jsonResponse, "", " ") if err != nil { fmt.Println(err) } fmt.Printf("The JSON response returned when the struct is marshalled\n\n") fmt.Println(string(byteArray))
}
Test it out and play with the code here https://play.golang.org/p/YhqKR6lZ_ge.
Ok now let’s look at consuming JSON from a rest API, I’m going to mock the returned JSON from the API to simplify the process. Since we have an understanding now of how Go structs relate to JSON strings we’ll jump straight in with a complicated JSON object.
This is the JSON response we are going to get from the API. I’m using the JSON returned from https://httpbin.org/get which is a great website for testing HTTP requests and responses. Here’s the JSON we are going to receive. (Run curl https://httpbin.org/get
in your terminal to test)
{
“args”: {},
“headers”: {
“Accept”: “/”,
“Host”: “httpbin.org”,
“User-Agent”: “curl/7.54.0”
},
“origin”: “83.7.252.17, 83.7.252.17”,
“url”: “https://httpbin.org/get”
}
Note the “origin” will be different for you as your request will be coming from a different IP. The empty “args” object will contain any arguments passed in the URL. So https://httpbin.org/get?testArg=testValue&x=3would return,
“args”: {
“testArg”: “testValue”,
“x”: “3”
}
This args value is a good example of a case when we don’t know what the key name or the values intended type is going to be. To get around this we set “args” type as map[string]interface{}
which is a map (a hash table) with keys of type string and values of type interface{}. An empty interface{}
is a way of defining a variable in Go as “this could be anything”. At runtime, Go will then allocate the appropriate memory to fit whatever you decide to store in it.
Let’s have a look at an example of using JSON from an API in code and discuss the main sections of the file.
package mainimport (
“encoding/json”
“fmt”
)type JSONResponse struct {
Args map[string]interface{}json:"args"
Headers Headersjson:"headers"
Origin stringjson:"origin"
Url stringjson:"url"
}type Headers struct {
Accept stringjson:"Accept"
Host stringjson:"Host"
UserAgent stringjson:"User-Agent"
}func main() {
jsonString :={"args": {}, "headers": {"Accept": "*/*", "Host": "httpbin.org", "User-Agent": "curl/7.54.0"}, "origin": "89.7.222.10, 89.7.222.10", "url": "https://httpbin.org/get"}
var jsonResponse JSONResponse err := json.Unmarshal([]byte(jsonString), &jsonResponse) if err != nil { fmt.Println(err) } fmt.Println(jsonResponse)
}
json:"headers"
etc. enable specific JSON key values to be loaded into the appropriate variables within the struct.Try it out for yourself here https://play.golang.org/p/oqFjZii_yjA.
We now know that we can parse JSON into a map[string]interface{} type if we are unsure of the value types or the key names that are going to be used in the JSON. If we look at the previous example and imagine values were passed in via the URL (https://httpbin.org/get?testArg=testValue&x=3) to result in,
“args”: {
“testArg”: “testValue”,
“x”: “3”
}
These values can be accessed simply by referencing jsonResponse.Args[“testArg”]
and jsonResponse.Args[“x”]
. Take a look here https://play.golang.org/p/WehVIkK8CRd, I’ve updated the mocked JSON to include the values within the “args” object and added 2 print statements at the end to show you how to access the values.
Hopefully this has helped you gain a better understanding of how to work with JSON in Go. As always any feedback is welcome and please let me know if I’ve made any mistakes.
Thanks for reading ❤
If you liked this post, please do share/like it with all of your programming buddies!
Follow us on Facebook | Twitter
☞ JSON Tutorial For Beginners | What is JSON | Learning JSON with JavaScript
☞ The complete beginner’s guide to JSON
☞ Converting JSON to CSV in Java
☞ How to use JSON.stringify() and JSON.parse() in JavaScript
☞ The Complete Guide to JSON Web Tokens
☞ Stateless Authentication with JSON Web Tokens
☞ Authenticate a Node ES6 API with JSON Web Tokens
#json #go #javascript