Kyle Chadha
Kyle Chadha

Reputation: 4161

Is it bad practice in Go to json.Decode a json object to an empty interface?

I have a fairly large nested JSON object I want to decode. I could decode this to a well defined nested struct, but an alternate solution I've seen is to just decode it to an empty interface.

Functionally, this works fine. But I'm wondering if behind the scenes I'm incurring a performance penalty (reflecting) when I decode the object from JSON and again when I later marshal it to JSON.

Thoughts? Thanks in advance.

Code:

CustomizationData    interface{} `json:"customizationData" datastore:"-"`

vs.

CustomizationData    struct {
    Items []struct {
        ID     string `json:"id"`
        Images []struct {
            CustomizationState struct {
                Areas []struct {
                    Height float64 `json:"height"`
                    ID     string  `json:"id"`
                    Left   float64 `json:"left"`
                    Parent struct {
                        Height float64 `json:"height"`
                        Left   float64 `json:"left"`
                        Top    float64 `json:"top"`
                        Width  float64 `json:"width"`
                    } `json:"parent"`
                    Rotation float64 `json:"rotation"`
                    Text     string  `json:"text"`
                    Top      float64 `json:"top"`
                    URL      string  `json:"url"`
                    Width    float64 `json:"width"`
                } `json:"areas"`
                BackgroundColor string  `json:"backgroundColor"`
                IsUserSet       bool    `json:"isUserSet"`
                Orientation     float64 `json:"orientation"`
            } `json:"customizationState"`
            SpaceId string `json:"spaceId"`
        } `json:"images"`
        ProductId    float64 `json:"productId"`
        Quantity     float64 `json:"quantity"`
        Sku          string  `json:"sku"`
        TemplateName string  `json:"templateName"`
    } `json:"items"`
    ShippingAddress struct {
        City        string `json:"city"`
        CountryCode string `json:"countryCode"`
        Email       string `json:"email"`
        FirstName   string `json:"firstName"`
        LastName    string `json:"lastName"`
        Line1       string `json:"line1"`
        Phone       string `json:"phone"`
        PostalCode  string `json:"postalCode"`
        State       string `json:"state"`
    } `json:"shippingAddress"`
    TimeStamp string `json:"timeStamp"`
} `json:"customizationData" datastore:"-"

And potentially more.

Upvotes: 3

Views: 2656

Answers (1)

sberry
sberry

Reputation: 132088

It depends entirely on what you intend on doing with the Unmarshalled data.

If you have nested objects / arrays in your json data, then you will end up with nested interfaces. That means you need to explicitly convert your interfaces to the correct type to access their data. In that case you are far better off using the struct in the second example as you will have your data more easily accessible as in myData.Items[0].CustomizationState.Areas[0].Height. Doing that with nested interface conversion is going to be a pain.

On the other hand, if you are just outputting this data, for example as a response to a webservice call, then you don't need to know the structure and can just spit it back out.

Personally, I always use the latter.

I assume you are using the awesome service at http://mervine.net/json2struct to convert your json into usable Go structs.

Here is a link showing the difference in ease of access between the two methods. http://play.golang.org/p/OlJJPZcxT7

And for those who want to stay in-page:

var dataz = `{"foo": ["bar", "baz"], "boff": {"foo": "bar", "baz": "boff"}}`

type Dataz struct {
    Foo  []string `json:"foo"`
    Boff struct {
        Foo string `json:"foo"`
        Baz string `json:"baz"`
    } `json:"boff"`
}

func main() {
    // Method 1
    var d interface{}
    json.Unmarshal([]byte(dataz), &d)
    fmt.Println(d.(map[string]interface{})["foo"].([]interface{})[0])

    // Method 2
    var D Dataz
    json.Unmarshal([]byte(dataz), &D)
    fmt.Println(D.Foo[0])
}

EDIT
Edit based on comment about performance

Thankfully we can test it with built-in Go tools

> go test -bench .
testing: warning: no tests to run
PASS
BenchmarkInterface    300000          6208 ns/op
BenchmarkStruct       500000          3622 ns/op
ok      parse   3.773s

It's the difference between handling unmarshalling of 276,000/sec or 161,000/sec. So this will almost certainly not be your bottleneck. :)

Upvotes: 3

Related Questions