0

Here's the response interface :

type Response interface{}

It's satisfied by a struct like this :

type CheckResponse struct {
    Status    string `json:"status"`
}

I am getting out []Response as an output which is to be consumed elsewhere.

I want to add a Version string to this JSON, before it's being sent. I've tried using anonymous structs ( but in vain ) :

for _, d := range out {
        outd := struct {
            Resp       Response `json:",inline"`
            Version    string   `json:",inline"`
        }{
            Resp:       d,
            Version: "1.1",
        }
        data, _ := json.Marshal(outd)
        log.Infof("response : %s", data)
    }

The output I am getting is :

response : {"Resp":{"status":"UP"},"Version":"1.1"}

What I want is

{"status":"UP","Version":"1.1"}

i.e. one single flat JSON.

ricizawu
  • 103
  • 2
  • 11

3 Answers3

4

Assert your d to CheckResponse type and then define dynamic struct like this

outd := struct {
            Resp       string `json:"status,inline"`
            Version    string   `json:",inline"`
        }

This is the full code for this.

package main

import (
    "encoding/json"
    "fmt"
)

type Response interface {}

type CheckResponse struct {
    Status    string `json:"status"`
}

func main() {
    out := []Response{
        CheckResponse{Status: "UP"},
    }
    for _, d := range out {
        res, ok := d.(CheckResponse)
        if !ok {
            continue
        }
        outd := struct {
            Resp       string `json:"status,inline"`
            Version    string   `json:",inline"`
        }{
            Resp:       res.Status,
            Version: "1.1",
        }
        data, _ := json.Marshal(outd)
        fmt.Printf("response : %s", data)
    }
}

You can run here

nipuna
  • 3,697
  • 11
  • 24
  • Adding this answer for completeness which is using [structs](https://github.com/fatih/structs) library Here's the playground link : https://play.golang.org/p/OIk1K5zbO6J It's extensible and easy to use, though with the drawback that you've to include another library for this. You can use simple `map[string]interface{}` construct for the same, as used in `structs.Map`. **Note** 1. structs for some reason, makes the json `keys` *Title Case*. That's not an issue though. Also mentioned here : https://github.com/fatih/structs/issues/122 – ricizawu Aug 05 '21 at 09:56
  • @ricizawu is this related to my answer? – nipuna Aug 05 '21 at 10:03
  • no. actually since this question was closed so couldn't add this answer and had to add it in comments. But now I see 'kush' has already added this answer as well. so the above comment is redundant now :D – ricizawu Aug 06 '21 at 07:04
2

inline tag is not supported by encoding/json and embedding interfaces will also not produce the result you want. You'll have to declare a type for the out value and have that type implement the json.Marshaler interface, you can then customize how its fields are marshaled, for example you could marshal the two fields Resp and Version separately and then "merge the result" into a single json object.

type VersionedResponse struct {
    Resp    Response
    Version string
}

func (r VersionedResponse) MarshalJSON() ([]byte, error) {
    out1, err := json.Marshal(r.Resp)
    if err != nil {
        return nil, err
    }
    out2, err := json.Marshal(struct{ Version string }{r.Version})
    if err != nil {
        return nil, err
    }

    // NOTE: if Resp can hold something that after marshaling
    // produces something other than a json object, you'll have
    // to be more careful about how you gonna merge the two outputs.
    //
    // For example if Resp is nil then out1 will be []byte(`null`)
    // If Resp is a slice then out1 will be []byte(`[ ... ]`)

    out1[len(out1)-1] = ',' // replace '}' with ','
    out2 = out2[1:]         // remove leading '{'

    return append(out1, out2...), nil
}

https://play.golang.org/p/66jIYXGUtWJ

mkopriva
  • 35,176
  • 4
  • 57
  • 71
1

One way that will work for sure is simply use a map[string]interface{}, iterate over fields in Response via reflect or use a library like structs, update your map with response fields, append your version field to map, and then marshal. Here is an example

package main

import (
    "encoding/json"
    "fmt"
    "github.com/fatih/structs"
)

type Response interface{}

type CheckResponse struct {
    Status string `json:"status"`
}

func main() {
    resp := CheckResponse{Status: "success"}
    m := structs.Map(resp)
    m["Version"] = "0.1"
    out, _ := json.Marshal(m)   
    
    fmt.Println(string(out))
}

Kush
  • 755
  • 7
  • 22