18. JSON and Go

18.1 JSON

JSON stands for JavaScript Object Notation.

JSON is syntax for storing and exchanging text information, much like XML. JSON is smaller than XML, and faster and easier to parse. JSON is language independent. Here’s an example:

1 {
2 "employees": [
3 { "firstName":"John" , "lastName":"Doe" },
4 { "firstName":"Anna" , "lastName":"Smith" },
5 { "firstName":"Peter" , "lastName":"Jones" }
6 ]
7 }

The employee object is an array of 3 employee records (objects).

JSON data is written as name/value pairs. A name/value pair consists of a field name (in double quotes), followed by a colon and followed by a value:

"firstName" : "Satish"

JSON values can be:

  • A number (integer or floating point)
  • A string (in double quotes)
  • A Boolean (true or false)
  • An array (in square brackets)
  • An object (in curly brackets)
  • null

JSON objects are written inside curly brackets, Objects can contain multiple name/values pairs:

{ "firstName":"Satish" , "lastName":"Talim" }

18.2 Package json

Usage: import "encoding/json"

Package json implements encoding and decoding of JSON objects.

18.2.1 Encoding

To encode JSON data we use the Marshal function.

func Marshal(v interface{}) ([]byte, error)

Let’s look at an example.

Program: json1.go


 1 package main
 2 
 3 import (
 4         "encoding/json"
 5         "fmt"
 6 )        
 7 
 8 type User struct {
 9 	UserName string
10 	EmailID  string
11 	Password  string
12 }
13 
14 func main() {
15 	u := User{UserName: "IndianGuru", EmailID: "satishtalim@gmail.com", Password: "password"}
16 	m, _ := json.Marshal(u)
17 	fmt.Println(string(m))
18 }

The output is:

{"UserName":"IndianGuru","EmailID":"satishtalim@gmail.com","Password":"password"}

In the above program we have a simple struct, we create a new instance of this struct and encode it.

18.2.2 Struct tags

1 type Person struct {
2         UserName string `json:"user_name"`
3         EmailID  string
4         Password string `json:"-"`
5 }

When we use the Marshal function on a struct instance it produces JSON.

In the above example, a field appears in JSON as key “user_name” and the Password field is ignored by this package.

In the previous example json1.go the password got printed. However, we don’t want to print the password. Here’s the changed code:

Program: json2.go


 1 package main
 2 
 3 import (
 4         "encoding/json"
 5         "fmt"
 6 )        
 7 
 8 type User struct {
 9 	UserName string `json:"user_name"`
10 	EmailID  string
11 	Password string `json:"-"`
12 }
13 
14 func main() {
15 	u := User{UserName: "IndianGuru", EmailID: "satishtalim@gmail.com", Password: "password"}
16 	m, _ := json.Marshal(u)
17 	fmt.Println(string(m))
18 }

The output is:

{"user_name":"IndianGuru","EmailID":"satishtalim@gmail.com"}

What if I don’t want to show the UserName if it is empty? Here’s the modified code:

Program: json3.go


 1 package main
 2 
 3 import (
 4         "encoding/json"
 5         "fmt"
 6 )        
 7 
 8 type User struct {
 9 	UserName string `json:"first_name,omitempty"`
10 	EmailID  string
11 	Password string `json:"-"`
12 }
13 
14 func main() {
15 	u := User{UserName: "", EmailID: "", Password: "password"}
16 	m, _ := json.Marshal(u)
17 	fmt.Println(string(m))
18 }

The output is:

{"EmailID":""}

The JSON parser also accepts a flag in the tag to let it know what to do if the field is empty. The omitempty flag tells it to not include the JSON value in the output if it’s the “zero-value” for that type.

The “zero-value” for numbers is 0, for strings it’s the empty string, for maps, slices and pointers it’s nil. This is how you include the omitempty flag.

1 type MyStruct struct {
2         SomeField string `json:"some_field,omitempty"`
3 }

Notice that the flag goes inside the quotes.

