Deliver my data, Mr. Json!
JSON is the lingua franca of exchanging data over the net and between applications written in different programming languages. In this article, we create a tiny JSON client/server app in Go.
(Apologies for the sound quality. I noticed too late that the mic was overmodulated.)
This is the transcript of the video.
What is JSON?
JSON is a standard data format for exchanging data over the net and between applications. It became popular as a more readable alternative to XML, with the added benefit that Javascript code can read and write JSON data out of the box. Still, almost any other popular programming language has at least one library for handling JSON data.
JSON is a human-readable text format, which makes it suitable for configuration files as well.
So how does JSON data look like?
The syntax of JSON is almost like you would create a JavaScript object. This, by the way, is also where the name “JSON” comes from: It is an acronym for “Javascript Standard Object Notation”.
In its base form, JSON data is a list of name-value entities. As an example, let's create some weather information in JSON.
{
"location": "Zzyzx",
"weather": "sunny"
}
Here we have two entries, location and weather. Both are strings. Note that the names must be enclosed in double quotes. This is not a requirement for Javascript, only for JSON.
JSON knows some other data types besides strings: numbers, booleans, a null value, arrays, and objects. Let's add some of these to our weather data: A numeric temperature, a boolean to tell whether the temperature is measured in celsius or fahrenheit, an array with the temperature forecast for the next three days, and an object that holds wind direction and wind speed.
{
"location": "Zzyzx",
"weather": "sunny",
"temperature": 30,
"celsius": true,
"temp_forecast": [ 27, 25, 28 ],
"wind": {
"direction": "NW",
"speed": 15
}
}
What is not in JSON?
Some data types or special values cannot be expressed in JSON. Most notably, there is no date type. Any date value is converted to and from an ISO-8601 date string. Also, Javascript's special values NaN
, Infinity
, and -Infinity
are simply turned into a null
value.
JSON and Go
Go's standard library includes the package encoding/json
that makes working with JSON a snap. With this package, we can map JSON objects to Go struct types, and convert data between the two. Let's examine this in code.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
)
type weatherData struct {
LocationName string `json: locationName`
Weather string `json: weather`
Temperature int `json: temperature`
Celsius bool `json: celsius`
TempForecast []int `json: temp_forecast`
Wind windData `json: wind`
}
type windData struct {
Direction string `json: direction`
Speed int `json: speed`
}
Location data is just a latitude and a longitude.
type loc struct {
Lat float32 `json: lat`
Lon float32 `json: lon`
}
func weatherHandler(w http.ResponseWriter, r *http.Request) {
location := loc{}
jsn, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Fatal("Error reading the body", err)
}
err = json.Unmarshal(jsn, &location)
if err != nil {
log.Fatal("Decoding error: ", err)
}
log.Printf("Received: %v\n", location)
weather := weatherData{
LocationName: "Zzyzx",
Weather: "cloudy",
Temperature: 31,
Celsius: true,
TempForecast: []int{30, 32, 29},
Wind: windData{
Direction: "S",
Speed: 20,
},
}
encoding/json
. weatherJson, err := json.Marshal(weather)
if err != nil {
fmt.Fprintf(w, "Error: %s", err)
}
w.Header().Set("Content-Type", "application/json")
w.Write(weatherJson)
}
func server() {
http.HandleFunc("/", weatherHandler)
http.ListenAndServe(":8088", nil)
}
func client() {
locJson, err := json.Marshal(loc{Lat: 35.14326, Lon: -116.104})
req, err := http.NewRequest("POST", "http://localhost:8088", bytes.NewBuffer(locJson))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
body, err := ioutil.ReadAll(resp.Body)
fmt.Println("Response: ", string(body))
resp.Body.Close()
}
func main() {
go server()
client()
}
A note about func main()
: Usually, properly designed code would check if the server is ready before running the client. In this simple test scenario, this unsynchronized execution appears to work; however, in general you should not rely on this.
Tip
Instead of typing the Go struct manually, have a script convert it for you. Just go to
https://mholt.github.io/json-to-go
and paste the JSON data into the textbox on the left, and the page then generates a Go struct on the fly. Yay!
Getting and installing the code
As always, use -d to prevent the binary from showing up in your path.
First, get the code:
with Go Modules:
git clone https://github.com/appliedgo/json
or if you still use GOPATH:
go get -d github.com/appliedgo/json
Then, simply cd into the code directory and run the code:
go run json.go
Final notes
Further reading
There are a couple more things about JSON than what fits into the 5-10 minutes format of a screencast.
If you want to learn more about JSON, like decoding JSON data of an unknown structure, or how to implement stream encoding or decoding, the article “JSON and Go” from the official Go blog is a great place to start:
https://blog.golang.org/json-and-go
Zzyzx
I did not make up the name “Zzyzx” that I use in the weather data. This is an existing location. Use the lat/long data from the code to find out where it is.
Coincidentally, xkcd just recently published a cartoon that mentions Zzyzx. I swear I did not steal the name from there; I already had it in the draft before the cartoon came out! :-)
Errata
2017-04-05: Fixed json tag for field Lon
(was: json lat
)