That Dave Guy That Dave Guy - 1 year ago 53
JSON Question

Unmarshal only gives me first element of array that is nested in JSON response

I am trying to work with the Flickr API and I have an endpoint which gives me a response like this

"photos": {
"page": 1,
"pages": 500,
"perpage": 1,
"total": 500,
"photo": [
"id": "12345",
"owner": "12345@N01",
"secret": "12ab345xyz",
"server": "1234",
"farm": 1,
"title": "Some Title",
"ispublic": 1,
"isfriend": 0,
"isfamily": 0
"stat": "ok"

I have a struct that looks like this

type FlickrResp struct {
Photos struct {
Page int `json:"page"` //what page the results were retreived from
Pages int `json:"pages"` //total number of pages of results
Perpage int `json:"perpage"` //number of results per page
Total int `json:"total"` //total number of results
Photo []struct {
ID string `json:"id"` //image ID
Owner string `json:"owner"` //owner ID
Secret string `json:"secret"` //owner secret
Server string `json:"server"` //server ID
Farm int `json:"farm"` //farm ID
Title string `json:"title"` //image title
Ispublic string `json:"ispublic"` //is image publicly viewable?
Isfriend string `json:"isfriend"` //is image owner my friend?
Isfamily string `json:"isfamily"` //is image owner my family?
} `json:"photo"` //array of objects with details of photos
} `json:"photos"` //object that holds information about photoset
Stat string `json:"stat"` //status

and I'm trying to Unmarshal thusly

var fr FlickrResp //create blank struct of type FlickrResp to hold JSON
resp, err := ioutil.ReadAll(flickrCB.Body) //read HTTP GET response
if err != nil {

json.Unmarshal([]byte(resp), &fr) //serialize JSON into template

flickCB.Body comes from an http.Get

The problem is that I'm only getting the first element of the photo array. I can see that I have more than one element when I print the response object cast as a string. What am I doing wrong? I've been banging my head against Unmarshal and more complicated JSON responses for a few days now.

Answer Source

Answered originally in comments, but for a bit more clarification:

There was an uncaught error from json.Unmarshal:

json: cannot unmarshal number into Go value of type string

Caused by a type mismatch between the flicker API giving ispublic, isfriend, and isfamily as integers, but the struct was expecting strings. An easy fix is just to convert them to ints.

Another option for these sorts of situations is to use json.Number, which wraps a basic string from the source json and provides a few convenience methods to convert it to a number later (or just keep it as a string). By doing say

Ispublic json.Number `json:"ispublic"`   //is image publicly viewable?
Isfriend json.Number `json:"isfriend"`   //is image owner my friend?
Isfamily json.Number `json:"isfamily"`

It will work fine, and the result can easily be gotten as a string via fr.Ispublic.String().

A more elaborate change that might be useful in this situation is to use a custom boolean type. The values in question seem to mirror booleans, but integers do not map cleanly to booleans either. A custom deserialized type can be used. Borrowing from this example elsewhere on stackoverflow:

type ConvertibleBoolean bool

func (bit ConvertibleBoolean) UnmarshalJSON(data []byte) error {
    asString := string(data)
    if asString == "1" || asString == "true" {
        bit = true
    } else if asString == "0" || asString == "false" {
        bit = false
    } else {
        return errors.New(fmt.Sprintf("Boolean unmarshal error: invalid input %s", asString))
    return nil

The above would let you declare the Is* values as ConvertibleBoolean, and will automatically map the 0 & 1 values you'd presumably get in those fields from Flickr to booleans.