1

I'm learning Go by writing a simple http server and I need to handle some JSON responses.

With an object response, I can unmarshal it idiomatically with 2 lines of code: structResult := Foo{} json.Unmarshal(structBody, &structResult)

I don't know how to do the same for an array response (see the example below). Is there a way to specify (possibly via json tag) that top-level array should go into a given struct field?

package main

import "fmt"
import "encoding/json"

type Foo struct {
    Id uint64 `json:"id"`
    Name string `json:"name"`
}

type BaseResult struct {
    Error string  `json:"error"`
}

type FooResult struct {
    BaseResult
    Foos []Foo
}

func main() {
    // Simple and works.
    structBody := []byte(`{"id": 1,"name": "foo"}`)
    structResult := Foo{}
    json.Unmarshal(structBody, &structResult)
    fmt.Printf("%#v\n", structResult)

    // Doesn't work.
    arrayBody := []byte(`[{"id": 1,"name": "foo"},{"id": 2,"name": "bar"},{"id": 3,"name": "foobar"}]`) 
    arrayResult := FooResult{}
    json.Unmarshal(arrayBody, &arrayResult)
    fmt.Printf("%#v\n", arrayResult)
}

I know I could make FooResult an array:

type FooResult []Foo

but then I lose the ability to specify base object which I would like to use to store error message and such. I also know that I can unmarshal into &fooResult.Foos directly, but I want the code to work with both objects and arrays.

UPDATE

Implementing UnmarshalJSON as suggested by @dyoo partially solves my problem, but I was hoping that I could use BaseResult to store parse error in case JSON has a different structure:

arrayBody := []byte(`{"error": "foo"}`)
arrayResult := FooResult{}
json.Unmarshal(arrayBody, &arrayResult)
fmt.Printf("%#v\n", arrayResult)

Of course I could implement more complex logic inside UnmarshalJSON - but isn't there a simpler way to do it?

2
  • Recommendation: rename FooResult to ArrayResult. Using Foo as any part of a name is often dubious because it doesn't mean anything. Commented Sep 25, 2014 at 18:16
  • Sure, I only used the name "foo" in this example. Commented Sep 26, 2014 at 8:55

2 Answers 2

2

You can implement the json.Unmarshaler interface in your FooResult, to customize exactly how it responds to unmarshaling. (Similarly, there's a json.Marshaler interface.)

Add:

func (f *FooResult) UnmarshalJSON(bs []byte) error {
    return json.Unmarshal(bs, &f.Foos)
}

after which your code should otherwise work. http://play.golang.org/p/oMdoB2e-rB

You might try something like:

func (f *FooResult) UnmarshalJSON(bs []byte) error {
    err1 := json.Unmarshal(bs, &f.BaseResult)
    err2 := json.Unmarshal(bs, &f.Foos)
    if err1 != nil && err2 != nil {
        // Arbitrarily choose an error.
        return err1
    }
    return nil
}

although even this is beginning to look dubious. Handling union type results is not quite what the json library is designed to handle automatically for you. You'll need to explicitly code the coercion logic if your JSON has dynamic type.

See: How to unmarshall an array of different types correctly? and http://blog.golang.org/json-and-go for related issues.

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you. This answers my problem, but the real reason for my question was that I was hoping to use base struct to store error in case when returned JSON has different structure (many APIs are enveloped this way). I wonder if that's possible.
You don't need to implement UnmarshalJSON, just specify Foos when you unmarshal. I have an example in my answer below.
0

Just specify Foos when you Unmarshal

package main

import "fmt"
import "encoding/json"

type Foo struct {
    Id   uint64 `json:"id"`
    Name string `json:"name"`
}

type BaseResult struct {
    Error string `json:"error"`
}

type FooResult struct {
    BaseResult
    Foos []Foo
}

func main() {
    // Simple and works.
    structBody := []byte(`{"id": 1,"name": "foo"}`)
    structResult := Foo{}
    json.Unmarshal(structBody, &structResult)
    fmt.Printf("%#v\n", structResult)

    // Doesn't work.
    arrayBody := []byte(`[{"id": 1,"name": "foo"},{"id": 2,"name": "bar"},{"id": 3,"name": "foobar"}]`)
    arrayResult := FooResult{}
    if err := json.Unmarshal(arrayBody, &arrayResult.Foos); err != nil {
        arrayResult.BaseResult.Error = string(arrayBody)
    }

    fmt.Printf("%#v\n", arrayResult)
}

1 Comment

Thank you. I know I can do that, but my whole point is to keep parsing logic close to struct and avoid writing code for each different case. @dyoo's solution via UnmarshalJSON is nicer.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.