Reputation: 37177
I'm asking this about Go's encoding/json
, but I guess it also applies to any other JSON libraries that map JSON blobs to objects in whatever language.
Here's an example. If you want to a shorten a URL using the goo.gl URL shortener API, you get back either a successful response:
{
"kind": "urlshortener#url",
"id": "http://goo.gl/fbsS",
"longUrl": "http://www.google.com/"
}
Or an error response:
{
"error": {
"errors": [
{
"domain": "global",
"reason": "required",
"message": "Required",
"locationType": "parameter",
"location": "resource.longUrl"
}
],
"code": 400,
"message": "Required"
}
}
Is there an idiomatic way of dealing with this -- a response that could adhere to two completely different schemas?
Normally I deal with JSON using maps/lists; I know that's possible in Go. I could unmarshal to a map[string]interface{}
and then check if the map has "error"
as a key. But then I'd have to decode again into a proper struct
, I think. (Am I wrong?)
I'm doing something like this. I have one type for each kind of response:
type successResponse struct {
Kind string
Id string
LongUrl string
}
type errorResponse struct {
Error struct {
Errors []struct {
Domain string
Reason string
Message string
LocationType string
Location string
}
Code int
Message string
}
}
And decoding looks like this:
s := new(successResponse)
err := json.Unmarshal(blob, s)
if err == nil {
// handle success
} else {
e := new(errorResponse)
err = json.Unmarshal(blob, e)
if err == nil {
// handle error response
} else {
// handle actual error
}
}
But that seems kind of ugly. How should I approach this?
Upvotes: 10
Views: 6312
Reputation: 6943
I was confused about this, too, and thought I had to decode it again. You don't, though. You just have to typecast the interface{} data into the appropriate structure.
For example if the json package has put the value into a generic interface{}
, you can typecast it into ErrorType
with error := val.(ErrorType)
.
You can use foo.(type)
in a switch
statement to "do the right thing", if you are parsing based on what type the value is.
I've only been learning Go this week so it's not the prettiest code, but there are some examples in the geodns JSON configuration parsing.
Upvotes: 6
Reputation: 24808
type Response struct {
Kind string
Id string
LongUrl string
Error struct {
Errors []struct {
Domain string
Reason string
Message string
LocationType string
Location string
}
Code int
Message string
}
}
s := Response{}
if err := json.Unmarshal(blob, &s); err == nil {
if s.Error == nil {
// success
} else {
// error
}
} else {
// something went wrong
}
Upvotes: 3
Reputation: 25237
Since the fields in your json responses are distinct from each other you can just create one struct with the union of all the fields. The json decoder will ignore fields that are not present in the json string and you can test the existence of the fields to know which type of response you are getting back.
Upvotes: 6