21
votes

Suppose I've written the following code snippet. Full code on the playground here for those inclined.

type Book struct {
  Title        string
  Author       string
}

func main() {
  ms := Book{"Catch-22", "Joseph Heller"}
  out, err := json.MarshalIndent(ms, "", "  ")
  if err != nil {
    log.Fatalln(err)
  }
  fmt.Println(string(out))
}

This code outputs the following, exactly as I'd expect:

{
  "Title": "Catch-22",
  "Author": "Joseph Heller"
}

Suppose for a moment I wanted to add a field to the JSON output without including it in the Book struct. Perhaps a genre:

{
  "Title": "Catch-22",
  "Author": "Joseph Heller",
  "Genre": "Satire"
}

Can I use MarshalJSON() to add an arbitrary field to the JSON payload on Marshal()? Something like:

func (b *Book) MarshalJSON() ([]byte, error) {
    // some code
}

Other answers make me think this should be possible, but I'm struggling to figure out the implementation.

4

4 Answers

33
votes

Here's a better answer than my previous one.

type FakeBook Book

func (b Book) MarshalJSON() ([]byte, error) {
    return json.Marshal(struct {
        FakeBook
        Genre string
    }{
        FakeBook: FakeBook(b),
        Genre:    "Satire",
    })
}

Since anonymous struct fields are "merged" (with a few additional considerations) we can use that to avoid remapping the individual fields. Note the use of the FakeBook type to avoid the infinite recursion which would otherwise occur.

Playground: http://play.golang.org/p/21YXhB6OyC

3
votes

One possible answer to this question is a struct literal (code here), although I'm hoping for something a bit more general, which doesn't require remapping all of the struct's fields:

func (b *Book) MarshalJSON() ([]byte, error) {
    return json.Marshal(struct{
        Title    string
        Author   string
        Genre    string
    } {
        Title: b.Title,
        Author: b.Author,
        Genre: "Satire",
    })
}
3
votes

Marshalling a map is another way around the problem.

tmap := make(map[string]interface{})

tmap["struct"] = struct {
    StructValue string `json:"struct_value"`
}{
    "Value 02",
}

tmap["string"] = "Value 01"

out, err := json.MarshalIndent(tmap, "", "  ")
if err != nil {
    log.Fatalln(err)
}
fmt.Println(string(out))

This will output:

{
  "string": "Value 01",
  "struct": {
    "struct_value": "Value 02"
  }
}

Where you have many arbitrary key names this could be a good solution.

Playground: https://play.golang.org/p/Umy9rtx2Ms

2
votes

This is one method of handling it:

type Object struct {
    A string
    B int
    Extra map[string]interface{} `json:"-"`
}

func (o Object) MarshalJSON() ([]byte, error) {
    type Object_ Object
    b, err := json.Marshal(Object_(o))
    if err != nil {
        return nil, err
    }
    if o.Extra == nil || len(o.Extra) == 0 {
        return b, nil
    }
    m, err := json.Marshal(o.Extra)
    if err != nil {
        return nil, err
    }
    if len(b) == 2 {
        return m, nil
    } else {
        b[len(b)-1] = ','
        return append(b, m[1:]...), nil
    }
}

You can add whatever additional fields to the Extra map and they will appear without nesting in the output.

Go Playground