If the SomeField was an empty string, and you converted it to JSON, some_field wouldn’t be included in the output at all.

What if I want to retain the field name EmailID and at the same time use the omitempty flag? Here’s the modified code:

Program: json4.go


 1 package main
 2 
 3 import (
 4         "encoding/json"
 5         "fmt"
 6 )        
 7 
 8 type User struct {
 9 	UserName string `json:"first_name,omitempty"`
10 	EmailID  string `json:",omitempty"`
11 	Password string `json:"-"`
12 }
13 
14 func main() {
15 	u := User{UserName: "", EmailID: "", Password: "password"}
16 	m, _ := json.Marshal(u)
17 	fmt.Println(string(m))
18 }

The output is:

{}

Let’s look at another example:

Program: json5.go


 1 package main
 2 
 3 import (
 4         "encoding/json"
 5         "fmt"
 6 )
 7 
 8 type Response1 struct {
 9         Page   int
10         Conf []string
11 }
12 
13 type Response2 struct {
14     Page   int      `json:"page"` // Field appears in JSON as key "page"
15     Conf []string   `json:"conf"` // Field appears in JSON as key "conf"
16 }
17 
18 func main() {
19         // encoding basic data types to JSON strings
20         bolB, _ := json.Marshal(false)
21         fmt.Println(string(bolB))
22 
23         intB, _ := json.Marshal(3)
24         fmt.Println(string(intB))
25 
26         fltB, _ := json.Marshal(87.23)
27         fmt.Println(string(fltB))
28 
29         strB, _ := json.Marshal("go class")
30         fmt.Println(string(strB))
31 
32         // encoding a slice
33         slcD := []string{"gophercon", "gopherconindia", "go china"}
34         slcB, _ := json.Marshal(slcD)
35         fmt.Println(string(slcB))
36 
37         // encoding a map
38         // the map's key type must be a string
39         mapD := map[string]int{"pune": 5, "franklin": 7}
40         mapB, _ := json.Marshal(mapD)
41         fmt.Println(string(mapB))
42 
43         // The JSON package can automatically encode your
44         // custom data types. It will only include exported
45         // fields in the encoded output and will by default
46         // use those names as the JSON keys.
47         res1D := &Response1{
48                 Page: 1,
49                 Conf: []string{"gophercon", "gopherconindia", "go china"}}
50         res1B, _ := json.Marshal(res1D)
51         fmt.Println(string(res1B))
52 
53         // You can use tags on struct field declarations to
54         // customize the encoded JSON key names. Check the 
55         // definition of Response2 above to see an example of
56         // such tags.
57         res2D := &Response2{
58                 Page: 1,
59                 Conf: []string{"gophercon", "gopherconindia", "go china"}}
60         res2B, _ := json.Marshal(res2D)
61         fmt.Println(string(res2B))
62 
63         // The last 2 lines above can be replaced with
64         json.NewEncoder(os.Stdout).Encode(res2D)
65 }

The output is:

false
3
87.23
"go class"
["gophercon","gopherconindia","go china"]
{"franklin":7,"pune":5}
{"Page":1,"Conf":["gophercon","gopherconindia","go china"]}
{"page":1,"conf":["gophercon","gopherconindia","go china"]}

func NewEncoder(w io.Writer) *Encoder

NewEncoder returns a new encoder that writes to w.

func (enc *Encoder) Encode(v interface{}) error

Encode writes the JSON encoding of v to the stream, followed by a newline character.

Tip:

The json package only accesses the exported fields of struct types (those that begin with an uppercase letter). Therefore only the exported fields of a struct will be present in the JSON output.

Errors are less common during marshals, but they can occur if Go can’t figure out how to convert one of your types to JSON. For example if you try to marshal something containing a nil pointer.

If you don’t want to deal with handling errors in every marshal, and marshal errors are suitably uncommon, one option is to convert errors into panics with a MustMarshal function:

func MustMarshal(data interface{}) []byte {
        out, err := json.Marshal(data)
        if err != nil {
                panic(err)
        }

       return out
}

