29

All of the ways I'm seeing involve building structs and unmarshalling the data into the struct. But what if I'm getting JSON responses with hundreds of fields? I don't want to have to create 100 field structs just to be able to get to the data I want. Coming from a Java background there are easy ways to simply get the http response as a string and then pass the JSON string into a JSON object that allows for easy traversal. It's very painless. Is there anything like this in Go?

Java example in pseudo code:

String json = httpResponse.getBody();
JsonObject object = new JsonObject(json); 
object.get("desiredKey");
  • 1
    Most of the answers involve interfaces, which don't support indexing, so accessing the elements contained in array values of JSON keys won't be as straight-forward as marshaling to a struct. – hermancain Jan 24 '17 at 00:58
  • @hermancain we can't index interface itself, but we can get the concrete type (such as slice) from the interface, and then we can use indexing. – starriet Feb 14 '22 at 09:41

4 Answers4

33

Golang: fetch JSON from an HTTP response without using structs as helpers

This is a typical scenario we come across. This is achieved by json.Unmarshal.

Here is a simple json

{"textfield":"I'm a text.","num":1234,"list":[1,2,3]}

which is serialized to send across the network and unmarshaled at Golang end.

package main

import (
    "fmt"
    "encoding/json"
)

func main() {
    // replace this by fetching actual response body
    responseBody := `{"textfield":"I'm a text.","num":1234,"list":[1,2,3]}`
    var data map[string]interface{}
    err := json.Unmarshal([]byte(responseBody), &data)
    if err != nil {
        panic(err)
    }
    fmt.Println(data["list"])
    fmt.Println(data["textfield"])
}

Hope this was helpful.

Vít Kotačka
  • 1,472
  • 1
  • 15
  • 40
Arun G
  • 1,678
  • 15
  • 17
  • 1
    Link to get actual response object http://stackoverflow.com/questions/38807903/how-do-i-handle-plain-text-http-get-response-in-golang – Arun G Jan 24 '17 at 00:47
  • 3
    For newbies like me: `data["list"]`, `data["textfield"]`, ... are interface, so you can use type assertion to get the concrete type. i.e., `data["list"].([]interface{})[0]` will be 1 (this is also an interface, whose concrete type is `int`). Why `[]interface{}`? because `json.Unmarshal` stores JSON array into []interface{}. See the doc of Unmarshal helps a lot. – starriet Feb 14 '22 at 09:48
5

The json.Unmarshal method will unmarshal to a struct that does not contain all the fields in the original JSON object. In other words, you can cherry-pick your fields. Here is an example where FirstName and LastName are cherry-picked and MiddleName is ignored from the json string:

package main

import (
  "encoding/json"
  "fmt"
)

type Person struct {
  FirstName string `json:"first_name"`
  LastName  string `json:"last_name"`
}

func main() {
  jsonString := []byte("{\"first_name\": \"John\", \"last_name\": \"Doe\", \"middle_name\": \"Anderson\"}")

  var person Person
  if err := json.Unmarshal(jsonString, &person); err != nil {
    panic(err)
  }

  fmt.Println(person)
}
daplho
  • 1,113
  • 1
  • 11
  • 25
3

The other answers here are misleading, as they don't show you what happens if you try to go deeper in the Map. This example works fine enough:

package main

import (
   "encoding/json"
   "fmt"
   "net/http"
)

func main() {
   r, e := http.Get("https://github.com/manifest.json")
   if e != nil {
      panic(e)
   }
   body := map[string]interface{}{}
   json.NewDecoder(r.Body).Decode(&body)
   /*
   [map[
      id:com.github.android
      platform:play
      url:https://play.google.com/store/apps/details?id=com.github.android
   ]]
   */
   fmt.Println(body["related_applications"])
}

but if you try to go one level deeper, it fails:

/*
invalid operation: body["related_applications"][0] (type interface {} does not
support indexing)
*/
fmt.Println(body["related_applications"][0])

Instead, you would need to assert type at each level of depth:

/*
map[
   id:com.github.android
   platform:play
   url:https://play.google.com/store/apps/details?id=com.github.android
]
*/
fmt.Println(body["related_applications"].([]interface{})[0])
Nimantha
  • 6,405
  • 6
  • 28
  • 69
Zombo
  • 1
  • 62
  • 391
  • 407
1

You can as well unmarshal it into a map[string]interface{}

body, err := ioutil.ReadAll(resp.Body)
map := &map[string]interface{}{}
json.Unmarshal(body, map)
desiredValue := map["desiredKey"]

The received json must have an object as the most outer element. The map can also contain lists or nested maps, depending on the json.

Christian
  • 3,551
  • 1
  • 28
  • 24
  • 1
    Doesn't work: At first, `map` doesn't accept as a variable, so I changed it to `map_`. However, in the last line this error appeared: `invalid operation: map_["data"] (type *map[string]interface {} does not support indexing)` – Benyamin Jafari Sep 04 '19 at 09:39
  • @BenyaminJafari you need to dereference, i.e. `(*map_)["your_key"]`. – starriet Feb 14 '22 at 10:06