Golang REST api request with token auth to json array reponse

Issue

EDIT
This is the working code incase someone finds it useful. The title to this question was originally
"How to parse a list fo dicts in golang".

This is title is incorrect because I was referencing terms I’m familiar with in python.

package main

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
)

//Regional Strut
type Region []struct {
    Region      string `json:"region"`
    Description string `json:"Description"`
    ID          int    `json:"Id"`
    Name        string `json:"Name"`
    Status      int    `json:"Status"`
    Nodes       []struct {
        NodeID    int    `json:"NodeId"`
        Code      string `json:"Code"`
        Continent string `json:"Continent"`
        City      string `json:"City"`
    } `json:"Nodes"`
}

//working request and response
func main() {
    url := "https://api.geo.com"

    // Create a Bearer string by appending string access token
    var bearer = "TOK:" + "TOKEN"

    // Create a new request using http
    req, err := http.NewRequest("GET", url, nil)

    // add authorization header to the req
    req.Header.Add("Authorization", bearer)

    //This is what the response from the API looks like
    //regionJson := `[{"region":"GEO:ABC","Description":"ABCLand","Id":1,"Name":"ABCLand [GEO-ABC]","Status":1,"Nodes":[{"NodeId":17,"Code":"LAX","Continent":"North America","City":"Los Angeles"},{"NodeId":18,"Code":"LBC","Continent":"North America","City":"Long Beach"}]},{"region":"GEO:DEF","Description":"DEFLand","Id":2,"Name":"DEFLand","Status":1,"Nodes":[{"NodeId":15,"Code":"NRT","Continent":"Asia","City":"Narita"},{"NodeId":31,"Code":"TYO","Continent":"Asia","City":"Tokyo"}]}]`
    //Send req using http Client
    client := &http.Client{}
    resp, err := client.Do(req)

    if err != nil {
        log.Println("Error on response.\n[ERROR] -", err)
    }
    defer resp.Body.Close()

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        log.Println("Error while reading the response bytes:", err)
    }

    var regions []Region
    json.Unmarshal([]byte(body), &regions)
    fmt.Printf("Regions: %+v", regions)
}

Solution

Have a look at this playground example for some pointers.

Here’s the code:

package main

import (
    "encoding/json"
    "log"
)

func main() {
    b := []byte(`
[
  {"key": "value", "key2": "value2"},
  {"key": "value", "key2": "value2"}
]`)

    var mm []map[string]string
    if err := json.Unmarshal(b, &mm); err != nil {
        log.Fatal(err)
    }

    for _, m := range mm {
        for k, v := range m {
            log.Printf("%s [%s]", k, v)
        }
    }
}

I reformatted the API response you included because it is not valid JSON.

In Go it’s necessary to define types to match the JSON schema.

I don’t know why the API appends % to the end of the result so I’ve ignored that. If it is included, you will need to trim the results from the file before unmarshaling.

What you get from the unmarshaling is a slice of maps. Then, you can iterate over the slice to get each map and then iterate over each map to extract the keys and values.

Update

In your updated question, you include a different JSON schema and this change must be reflect in the Go code by update the types. There are some other errors in your code. Per my comment, I encourage you to spend some time learning the language.

package main

import (
    "bytes"
    "encoding/json"
    "io/ioutil"
    "log"
)

// Response is a type that represents the API response
type Response []Record

// Record is a type that represents the individual records
// The name Record is arbitrary as it is unnamed in the response
// Golang supports struct tags to map the JSON properties
// e.g. JSON "region" maps to a Golang field "Region"
type Record struct {
    Region      string `json:"region"`
    Description string `json:"description"`
    ID          int    `json:"id"`
    Nodes       []Node
}
type Node struct {
    NodeID int    `json:"NodeId`
    Code   string `json:"Code"`
}

func main() {
    // A slice of byte representing your example response
    b := []byte(`[{
        "region": "GEO:ABC",
        "Description": "ABCLand",
        "Id": 1,
        "Name": "ABCLand [GEO-ABC]",
        "Status": 1,
        "Nodes": [{
            "NodeId": 17,
            "Code": "LAX",
            "Continent": "North America",
            "City": "Los Angeles"
        }, {
            "NodeId": 18,
            "Code": "LBC",
            "Continent": "North America",
            "City": "Long Beach"
        }]
    }, {
        "region": "GEO:DEF",
        "Description": "DEFLand",
        "Id": 2,
        "Name": "DEFLand",
        "Status": 1,
        "Nodes": [{
            "NodeId": 15,
            "Code": "NRT",
            "Continent": "Asia",
            "City": "Narita"
        }, {
            "NodeId": 31,
            "Code": "TYO",
            "Continent": "Asia",
            "City": "Tokyo"
        }]
    }]`)

    // To more closely match your code, create a Reader
    rdr := bytes.NewReader(b)

    // This matches your code, read from the Reader
    body, err := ioutil.ReadAll(rdr)
    if err != nil {
        // Use Printf to format strings
        log.Printf("Error while reading the response bytes\n%s", err)
    }

    // Initialize a variable of type Response
    resp := &Response{}
    // Try unmarshaling the body into it
    if err := json.Unmarshal(body, resp); err != nil {
        log.Fatal(err)
    }

    // Print the result
    log.Printf("%+v", resp)
}

Answered By – DazWilkin

Answer Checked By – Cary Denson (GoLangFix Admin)

Leave a Reply

Your email address will not be published.