18.2.3 Decoding

To decode JSON data we use the Unmarshal function.

func Unmarshal(data []byte, v interface{}) error

We must first create a place where the decoded data will be stored.

var m Message

and call json.Unmarshal, passing it a []byte of JSON data and a pointer to m.

err := json.Unmarshal(b, &m)

If b contains valid JSON that fits in m, after the call err will be nil and the data from b will have been stored in the struct m, as if by an assignment like:

1 m = Message {
2             Name: "Alice",
3             Body: "Hello",
4             Time: 1294706395881547000,
5 }

How does Unmarshal identify the fields in which to store the decoded data? For a given JSON key “Foo”, Unmarshal will look through the destination struct’s fields to find (in order of preference):

  • An exported field with a tag of “Foo” (see the Go spec for more on struct tags),
  • An exported field named “Foo”, or
  • An exported field named “FOO” or “FoO” or some other case-insensitive match of “Foo”.

What happens when the structure of the JSON data doesn’t exactly match the Go type?

1 b := []byte(`{"Name":"Bob","Food":"Pickle"}`)
2 var m Message
3 err := json.Unmarshal(b, &m)

Unmarshal will decode only the fields that it can find in the destination type. In this case, only the Name field of m will be populated, and the Food field will be ignored. This behavior is particularly useful when you wish to pick only a few specific fields out of a large JSON blob. It also means that any unexported fields in the destination struct will be unaffected by Unmarshal.

Unmarshal stores in the interface value: map[string]interface{}, for JSON objects.

Here’s an example:

Program: json6.go


 1 package main
 2 
 3 import (
 4         "encoding/json"
 5         "fmt"
 6 )
 7 
 8 type Response2 struct {
 9     Page   int      `json:"page"`
10     Conf []string   `json:"conf"`
11 }
12 
13 func main() {
14         // We can also decode JSON into custom data types. 
15         // This has the advantages of adding additional 
16         // type-safety to our programs and eliminating the
17         // need for type assertions when accessing the decoded data.
18         // `Unmarshal` will decode only the fields that it can find 
19         // in the destination type. In this case, only the Page and
20         // Conf field of res will be populated, and the Food field
21         // will be ignored.
22         str := `{"page": 1, "conf": ["gophercon", "gopherconindia"], "Food":"Pickle"}`
23         res := &Response2{}
24         json.Unmarshal([]byte(str), &res)
25         fmt.Println(res)
26         fmt.Println(res.Conf[0])
27 }

The output is:

&{1 [gophercon gopherconindia]}
gophercon

18.2.4 Streaming Encoders and Decoders

The json package provides Decoder and Encoder types to support the common operation of reading and writing streams of JSON data. The NewDecoder and NewEncoder functions wrap the io.Reader and io.Writer interface types.

func NewDecoder(r io.Reader) *Decoder

func NewEncoder(w io.Writer) *Encoder

func (enc *Encoder) Encode(v interface{}) error

Encode1 writes the JSON encoding of v to the stream, followed by a newline character.

func (dec *Decoder) Decode(v interface{}) error

Decode2 reads the next JSON-encoded value from its input and stores it in the value pointed to by v.

Previously, to encode JSON data we had used the Marshal function.

Program: json1.go


 1 package main
 2 
 3 import (
 4         "encoding/json"
 5         "fmt"
 6 )        
 7 
 8 type User struct {
 9 	UserName string
10 	EmailID  string
11 	Password  string
12 }
13 
14 func main() {
15 	u := User{UserName: "IndianGuru", EmailID: "satishtalim@gmail.com", Password: "password"}
16 	m, _ := json.Marshal(u)
17 	fmt.Println(string(m))
18 }

The output was:

{"UserName":"IndianGuru","EmailID":"satishtalim@gmail.com","Password":"password"}

Let’s rewrite the above program using json.NewEncoder as follows:

