Standard library patterns · 3 MIN READ

JSON presence: omitted, zero and null are different states

Choose omitempty, omitzero and request presence models from the endpoint contract.

A JSON object can omit a member, contain null, or contain a concrete zero value. Go fields alone do not always preserve those distinctions. An update that sets enabled to false must not silently become an instruction to leave enabled unchanged. Start with a state table for the operation.

JSON presence: omitted, zero and null are different states · A MAP OF THE IDEA

  1. List input states
  2. Choose update semantics
  3. Decode fresh data
  4. Assert stored effects

Useful when

  • Choose omitempty, omitzero and request presence models from the endpoint contract.
  • Specify presence semantics before choosing tags or pointer fields. Missing, null and a concrete zero value may represent different commands.

Watch out

  • A tag controls encoding behavior; it does not define authorization, business validation or every partial-update transition.

Implementation & operation cost: Choose the policy from the actual contract, then verify its failure path in the target environment.

01

Compare omission rules

Under v1 defaults, omitempty omits a zero integer and false boolean. Under v2 defaults those are nonempty JSON values and remain present. omitzero expresses Go zero-value omission. Do not replace tags mechanically: some response fields must be visible even when false or zero.

go
type Flags struct {
	Count   int  `json:"count,omitempty"`
	Enabled bool `json:"enabled,omitempty"`
}

// v1 zero value: {}
// v2 zero value: {"count":0,"enabled":false}
type OptionalCount struct {
	Count int `json:"count,omitzero"`
}
Read the code

These declarations compare ordinary scalar fields. They do not describe every custom marshaler. Build table tests for zero, nonzero, nil and explicitly empty values on the actual request or response type.

02

Do not use an ordinary bool as a PATCH protocol

A bool stores true or false, not whether a property was absent. A *bool can distinguish absent from false in a freshly decoded value, but ordinary decoding also maps explicit null to nil. If null means clear while absence means keep, use a documented presence-aware representation or an explicit operation type.

03

A complete update has a simpler contract

For a full replacement command, decide required fields and validate the final state. For a partial update, separately validate which transitions are allowed. Avoid reusing an already populated decoding target: fields absent from input can retain earlier state and make missing values appear supplied.

04

Write the four cases before the code

Test an omitted property, null, false and true independently. Assert both the final stored value and whether a write occurs. Add unknown members and malformed values to the matrix, and verify that decoding or validation failure cannot publish a partial update.

TAKE THIS WITH YOU

Specify presence semantics before choosing tags or pointer fields. Missing, null and a concrete zero value may represent different commands.

Check your understanding

Ordinary decoding into a fresh *bool must distinguish missing, null and false. What is missing from that representation?

Go to the source

Progress stays in this browser. No account needed.