This guide explains how to model null, undefined (missing), and zero values in JSON Patch requests when working with Go. You’ll learn how to distinguish between a field that is absent, explicitly set to null, and one set to 0, so your API updates behave exactly as intended. We’ll cover the use of pointers, custom types, and the omitempty tag, along with practical examples and common pitfalls.
What You’ll Need
- Go 1.16 or later (for
anytype support, though older versions work withinterface{}) - A basic understanding of JSON and JSON Patch (RFC 6902) operations:
add,remove,replace,copy,move,test - Familiarity with Go’s
encoding/jsonpackage - Optionally, a JSON Patch library like
evanphx/json-patch(but the concepts apply to any implementation)
Step-by-Step
- Understand the three states. In JSON, a field can be:
- Undefined: the key is absent from the JSON object. In Go, this is the zero value of the type (e.g.,
0forint,""forstring). - Null: the key exists with value
null. - Zero: the key exists with a value like
0,"",false, etc.
- Undefined: the key is absent from the JSON object. In Go, this is the zero value of the type (e.g.,
- Use pointers for nullable fields. In your Go struct, define fields that can be
nullas pointers (*int,*string, etc.). Anilpointer meansnullin JSON; a non-nil pointer means the value is present. Example:type User struct { Name *string `json:"name"` Age *int `json:"age"` Email string `json:"email"` }Here,
NameandAgecan benull, whileEmailcannot (it will be an empty string if missing). - Distinguish undefined from null. A pointer field that is
nilwill be serialized asnullby default. If you need to distinguish “not provided” from “explicitly null”, you need a custom type or a separate presence flag. For JSON Patch, however, you usually don’t need this distinction on the receiving side—the patch operation itself tells you what to do. For example, aremoveoperation means “delete the key”, while areplacewithnullmeans “set to null”. - Handle zero values correctly. In Go, a non-pointer
intfield will be0if not set. If you want to allow a real0but also detect “not provided”, use a pointer. For JSON Patch, areplaceoperation with0should set the field to0, not leave it unchanged. Always use pointers for numeric fields that can legitimately be zero. - Use
omitemptycarefully. Theomitemptytag omits a field when it has its zero value. For pointers,omitemptyomits when the pointer isnil. This is useful for requests where you don’t want to send unchanged fields. But beware: if you useomitemptyon a non-pointerint, a value of0will be omitted, which may be incorrect for your API. Example:type UpdateRequest struct { Age *int `json:"age,omitempty"` }This omits
ageif it’snil, but includes it if it’s0(because the pointer is non-nil). - Build JSON Patch operations explicitly. When constructing a patch, you decide what each operation means. For example:
- To set a field to
null: use{"op":"replace","path":"/age","value":null} - To remove a field entirely: use
{"op":"remove","path":"/age"} - To set a field to
0: use{"op":"replace","path":"/age","value":0}
In Go, you can define a struct for the operation:
type PatchOperation struct { Op string `json:"op"` Path string `json:"path"` Value interface{} `json:"value,omitempty"` }Note:
omitemptyonValuewill omit it whennil, which is fine forremoveoperations. Forreplacewithnull, you need to pass a typednillikenil(which is an untyped nil) or a pointer to nil. Actually,interface{}holding a nil pointer will serialize asnull—that works. - To set a field to
- Test your serialization. Write unit tests to verify that your structs marshal to the expected JSON. For example:
func TestPatch(t *testing.T) { op := PatchOperation{Op: "replace", Path: "/age", Value: nil} b, _ := json.Marshal(op) // b should be: {"op":"replace","path":"/age","value":null} }If
Valueis a typed nil pointer, it will marshal asnull. But if you useomitempty, a nil pointer will be omitted, so forreplaceoperations you should not useomitemptyonValue. - Decode incoming patches correctly. When receiving a JSON Patch, unmarshal into a slice of
PatchOperation. TheValuefield will benilfornull, and0for zero. You can then apply the patch to your data structure, handling each operation type. - Apply the patch to your data. For each operation, update your target struct. For
replacewithnull, set the pointer field tonil. Forreplacewith0, set the pointer to a value pointing to0. Forremove, delete the key (if using a map) or set the pointer to nil (if using a struct).
Common Mistakes
- Using non-pointer types for nullable fields. If you use
intinstead of*int, you cannot representnull—it will always be0. - Misusing
omitemptyon value types. This causes zero values to be omitted, making it impossible to send a real0in a patch. - Confusing
nullwith0. In JSON,nullis not the same as0. Your API logic must treat them differently. - Assuming
removeis the same as settingnull.removedeletes the key;replacewithnullkeeps the key but sets it to null. - Not handling pointer nil when applying patches. If you set a pointer field to
nil, make sure your code doesn’t dereference it later.
Tips
- Use pointers for all fields that can be null or zero. This gives you the most flexibility.
- Consider a custom type for tri-state fields. If you need to distinguish “not provided”, “null”, and “zero”, create a type like
type NullableInt struct { Valid bool; Value int }and implementMarshalJSONandUnmarshalJSON. - Keep your patch operations explicit. Don’t rely on the zero value of a struct to mean “no change”. Always build operations deliberately.
- Use a library for applying patches. Libraries like
evanphx/json-patchhandle the details, but you still need to model your data correctly. - Document your API’s semantics. Clearly state whether a missing field means “no change” or “set to zero”. This avoids client confusion.
Troubleshooting
- Patch value is omitted when sending
null. If you useomitemptyon theValuefield, anilvalue will be omitted. RemoveomitemptyfromValueor use a pointer type forValue(like*interface{}) to force inclusion. - Cannot distinguish between missing and null in request body. If you’re not using JSON Patch but a direct JSON body, use pointers and check for
nil. For missing fields, the pointer will benilas well, so you need a separate presence flag or a custom type. - Zero value is omitted from patch. If you use
omitemptyon a non-pointerint,0is omitted. Use a pointer or removeomitempty. - Applying a patch with
nullto a non-pointer field. This will cause an error because you can’t set aninttonull. Ensure the target field is a pointer. - JSON unmarshal of
nullinto a pointer field. This works fine—it sets the pointer tonil. But if you unmarshal into a non-pointer, it returns an error.
Frequently Asked Questions
How do I represent “field not provided” vs “field set to null” in a JSON Patch?
In JSON Patch, you don’t need to distinguish them on the wire. The operation itself defines the intent: remove means “not present”, replace with null means “present but null”. Your server logic should handle each operation accordingly.
Should I use omitempty on patch operation value?
No, not if you need to send null. omitempty will drop nil values. For remove operations, you don’t need a value, so it’s fine to omit it. But for replace with null, you must include it. A common pattern is to use a pointer to interface{} for the value and omit omitempty.
Can I use json.RawMessage for the value field?
Yes, json.RawMessage preserves the exact JSON, including null. It’s useful if you need to forward the raw value. However, it requires manual unmarshalling when applying the patch.
How do I apply a JSON Patch to a map instead of a struct?
You can unmarshal the target JSON into a map[string]interface{}. Then, for replace with null, set the map key to nil. For remove, delete the key. This approach is simpler for dynamic data but loses type safety.
What’s the difference between null and 0 in JSON?
null means “no value”, while 0 is a numeric value. In Go, nil for a pointer maps to null, and 0 for an integer maps to 0. They are distinct and should be treated differently in your business logic.
Is it better to use a custom type or pointers?
Pointers are simpler and cover most cases. Custom types are needed only when you must distinguish between three states (missing, null, zero) in a single field, which is rare in JSON Patch because the operation type already provides that distinction.
Related
Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.
More cards in this drawer
- Campus Tech Innovation Hubs: What They Are
- Poco C51 Airtel Prepaid Locked Variant India?
- Jenkins vs GitHub Actions: Which CI/CD Fits Your Team?
- Turn Off Backup Encryption in iTunes?
- Shared VPS Hosting: Is It Better?
- Disk Defrag Pro: What It Does and Whether You Need It