Program: json7.go


 1 package main
 2 
 3 import (
 4         "encoding/json"
 5         "log"
 6         "os"
 7 )        
 8 
 9 type User struct {
10 	UserName string
11 	EmailID  string
12 	Password  string
13 }
14 
15 func main() {
16 	u := User{UserName: "IndianGuru", EmailID: "satishtalim@gmail.com", Password: "password"}
17 	enc := json.NewEncoder(os.Stdout)
18 	if err := enc.Encode(u); err != nil {
19             log.Println(err)
20         }
21 }

The output is the same as before:

{"UserName":"IndianGuru","EmailID":"satishtalim@gmail.com","Password":"password"}

These Encoder and Decoder types can be used in a broad range of scenarios, such as reading and writing to HTTP connections, WebSockets, or files.

18.3 A Fun, Weather Forecast Go Web App

We shall build a fun Go app that displays the current weather forecast for a given city.

18.3.1 Register for an account at Forecast for Developers

Go to https://developer.forecast.io/register3 and register for an account. We shall use their free plan that allows us to use 1000 calls to their API per day. As such, there is no need to enter your credit card details. Also, this page gives you your very own API key that you will use in your program.

18.3.1.1 Study the API documentation

We need to read thro’ the API documentation4 of the Forecast for Developers.

Very briefly:

The Forecast Call

https://api.forecast.io/forecast/APIKEY/LATITUDE,LONGITUDE?units=ca

APIKEY should be your API key as mentioned above. LATITUDE and LONGITUDE should be the geographic coordinates of a location in decimal degrees.

The response will be a JSON-formatted object with the following properties defined:

  • latitude: The requested latitude.
  • longitude: The requested longitude.
  • timezone: The timezone name for the requested location (e.g. America/New_York).
  • offset: The current timezone offset in hours from GMT.
  • currently: A data point (see below) containing the current weather conditions at the requested location.

The following JSON Schema of the API is very informative - forecast.json5

Data Points

A data point object represents the various weather phenomena occurring at a specific instant of time, and has many varied properties. All of these properties (except time) are optional, and will only be set if we have that type of information for that location and time.

The following JSON Schema of the API is very informative - datapoint.json6

We shall use the two schema (datapoint.json and forecast.json) in our program.

Let us define our structs DataPoint and Forecast based on these two schema as follows:

 1 type DataPoint struct {
 2         Time                   float64
 3         Summary                string
 4         Icon                   string
 5         SunriseTime            float64
 6         SunsetTime             float64
 7         PrecipIntensity        float64
 8         PrecipIntensityMax     float64
 9         PrecipIntensityMaxTime float64
10         PrecipProbability      float64
11         PrecipType             string
12         PrecipAccumulation     float64
13         Temperature            float64
14         TemperatureMin         float64
15         TemperatureMinTime     float64
16         TemperatureMax         float64
17         TemperatureMaxTime     float64
18         DewPoint               float64
19         WindSpeed              float64
20         WindBearing            float64
21         CloudCover             float64
22         Humidity               float64
23         Pressure               float64
24         Visibility             float64
25         Ozone                  float64
26 }
27 
28 type Forecast struct {
29         Latitude  float64
30         Longitude float64
31         Timezone  string
32         Offset    float64
33         Currently DataPoint
34         Junk      string
35 }

Understanding url query string

A query string is a part of an URL that contains data that can be passed to web applications. This data needs to be encoded, and this encoding is done using url.QueryEscape. It performs what is also commonly called URL encoding7.

1         // addr is a string which contains our address
2         addr := "Pune,India"
3 
4         // QueryEscape escapes the addr string so
5         // it can be safely placed inside a URL query
6         safeAddr := url.QueryEscape(addr)
7         fmt.Println(safeAddr) // Pune%2CIndia

With url.QueryEscape our address “Pune,India” becomes “Pune%2CIndia” where %2C is the ASCII keycode in hexadecimal for a comma (,).

The Google Geocoding API

We shall use The Google Geocoding API8 to help us convert addresses (like “1600 Amphitheatre Parkway, Mountain View, CA”) into geographic coordinates (like latitude 37.423021 and longitude -122.083739).

