Search code examples
jsongostructmarshalling

JSON Marshaling of composite structs which both implement MarshalJSON()


I faced recently the following problem and have not found any solution. I have two struct types in Go, let us call them Parent and Child. Child has an anonymous field of the type *Parent. However, Parent has a field called "ID" which has the type of a third struct which type we will call "IDType" (in my real problem this is a dialect/sql.NullInt64). IDType has an int field and a bool field.

The problem is the following: Both, Parent and Child, implement MarshalJSON() because for Parent I only want the int field inside the JSON and for Child the same. However, it seems that both MarshalJSONs infer with the result that only the values of Parent are encoded in the final JSON.

Maybe a minimal example makes it easier to understand:

package main

import (
    "encoding/json"
    "fmt"
    "os"
)

type IDType struct {
    Value int
    Valid bool
}

type Parent struct {
    ID         IDType `json:"id"`
    SomeString string `json:"some_string"`
}

type Child struct {
    *Parent
    Status int `json:"status"`
}

func (parent *Parent) MarshalJSON() ([]byte, error) {
    type Alias Parent
    fmt.Println("Parent")
    return json.Marshal(struct {
        *Alias
        ID int `json:"id"`
    }{
        Alias: (*Alias)(parent),
        ID:    parent.ID.Value,
    })
}

func (child *Child) MarshalJSON() ([]byte, error) {
    type Alias Child
    fmt.Println("Child")
    return json.Marshal(struct {
        *Alias
        Status int `json:"status"`
    }{
        Alias:  (*Alias)(child),
        Status: child.Status,
    })
}

func main() {
    ID := IDType{Value: 1, Valid: true}
    parent := Parent{ID: ID, SomeString: "Hello"}
    child := Child{Parent: &Parent{ID: ID, SomeString: "Hello"}, Status: 1}
    json.NewEncoder(os.Stdout).Encode(&parent)
    json.NewEncoder(os.Stdout).Encode(&child)
}

The output is:

Parent
{"some_string":"Hello","id":1}
Child
Parent
{"some_string":"Hello","id":1}

I expect something like:

Parent
{"some_string":"Hello","id":1}
Child
Parent
{"some_string":"Hello","id":1, "status": 1}

Solution

  • It looks you only define a custom marshaling logic because of the custom ID marshaling. Define your custom marshaling for the IDType type only which is not embedded, so it won't cause any trouble marshaling the other types:

    func (id *IDType) MarshalJSON() ([]byte, error) {
        return json.Marshal(id.Value)
    }
    

    And no other custom marshaling are required. With this, the output will be:

    {"id":1,"some_string":"Hello"}
    {"id":1,"some_string":"Hello","status":1}
    

    Try it on the Go Playground.