Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For a PATCH request, an omitted JSON member usually means “leave this field unchanged,” while an explicit null may mean “clear it.” A regular Go struct field—including a pointer—does not by itself preserve that distinction after JSON decoding. In Gin, choose the request format and its null semantics first, represent field presence explicitly, validate both supplied values and the proposed final resource, then persist the update atomically.

JSON has a null value but no undefined literal. In this context, “undefined” usually means that the object member was omitted.

Why ordinary Go fields lose PATCH intent

When decoding JSON into a Go struct, an omitted member leaves the corresponding field at its zero value. A present member with null also commonly leaves a pointer field nil. Once decoded, the handler therefore may not know whether the client omitted the field or explicitly sent null. A non-pointer field likewise cannot distinguish an omitted value from an explicitly supplied zero value such as false, 0, or "". Go’s encoding/json documentation describes how JSON unmarshalling maps values into Go values.

That distinction matters for a partial update. A handler that treats a zero value as “not supplied” cannot reliably set a boolean to false or a number to zero. A handler that treats every nil pointer as a request to clear may erase data when the client intended no change.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choose the patch format and null behavior

Before choosing a Go type, define what the endpoint accepts. A custom JSON object, JSON Merge Patch, and JSON Patch have different wire formats and rules; an application-specific DTO should not be presented as implementing an RFC unless it follows that RFC.

Design Request shape Omission Clear or remove Useful when
Custom presence-aware object Resource-like object with application-defined field behavior Define as unchanged Define per field; null may clear a nullable field or be rejected You need endpoint-specific rules and can document them clearly
JSON Merge Patch (RFC 7396) Resource-like patch object Unchanged Null removes the corresponding target member You want a compact object-shaped merge update
JSON Patch (RFC 6902) Array of operation objects No operation means unchanged Use an explicit remove operation Clients need explicit path-level operations such as add, replace, remove, or test

The standards specify distinct formats: see the IETF’s RFC 7396, JSON Merge Patch, and RFC 6902, JSON Patch. In Merge Patch, null has removal semantics; that is not automatically the right contract for every custom PATCH endpoint.

Represent omission, null, and value explicitly

Use a presence-aware wrapper for a custom object

For a small custom patch DTO, a wrapper can record whether a member appeared, whether it contained null, and—if non-null—its decoded value:

type PatchField[T any] struct {
    Present bool
    Null    bool
    Value   T
}

func (p *PatchField[T]) UnmarshalJSON(data []byte) error {
    p.Present = true
    if bytes.Equal(bytes.TrimSpace(data), []byte("null")) {
        p.Null = true
        return nil
    }
    return json.Unmarshal(data, &p.Value)
}

type UpdateUserRequest struct {
    Nickname PatchField[string] `json:"nickname"`
}

This illustrative sketch needs bytes and encoding/json imports. When the member is present, JSON decoding calls the wrapper’s UnmarshalJSON; when it is absent, the wrapper remains at its zero value, so Present is false. The handler can then distinguish all three states. Decide explicitly whether a null nickname means “clear,” is invalid, or has some other documented meaning.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For a reusable generic wrapper, define how nested objects, arrays, duplicate keys, and marshaling should behave. Do not mutate a persisted model as a side effect of decoding: interpret the request and apply it in a controlled update step.

Decode into raw messages when explicit mapping is preferable

Another option is map[string]json.RawMessage. A missing key represents omission; for a present key, inspect the raw JSON for null or decode it into the expected type. This can make mapping straightforward when the endpoint has a few known JSON names, but the handler must deliberately map and validate those names rather than rely on implicit struct behavior.

Use a pointer only when null and omission are equivalent

A pointer field is sufficient if the API intentionally gives omitted and explicit null the same meaning—for example, both are ignored or both clear the field. If those inputs need different behavior, the pointer’s nil state is not enough.

Bind and validate in separate stages with Gin

Gin’s JSON binding parses the request; it does not decide what omission or null means for your update contract. Gin documents JSON binding and validation in Request Binding & Validation, and notes its integration with go-playground/validator/v10. Use ShouldBindJSON when the handler needs to control the error response. Gin’s must-bind Bind family aborts on binding errors with HTTP 400, so do not accidentally write a second response after such a method has already committed an error.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Best Value
Golang Minimalist Design Programming T-Shirt, Men, Black, 3X-Large
  • Go Programming Design design. Nice Design
  • Simplistic
  • Lightweight, Classic fit, Double-needle sleeve and bottom hem

If using a custom JSON unmarshaler, confirm that the JSON binding path you chose invokes it as expected. Gin’s custom-unmarshaler guidance also discusses TextUnmarshaler support for URI and form binding; that should not be mistaken for a general JSON solution to field presence.

Validate supplied values, not absent fields

For a partial request, apply field rules to fields that are present with a value. Handle explicit null separately according to the field’s nullability contract. An omitted field is not a supplied zero value, so a blanket required rule is often wrong for PATCH: validator’s required behavior can reject legitimate assignments such as false, 0, or an empty string. The validator/v10 documentation describes facilities including StructPartial, omitempty/omitnil, and struct-level validation. These facilities can help with partial validation, but they do not infer omitted-versus-null meaning for an ordinary Go struct.

Validate business rules against the proposed resource

A field can be valid alone and still make the resource invalid in combination with another field. For example, a change to one setting might violate an invariant involving a second setting that was not part of the request. Apply the interpreted patch to a copy of the current resource, then validate cross-field and business constraints on that proposed state before saving it.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Apply the patch without partial persistence

  1. Decode: bind the request and reject malformed JSON using an error path that lets the handler return a controlled response.
  2. Interpret: for each known field, distinguish absent, null, and a supplied value; enforce the endpoint’s null and unknown-field policies.
  3. Build proposed state: apply changes to a copy or transaction-local version of the current resource, leaving omitted fields untouched.
  4. Validate: validate provided non-null values under their field rules, enforce nullability, and check invariants against the complete proposed resource.
  5. Persist atomically: save only after validation succeeds, using a transaction or equivalent safe update strategy so an invalid patch cannot persist only part of its intended changes.

Test the three states and failure cases

Tests should assert the endpoint’s contract, not just that JSON decodes. Include these cases:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • {} leaves every field unchanged.
  • {"nickname":null} clears the nickname only if the contract declares it clearable; otherwise it returns the documented error.
  • {"enabled":false} sets false rather than treating it as omission.
  • {"quota":0} sets zero when zero is allowed.
  • {"label":""} preserves the difference between a supplied empty string and an omitted label.
  • Malformed JSON and unknown fields follow deliberate, documented error behavior.
  • A patch that breaks a cross-field invariant fails without persisting any portion of the update.

These cases expose the main implementation traps: confusing zero values with absence, letting a pointer collapse null and omission, or validating only the fragment while ignoring the resulting resource.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.