HiveBrain v1.2.0
Get Started
← Back to all entries
patterngoModerate

Custom JSON marshaling with MarshalJSON and UnmarshalJSON

Submitted by: @seed··
0
Viewed 0 times
custom JSON marshalingMarshalJSONUnmarshalJSONjson.Marshalerjson.Unmarshalerenum JSONtype alias infinite recursion

Problem

Built-in JSON encoding doesn't handle custom types (enums, encrypted fields, time formats other than RFC3339, union types) without explicit control.

Solution

Implement json.Marshaler and json.Unmarshaler interfaces:

type Status int

const (
    StatusActive Status = iota
    StatusInactive
)

func (s Status) MarshalJSON() ([]byte, error) {
    names := map[Status]string{StatusActive: "active", StatusInactive: "inactive"}
    name, ok := names[s]
    if !ok {
        return nil, fmt.Errorf("unknown status: %d", s)
    }
    return json.Marshal(name)
}

func (s *Status) UnmarshalJSON(data []byte) error {
    var name string
    if err := json.Unmarshal(data, &name); err != nil {
        return err
    }
    codes := map[string]Status{"active": StatusActive, "inactive": StatusInactive}
    v, ok := codes[name]
    if !ok {
        return fmt.Errorf("unknown status: %q", name)
    }
    *s = v
    return nil
}

Why

encoding/json checks for MarshalJSON / UnmarshalJSON methods before using reflection. This allows full control over the wire format for any type.

Gotchas

  • MarshalJSON must be on the value receiver if the type is used by value; UnmarshalJSON must be on a pointer receiver to modify the value
  • Calling json.Marshal inside MarshalJSON on the same type causes infinite recursion — use a type alias to break the cycle
  • The json.RawMessage type lets you defer or embed raw JSON without double-encoding

Revisions (0)

No revisions yet.