To access the Geocoding API over HTTP, use:

http://maps.googleapis.com/maps/api/geocode/output?parameters

where output may be either of the following values:

  • json (recommended) indicates output in JavaScript Object Notation (JSON)
  • xml indicates output as XML

Some parameters are required while some are optional. As is standard in URLs, parameters are separated using the ampersand (&) character.

Required parameters in a geocoding request:

address — The street address that you want to geocode, in the format used by the national postal service of the country concerned. Additional address elements such as business names and unit, suite or floor numbers should be avoided.

We are not using any optional parameters in our geocoding request.

1 // Geocoding API
2 fullUrl := fmt.Sprintf("http://maps.googleapis.com/maps/api/geocode/json?address=%s", safe\
3 Addr)
4 fmt.Println(fullUrl)

The output is:

http://maps.googleapis.com/maps/api/geocode/json?address=Pune%2C+India

In your browser, open the site http://maps.googleapis.com/maps/api/geocode/json?address=Pune%2C+India the browser output is a huge blob of JSON. This may be difficult to look at in the browser, unless you have the JSONView plugin installed. These extensions are available for Firefox9 and Chrome10. With the extension installed you should be able to see a better view of the JSOn returned.

Build the http request

func NewRequest(method, urlStr string, body io.Reader) (*Request, error)

NewRequest11 returns a new Request given a method, URL, and an optional body.

1         // Build the http request
2         req, err1 := http.NewRequest("GET", fullUrl, nil)
3         check(err1, "NewRequest:")

Also, we write a check function that checks for errors, if any:

1 func check(e error, str string) {
2         if e != nil {
3                 log.Fatal(str, " ", e)
4                 return
5         }
6 }

Create a Client

For control over HTTP client headers, redirect policy, and other settings, create a Client12. A Client is an HTTP client:

client := &http.Client{}

Send the request via a client

func (c *Client) Do(req *Request) (resp *Response, err error)

Do sends an HTTP request and returns an HTTP response.

1         resp, err2 := client.Do(req)
2         check(err2, "Do:")
3 
4         // Callers should close resp.Body
5         // when done reading from it
6         // Defer the closing of the body
7         defer resp.Body.Close()

Read the content into a byte array

func ReadAll(r io.Reader) ([]byte, error)

ReadAll13 reads from r until an error or EOF and returns the data it read. A successful call returns err == nil, not err == EOF. Because ReadAll is defined to read from src until EOF, it does not treat an EOF from Read as an error to be reported.

1         body, dataReadErr := ioutil.ReadAll(resp.Body)
2         check(dataReadErr, "ReadAll:")

Create a map

The json package uses map[string]interface{} and []interface{} values to store arbitrary JSON objects and arrays.

Observe the JSON output when you open the site http://maps.googleapis.com/maps/api/geocode/json?address=Pune%2C+India in your browser. There is a results array, within which there is geometry, then location which finally contains lat and lng.

Therefore let’s define our map as follows:

res := make(map[string][]map[string]map[string]map[string]interface{}, 0)

0 above is the initial capacity of the map.

Using the Unmarshal function

We will be using the Unmarshal function to transform our JSON bytes into the appropriate structure. The Unmarshal function accepts a byte array and a reference to the object which shall be filled with the JSON data (this is simplifying, it actually accepts an interface).

json.Unmarshal(body, &res)

Extract the latitude and logitude

1         // lat, lng as float64
2         lat, _ := res["results"][0]["geometry"]["location"]["lat"]
3         lng, _ := res["results"][0]["geometry"]["location"]["lng"]

** Use the Forecast API**

Declare a global constant APIKey as: const APIKey string = "yourapikey"

safeLatLng := url.QueryEscape(fmt.Sprintf("%.13f,%.13f", lat, lng)) url := fmt.Sprintf("https://api.forecast.io/forecast/%s/%s?units=ca", APIKey, safeLatLng)

  • Remember to replace APIKey above with your actual api key.
  • %.13f is used to convert float64 to a string
  • ?units=ca - the API request was optionally modified through the use of query parameter units=ca will return temperatures in degrees Celsius.

