Custom MarshalJSON() never gets called in Go

13,831

In this part of the code, ms gets copied into an interface{} variable:

// Trying another method (UNSUCCESSFUL)
if ret, err := json.Marshal(ms); err != nil {

The problem is that this variable does not implement the json.Marshaler interface, since MarshalJSON is not in the method set for myStruct (only for *myStruct).

The fix is to either (a) make your MarshalJSON method take a non-pointer receiver (which will mean it gets a copy of the struct: possibly costly if it is large), or (b) marshal a pointer to the struct (as Kavu mentioned in a comment).

The reason for this behaviour is that Go doesn't let you take a pointer to the value stored inside an interface variable, instead requiring you to make a copy of the value whenever you want to access it. While the language has syntactic sugar to convert ms.MarshalJSON() into (&ms).MarshalJSON() as a way to access the method with a pointer receiver, this can not be done for a value stored in an interface variable. For this reason, the method is not considered to be in its method set.

Share:
13,831
Anders Sjöqvist
Author by

Anders Sjöqvist

Educations in Business and Computer Science. Currently working with development at a startup in Beijing, China.

Updated on June 04, 2022

Comments

  • Anders Sjöqvist
    Anders Sjöqvist almost 2 years

    I've written custom versions of MarshalJSON and UnmarshalJSON. My UnmarshalJSON gets called the way I want it to, but I can't get it to work with MarshalJSON. Here's code that summarizes my problem:

    package main
    
    import (
        "bytes"
        "encoding/json"
        "fmt"
        "log"
        "os"
    )
    
    type myStruct struct {
        Data string `json:"data"`
    }
    
    func (s *myStruct) MarshalJSON() ([]byte, error) {
        return []byte(`{"data":"charlie"}`), nil
    }
    
    func (s *myStruct) UnmarshalJSON(b []byte) error {
        // Insert the string directly into the Data member
        return json.Unmarshal(b, &s.Data)
    }
    
    func main() {
        // Create a struct with initial content "alpha"
        ms := myStruct{"alpha"}
    
        // Replace content with "bravo" using custom UnmarshalJSON() (SUCCESSFUL)
        if err := json.NewDecoder(bytes.NewBufferString(`"bravo"`)).Decode(&ms); err != nil {
            log.Fatal(err)
        }
    
        // Use custom MarshalJSON() to get "charlie" back (UNSUCCESSFUL)
        if err := json.NewEncoder(os.Stdout).Encode(ms); err != nil {
            log.Fatal(err)
        }
    
        // Trying another method (UNSUCCESSFUL)
        if ret, err := json.Marshal(ms); err != nil {
            log.Fatal(err)
        } else {
            fmt.Println(string(ret))
        }
    
        // Verify that the Marshaler interface is correctly implemented
        var marsh json.Marshaler
        marsh = &ms
        ret, _ := marsh.MarshalJSON()
        fmt.Println(string(ret)) // Prints "charlie"
    }
    

    In short, the program encodes the struct "automatically" in two ways, and then finally calls MarshalJSON manually. The response I want is "charlie". Running the code generates the following output:

    {"data":"bravo"}
    {"data":"bravo"}
    {"data":"charlie"}
    

    Try it at Go Playground: http://play.golang.org/p/SJ05S8rAYN

  • Valerio
    Valerio almost 10 years
    very instructive, &myStruct{} is a life-saving pattern