Converting struct with embedded interface into JSON

Viewed 2499

I have a struct that I want to marshal to JSON. It has a defined field called Foo (exported as foo) and a data interface field to which I want to pass a dynamic struct with additional JSON fields.

However when the data field is an interface instead of the specific struct it never gets exported as JSON. How can I make this work?

package main

import (
    "encoding/json"
    "fmt"
)

type data interface{}

type foo struct {
    Foo string `json:"foo,omitempty"`
    data
}

type bar struct {
    Bar string `json:"bar,omitempty"`
}

func main() {
    b := bar{"bar"}
    f := foo{"foo", b}

    byt, err := json.Marshal(f)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(string(byt))
}

I need to output to look like this (it needs to be flat, not nested):

{"foo": "foo", "bar": "bar"}
3 Answers

You could do this with a custom json.Marshaler implementation and a little bit of byte slicing.

func (f foo) MarshalJSON() ([]byte, error) {
    type goo foo
    g := goo(f)

    b1, err := json.Marshal(g)
    if err != nil {
        return nil, err
    }

    b2, err := json.Marshal(g.data)
    if err != nil {
        return nil, err
    }

    s1 := string(b1[:len(b1)-1])
    s2 := string(b2[1:])

    return []byte(s1 + ", " + s2), nil
}

https://play.golang.org/p/NYTNWIL-xu

Please note that this is not checking whether the bytes can actually be sliced and it does also not consider the possible case of the data field being a slice or an array, which i'm unsure how you would want that flattened anyway.

I would write a custom marshaller, like so:

func (f foo) MarshalJSON() ([]byte, error) {
    type tmp foo
    g := tmp(f)
    first, err := json.Marshal(g)
    if err != nil {
        return nil, err
    }
    second, err := json.Marshal(f.data)
    if err != nil {
        return nil, err
    }
    data := make(map[string]interface{})
    json.Unmarshal(first, &data)
    json.Unmarshal(second, &data)
    return json.Marshal(data)
    //{"bar":"bar","foo":"foo"}
}

https://play.golang.org/p/TENiCe9nR0

Related