Use http.Get and ioutil.ReadAll

func Get(url string) (resp *Response, err error)

Get14 issues a GET to the specified URL.

1         resp, err := http.Get(url)
2         check(err, "Get:")
3         defer resp.Body.Close()
4 
5         fbody, err := ioutil.ReadAll(resp.Body)
6         check(err, "ReadAll:")

Use the Forecast struct to get the results

1         var f Forecast
2         json.Unmarshal(fbody, &f)
3 
4         fmt.Println("The Weather at ", addr)
5         fmt.Println("Timezone = ", f.Timezone)
6         fmt.Println("Temp in Celsius = ", f.Currently.Temperature)
7         fmt.Println("Summary = ", f.Currently.Summary)

Here’s the full program:

Program: weather.go


  1 package main
  2 
  3 import (
  4 	"encoding/json"
  5 	"fmt"
  6 	"io/ioutil"
  7 	"log"
  8 	"net/http"
  9 	"net/url"
 10 )
 11 
 12 type DataPoint struct {
 13 	Time                   float64
 14 	Summary                string
 15 	Icon                   string
 16 	SunriseTime            float64
 17 	SunsetTime             float64
 18 	PrecipIntensity        float64
 19 	PrecipIntensityMax     float64
 20 	PrecipIntensityMaxTime float64
 21 	PrecipProbability      float64
 22 	PrecipType             string
 23 	PrecipAccumulation     float64
 24 	Temperature            float64
 25 	TemperatureMin         float64
 26 	TemperatureMinTime     float64
 27 	TemperatureMax         float64
 28 	TemperatureMaxTime     float64
 29 	DewPoint               float64
 30 	WindSpeed              float64
 31 	WindBearing            float64
 32 	CloudCover             float64
 33 	Humidity               float64
 34 	Pressure               float64
 35 	Visibility             float64
 36 	Ozone                  float64
 37 }
 38 
 39 type Forecast struct {
 40 	Latitude  float64
 41 	Longitude float64
 42 	Timezone  string
 43 	Offset    float64
 44 	Currently DataPoint
 45 	Junk      string
 46 }
 47 
 48 // Replace the text `yourapikey` below with your actual api key
 49 const APIKey string = "yourapikey"
 50 
 51 func main() {
 52 	Get()
 53 }
 54 
 55 func check(e error, str string) {
 56 	if e != nil {
 57 		log.Fatal(str, " ", e)
 58 		return
 59 	}
 60 }
 61 
 62 func Get() {
 63 	var addr string
 64 
 65 	fmt.Println("Enter City eg. Pune, India: ")
 66 	fmt.Scanf("%s", &addr)
 67 
 68 	// QueryEscape escapes the addr string so
 69 	// it can be safely placed inside a URL query
 70 	safeAddr := url.QueryEscape(addr)
 71 
 72 	// Geocoding API
 73 	fullUrl := fmt.Sprintf("http://maps.googleapis.com/maps/api/geocode/json?address=%s", saf\
 74 eAddr)
 75 
 76 	// Build the http request
 77 	req, err1 := http.NewRequest("GET", fullUrl, nil)
 78 	check(err1, "NewRequest:")
 79 
 80 	// For control over HTTP client headers,
 81 	// redirect policy, and other settings,
 82 	// create a Client
 83 	// A Client is an HTTP client
 84 	client := &http.Client{}
 85 
 86 	// Send the request via a client
 87 	// Do sends an HTTP request and
 88 	// returns an HTTP response
 89 	resp, err2 := client.Do(req)
 90 	check(err2, "Do:")
 91 
 92 	// Callers should close resp.Body
 93 	// when done reading from it
 94 	// Defer the closing of the body
 95 	defer resp.Body.Close()
 96 
 97 	// Read the content into a byte array
 98 	body, dataReadErr := ioutil.ReadAll(resp.Body)
 99 	check(dataReadErr, "ReadAll:")
100 
101 	res := make(map[string][]map[string]map[string]map[string]interface{}, 0)
102 
103 	// We will be using the Unmarshal function
104 	// to transform our JSON bytes into the
105 	// appropriate structure.
106 	// The Unmarshal function accepts a byte array
107 	// and a reference to the object which shall be
108 	// filled with the JSON data (this is simplifying,
109 	// it actually accepts an interface)
110 	json.Unmarshal(body, &res)
111 
112 	// lat, lng as float64
113 	lat, _ := res["results"][0]["geometry"]["location"]["lat"]
114 	lng, _ := res["results"][0]["geometry"]["location"]["lng"]
115 
116         safeLatLng := url.QueryEscape(fmt.Sprintf("%.13f,%.13f", lat, lng))
117         url := fmt.Sprintf("https://api.forecast.io/forecast/%s/%s?units=ca", APIKey, safe\
118 LatLng)
119 
120 	resp, err := http.Get(url)
121 	check(err, "Get:")
122 	defer resp.Body.Close()
123 
124 	fbody, err := ioutil.ReadAll(resp.Body)
125 	check(err, "ReadAll:")
126 
127 	var f Forecast
128 	json.Unmarshal(fbody, &f)
129 
130 	fmt.Println("The Weather at ", addr)
131 	fmt.Println("Timezone = ", f.Timezone)
132 	fmt.Println("Temp in Celsius = ", f.Currently.Temperature)
133 	fmt.Println("Summary = ", f.Currently.Summary)
134 }

