Reproduce the disappearing nickname
Start with a profile containing nickname: "A". The client sends an empty update because it changed nothing. A request adapter reads the missing nickname using a lookup that returns null, then builds a new object containing nickname: null. The resulting patch removes the nickname.
The request did not ask for that deletion. The adapter invented it by collapsing two different states: absent and present with a null value. This is a useful failure to test even when the patch library itself is correct.
The accompanying Python fixture runs eight transformation cases and a separate adapter counterexample. It checks the transformed document and verifies that the original input remains unchanged. No HTTP framework, generated SDK or database participates in the fixture.
Choose the update language before the DTO
JSON Merge Patch uses the media type application/merge-patch+json. In an object patch, omitted members remain untouched, supplied values update members and null requests member removal. Nested objects are processed recursively. An array replaces the corresponding array as a whole. IETF RFC 7396.
Those rules make a nullable product field a contract decision. Removing a member and storing an explicit null are different document states. If callers must select between them, the patch format must preserve that choice all the way through decoding and validation.
JSON Patch supplies explicit operations such as remove and replace. A replace operation can carry a null value, which gives an API a way to express stored null separately from removal. Choose the format from the operations your clients need rather than from the easiest request class to generate. IETF RFC 6902.
Read the expected outputs, including the awkward ones
The fixture starts from a profile with a nickname, two notification settings and a two-item tag array. The assertions below are specific to that input.
| Input case | Observed result | Application question |
|---|---|---|
| Empty patch object | Profile unchanged | Did decoding preserve omission? |
| Nickname set to null | Nickname member removed | Is removal allowed? |
| Nickname set to empty string | Empty string retained | Is blank a valid nickname? |
| Email preference set false | SMS preference retained | Did validation keep the nested distinction? |
| New one-item tags array | Old tags replaced | Did the editor intend full replacement? |
| Empty tags array | Tags cleared | Is an empty collection permitted? |
| Whole patch is null | Whole result is null | Does this endpoint permit a non-object document? |
| Absent member set to null | Profile unchanged | Is the request acceptable under your schema? |
A transformation being valid does not make it valid for your endpoint. The whole-document-null case should fail a profile schema that requires an object. That rejection belongs to endpoint policy. Silently turning the input into an empty object would hide a client error and change its meaning.
For an editor that only changes one tag, replacing the full array can overwrite a concurrent edit to another tag. Use a contract that fits the editing operation, or require a current resource version. The separate ETag optimistic concurrency guide addresses lost updates after the request's meaning is settled.
Keep field presence through every boundary
Inspect the raw parsed request before constructing a domain command. Track whether a member exists independently from the value it carries. A language may offer a dedicated optional wrapper, a presence bit or a tagged union. The representation matters less than retaining the distinction.
ABSENT -> leave the field unchanged
PRESENT with null -> apply the contract's removal operation
PRESENT with value -> validate and apply that valueDo not populate an update object from every property on a form model. A disabled input, an untouched input and an intentionally cleared input can otherwise become indistinguishable. Test the actual serialized body emitted by the client, not only the object visible in its debugger.
The fixture's broken adapter constructs {"nickname": request.get("nickname")} from an empty request. Its corrected path preserves the empty request. Download the executable example and complete outputs, then run python3 experiment.py. Compare the adapter_counterexample record before trying the same check through your SDK.
Validate the resulting resource as well as the patch
Suppose an account requires at least one notification channel. Each field may individually accept a Boolean, yet a patch that leaves both false can violate the account rule. Construct a candidate result from the current resource, validate the resulting state and only then persist the accepted update.
Apply authorization to the fields and operation being requested. A general patch transformer must not let a profile endpoint change a tenant ID, role or billing status because those properties happen to exist in storage. Use an explicit writable-field policy. The permission-matrix example provides a way to make those decisions testable.
HTTP PATCH requires the server to apply the set of changes atomically rather than expose a partially modified resource. The database transaction and the external effects of your endpoint still need an implementation design. IETF RFC 5789.
Move the test to the client-server boundary
Keep the local fixture as a reference, then send the same cases through the supported client. Capture request bytes, decoded field presence and the persisted resource. This catches a serializer that drops null values as well as one that adds them.
- Send an empty update and verify that no optional field disappears.
- Clear a field intentionally and inspect the serialized operation.
- Submit a forbidden field alongside a valid change and verify that no partial write appears.
- Retry a stale version after another editor changes an array and check the documented conflict response.
Add these cases when generating a new SDK version or changing a request-validation library. The failure often occurs before the patch algorithm runs, so a green unit test for that algorithm cannot close the integration check.
Sources
Documentation checked .

Dreamtsoft Editorial
The adapter counterexample is useful at the serializer boundary too. Capture the outgoing request bytes for an untouched form field and an intentionally cleared field. A correct server-side merge function cannot recover a distinction the client has already erased.