You implemented MarshalJSON but it isn't called. Why? Explain receiver rules and the recursion trap.
Question 453HardGo 1.22 to 1.25
If MarshalJSON has a pointer receiver, only *T implements json.Marshaler. Marshaling a non-addressable T value (e.g. json.Marshal(t), a value in a map, or a value field of a value passed in) skips your method. Use a value receiver for MarshalJSON; use a pointer receiver for UnmarshalJSON (it must mutate).
Recursion trap: calling json.Marshal(t) inside T.MarshalJSON recurses infinitely (stack overflow). Use a local type alias that has the same fields but no methods:
type Money struct {
Cents int64
Currency string
}
func (m Money) MarshalJSON() ([]byte, error) {
type plain Money // same fields, no methods
return json.Marshal(struct {
plain
Display string `json:"display"`
}{plain(m), fmt.Sprintf("%.2f %s", float64(m.Cents)/100, m.Currency)})
}
func (m *Money) UnmarshalJSON(b []byte) error {
type plain Money
var p plain
if err := json.Unmarshal(b, &p); err != nil {
return err
}
if p.Currency == "" {
return errors.New("currency required")
}
*m = Money(p)
return nil
}
Also note encoding.TextMarshaler is used for map keys and as a fallback, and a nil pointer with a value-receiver MarshalJSON encodes as null without calling the method.
More on Standard Library, HTTP & Systems Design in Go
- Q451Explain encoding/json struct tag behavior: omitempty, omitzero, "-", and ",string". What does this print?
- Q452What does this print? How do you preserve large integer precision when decoding unknown JSON?
- Q454json.Unmarshal vs json.Decoder: when to use each, and how do you safely decode a request body?
- Q455What does this print? (nil vs empty slices and maps in JSON)
- Q456How does database/sql connection pooling work? Which settings matter and what causes connection leaks?
- Q457How do you handle transactions, NULLs and "no rows" correctly with database/sql?