Alex Sakh
Alex Sakh

Reputation: 45

When unmarshaling JSON into interface{}, it becomes not needed type, but map

When you I use json.Unmarshal to conctere type i get my values filled as expected. But if i pass interface{} instead of struct (for more flexibility), i get map. How can i fix this... Or is it just antipattern, to pass interfaces.

Example:

package main

import (
    "encoding/json"
    "fmt"
)

type Response struct {
    Success bool    `json:"success"`
    Data    []int64 `json:"data"`
}

func handlerWithInterface(request interface{}, response interface{}) (interface{}, error) {
    var err error
    // logic with request ....
    fixture := []byte("{\"success\": true, \"data\": [20, 21, 22]}")
    
    // assingned to interface
    err = json.Unmarshal(fixture, &response)
    if err != nil {
        return nil, fmt.Errorf("failed to unmarshal responses: %s", err.Error())
    }
    
    return response, nil
}

func handler(request interface{}, response Response) (interface{}, error) {
    var err error
    // logic with request ....
    fixture := []byte("{\"success\": true, \"data\": [20, 21, 22]}")
    
    err = json.Unmarshal(fixture, &response)
    if err != nil {
        return nil, fmt.Errorf("failed to unmarshal responses: %s", err.Error())
    }
    
    return response, nil
}

func main() {
    req := "some request"
    resp := Response{}
    result1, err := handlerWithInterface(req, resp)
    if err != nil {
        panic(err)
    }
    fmt.Println(result1)
    
    result2, err := handler(req, resp)
    if err != nil {
        panic(err)
    }
    fmt.Println(result2)
}

Output:

map[data:[20 21 22] success:true]

{true [20 21 22]}

Upvotes: 1

Views: 1029

Answers (1)

Thundercat
Thundercat

Reputation: 120969

The application passes a pointer to an interface{} containing a Response value. The json.Unmarshal function traverses through pointers an interfaces to the last value that's settable, the interface{}. When the destination is an interface{}, json.Unmarshal decodes JSON objects to map[string]interface{}.

Fix by passing a pointer to resp in main. With this change, the unmarshal function will use resp is the target for decoding.

resp := Response{}
result1, err := handlerWithInterface(req, &resp) // <-- add & on this line

It's not necessary to take the address of the response in handlerWithInterface. Pass the pointer from main on through to the json.Unmarshal.

func handlerWithInterface(request interface{}, response interface{}) (interface{}, error) {
    var err error
    // logic with request ....
    fixture := []byte("{\"success\": true, \"data\": [20, 21, 22]}")

    err = json.Unmarshal(fixture, response) // <-- remove & on this line
    if err != nil {
        return nil, fmt.Errorf("failed to unmarshal responses: %s", err.Error())
    }

    return response, nil
}

Upvotes: 3

Related Questions