31

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 Answers 4

46

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

Sign up to request clarification or add additional context in comments.

Comments

5

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

Comments

4

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",
    })
}

Comments

4

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.