The output is:

Enter City eg. Pune, India:
Pune, India
The Weather at  Pune,
Timezone =  Asia/Kolkata
Temp in Celsius =  30.59
Summary =  Mostly Cloudy

Previously we had used a map in our program, namely:

res := make(map[string][]map[string]map[string]map[string]interface{}, 0)

Let’s use a struct instead:

 1 type Response struct {
 2         Results []struct {
 3                 Geometry struct {
 4                         Location struct {
 5                                 Lat float64
 6                                 Lng float64
 7                         }
 8                 }
 9         }
10 }

The modified program is:

Program: weather_new.go


  1 package main
  2 
  3 import (
  4         "encoding/json"
  5         "fmt"
  6         "io/ioutil"
  7         "log"
  8         "net/http"
  9         "net/url"
 10 )
 11 
 12 type DataPoint struct {
 13         Time                   float64
 14         Summary                string
 15         Icon                   string
 16         SunriseTime            float64
 17         SunsetTime             float64
 18         PrecipIntensity        float64
 19         PrecipIntensityMax     float64
 20         PrecipIntensityMaxTime float64
 21         PrecipProbability      float64
 22         PrecipType             string
 23         PrecipAccumulation     float64
 24         Temperature            float64
 25         TemperatureMin         float64
 26         TemperatureMinTime     float64
 27         TemperatureMax         float64
 28         TemperatureMaxTime     float64
 29         DewPoint               float64
 30         WindSpeed              float64
 31         WindBearing            float64
 32         CloudCover             float64
 33         Humidity               float64
 34         Pressure               float64
 35         Visibility             float64
 36         Ozone                  float64
 37 }
 38 
 39 type Forecast struct {
 40         Latitude  float64
 41         Longitude float64
 42         Timezone  string
 43         Offset    float64
 44         Currently DataPoint
 45         Junk      string
 46 }
 47 
 48 
 49 type Response struct {
 50         Results []struct {
 51                 Geometry struct {
 52                         Location struct {
 53                                 Lat float64
 54                                 Lng float64
 55                         }
 56                 }
 57         }
 58 }
 59 
 60 // Replace the text `yourapikey` below with your actual api key
 61 const APIKey string = "yourapikey"
 62 
 63 func main() {
 64         Get() 
 65 }
 66 
 67 func check(e error, str string) {
 68         if e != nil {
 69                 log.Fatal(str, " ", e)
 70                 return
 71         }
 72 }
 73 
 74 func Get() {        
 75         var addr string
 76 
 77         fmt.Println("Enter City eg. Pune, India: ")
 78         fmt.Scanf("%s", &addr)        
 79 
 80         // QueryEscape escapes the addr string so
 81         // it can be safely placed inside a URL query
 82         safeAddr := url.QueryEscape(addr)
 83 
 84         // Geocoding API
 85         fullUrl := fmt.Sprintf("http://maps.googleapis.com/maps/api/geocode/json?address=%\
 86 s", safeAddr)
 87 
 88         // Build the http request
 89         req, err1 := http.NewRequest("GET", fullUrl, nil)
 90         check(err1, "NewRequest:")
 91 
 92         // For control over HTTP client headers,
 93         // redirect policy, and other settings,
 94         // create a Client
 95         // A Client is an HTTP client
 96         client := &http.Client{}
 97 
 98         // Send the request via a client
 99         // Do sends an HTTP request and
100         // returns an HTTP response
101         resp, err2 := client.Do(req)
102         check(err2, "Do:")
103 
104         // Callers should close resp.Body
105         // when done reading from it
106         // Defer the closing of the body
107         defer resp.Body.Close()
108 
109         // Read the content into a byte array
110         body, dataReadErr := ioutil.ReadAll(resp.Body)
111         check(dataReadErr, "ReadAll:")
112 
113         var res Response
114 
115         // We will be using the Unmarshal function
116         // to transform our JSON bytes into the
117         // appropriate structure.
118         // The Unmarshal function accepts a byte array
119         // and a reference to the object which shall be
120         // filled with the JSON data (this is simplifying,
121         // it actually accepts an interface)
122         json.Unmarshal(body, &res)
123         
124         // lat, lng as float64
125         lat := res.Results[0].Geometry.Location.Lat
126         lng := res.Results[0].Geometry.Location.Lng
127 
128         safeLatLng := url.QueryEscape(fmt.Sprintf("%.13f,%.13f", lat, lng))
129         url := fmt.Sprintf("https://api.forecast.io/forecast/%s/%s?units=ca", APIKey, safe\
130 LatLng)
131 
132         resp, err := http.Get(url)
133         check(err, "Get:")
134         defer resp.Body.Close()
135 
136         fbody, err := ioutil.ReadAll(resp.Body)
137         check(err, "ReadAll:")
138          
139         var f Forecast
140         json.Unmarshal(fbody, &f)
141 
142         fmt.Println("The Weather at ", addr)
143         fmt.Println("Timezone = ", f.Timezone)
144         fmt.Println("Temp in Celsius = ", f.Currently.Temperature)
145         fmt.Println("Summary = ", f.Currently.Summary)
146 }

The output is:

Enter City eg. Pune, India:
Pune, India
The Weather at  Pune,
Timezone =  Asia/Kolkata
Temp in Celsius =  30.59
Summary =  Mostly Cloudy

That’s it!

  1. http://golang.org/pkg/encoding/json/#Encoder.Encode
  2. http://golang.org/pkg/encoding/json/#Decoder.Decode
  3. https://developer.forecast.io/register
  4. https://developer.forecast.io/docs/v2
  5. https://github.com/kingsfleet/rest-metadata/blob/master/forecast.io/forecast.json
  6. https://github.com/kingsfleet/rest-metadata/blob/master/forecast.io/datapoint.json
  7. http://en.wikipedia.org/wiki/Query_string#URL_encoding
  8. https://developers.google.com/maps/documentation/geocoding/
  9. https://addons.mozilla.org/en-us/firefox/addon/jsonview/
  10. https://chrome.google.com/webstore/detail/jsonview/chklaanhfefbnpoihckbnefhakgolnmc
  11. http://golang.org/pkg/net/http/#NewRequest
  12. http://golang.org/pkg/net/http/#Client
  13. https://golang.org/pkg/io/ioutil/#ReadAll)
  14. http://golang.org/pkg/net/http/#Get