« Back
read.

Custom MarshalJSON in GOLang.

Getting JSON from structs or any interface{} in GO is pretty trivial, but custom marshalling can be troublesome.

The following struct is based on that in the GO docs but with the json field hints to demonstrate the output modifications produced.

type Message struct {  
    Name string `json:"user"`
    Body string `json:"message"`
    Time int64  `json:"timestamp"`
}

m := Message{"Alice", "Hello", 1294706395881547000}

b, err := json.Marshal(m)

b == {  
"user":"Alice",
"message":"Hello",
"timestamp":1294706395881547000
}

The above shows how to rename fields during output, but modifying an output requires the a custom MarshalJSON function on the struct. If this function exists, json.encode will call it recurvisely on the struct and any interfaces within the struct. This is also helpful if you cannot modify the struct with the json field hints.

This simple example performs the same modifications as the field hints above and adds the additional ID and Origin fields.

func (m *Message) MarshalJSON() ([]byte, error) {  
    return json.Marshal(&struct {
        ID          string `json:"id"`
        Origin      string `json:"origin"`
        Name        string `json:"name"`
        Message     string `json:"message"`
        Timestamp   int64  `json:"timestamp"`
    }{
        ID:        "Alices-Message-ID"
        Origin:    "Wonderland"
        Name:      m.Name,
        Message:   m.Body,
        Timestamp: m.Time,
    })
}

The problem with this solution is the requirement to specify each of the fields that exist on the Message struct. If we use both the json field hints and the above approach we eliminate this issue.

func (m *Message) MarshalJSON() ([]byte, error) {  
    return json.Marshal(&struct {
        ID          string `json:"id"`
        Origin      string `json:"origin"`
        *Message
    }{
        ID:        "Alices-Message-ID"
        Origin:    "Wonderland"
        Message:   m
    })
}

However, the above function will loop; the outer struct marshalling will call the inner Message struct marshal function and there begins the loop. To circumvent this issue we must instead "copy" the original object; this object will have the same properties of the original but no functions - thus removing the infinite loop.

func (m *Message) MarshalJSON() ([]byte, error) {  
    type Copy Message
    return json.Marshal(&struct {
        ID          string `json:"id"`
        Origin      string `json:"origin"`
        *Copy
    }{
        ID:        "Alices-Message-ID"
        Origin:    "Wonderland"
        Copy:      (*Copy)(m),
    })
}
comments powered by Disqus