vibhor1997a
vibhor1997a

Reputation: 2376

Can I add a field to an interface using a struct?

I came across an interesting scenario. I had a struct and I want to add a field message to it. I was able to do this by going through Can I add a field to an existing struct with Go?.

type User struct {
 // user fields here
}

type UpdationResponse struct {
 User
 Message string `json:"message,omitempty"` 
}

func SendSuccessResponse(w http.ResponseWriter, r *http.Request, resp interface{}) interface{} {
    w.Header().Set("Content-Type", "application/json")
    return json.NewEncoder(w).Encode(resp)
}

This returns a JSON like

{
    "id": "50",
    "firstName": "vibhor",
    "lastName": "agrawal",
    "email": "[email protected]",
    "isVerified": false,
    "joinedAt": "2020-06-28T09:45:59Z",
    "fullName": "vibhor agrawal"
    "message": "Profile Updated."
}

So, this helps me in sending the User data along with a message let's say "Profile Updated.". If I want to generalize this for all my APIs. Is there something I can do.

I tried:

type SuccessResponse struct {
 Data    interface{}
 Message string `json:"message,omitempty"`
}

func SendSuccessResponse(w http.ResponseWriter, r *http.Request, resp SuccessResponse) interface{} {
    w.Header().Set("Content-Type", "application/json")
    return json.NewEncoder(w).Encode(resp)
}

But when I send it as JSON it makes a structure like

{
    "Data": {
        "id": "50",
        "firstName": "vibhor",
        "lastName": "agrawal",
        "email": "[email protected]",
        "isVerified": false,
        "joinedAt": "2020-06-28T09:45:59Z",
        "fullName": "vibhor agrawal"
    },
    "message": "Profile Updated."
}

Is there a way I can add message in the data itself and generalize this for all my success responses irrespective of Data?

Upvotes: 1

Views: 354

Answers (2)

mkopriva
mkopriva

Reputation: 38223

You could have the response implement the json.Marshaler interface, have it marshal the two fields separately and then merge the results at the end.

func (r Response) MarshalJSON() ([]byte, error) {
    out1, err := json.Marshal(r.Data)
    if err != nil {
        return nil, err
    }

    type _Response Response // to avoid infinite recursion
    out2, err := json.Marshal((_Response)(r))
    if err != nil {
        return nil, err
    }

    // NOTE: this may need more work to handle other cases, for example
    // the Data field's dynamic type being a slice of some type, rather than
    // a single struct....
    if size := len(out1); size > 0 && out1[size-1] == '}' {
        if size > 2 {
            out1[size-1] = ',' // replace "}" with ","
        } else {
            out1 = out1[:size-1] // drop "}"
        }

        out2 = out2[1:]              // drop "{"
        out := append(out1, out2...) // merge
        return out, nil
    }

    return out2, nil
}

https://play.golang.org/p/9u0jcEgt2zL

Upvotes: 3

mbuechmann
mbuechmann

Reputation: 5760

In short: This is not possible.

Long answer:

In your first example the struct UpdationResponse extends the struct User. This is also called a mixin. Simply speaking UpdationResponse takes all attributes of User and uses them itself.

In your second example the struct SuccessResponse defines the attribute Data which can be anything. Data is a child element of SuccessResponse and gets marshalled as such. There is no way around it, as go source code is statically typed and compiled ahead of time.

Upvotes: 4

Related Questions