Data Formats & Serialization
How missing fields versus null define partial-update transitions
Separate Keep, null assignment and Remove, compare partial updates with replacement, and verify presence, action order and malformed instructions.

An update containing only some fields is a request to change state, not automatically a smaller replacement object. If a field is absent from that request, the server might leave it unchanged. If its value is null, the server might assign null or remove the field. Those choices produce different resulting objects and need an explicit contract.
The distinction in missing fields versus null becomes important when an application must support all of those intentions. A compact format can accidentally give the same encoding to assignment and deletion. The receiver needs distinct instructions for intentions that require distinct effects. The solution is to compare the required transitions with the format's expressive choices before relying on a successful request response.
Separate stored field states from requested actions
Consider a hypothetical profile field nickname. It can be absent, present with null, present with an empty string, or present with a nonempty string such as "Ada". These are four distinct state categories under the model. An absent field has no nickname entry; a null field has an entry whose value is null; an empty string has an entry containing a string of length zero.
Now define update actions independently: Keep makes no field change, Set(v) creates or replaces the field with value v, and Remove leaves the field absent. In this example v may be null or a string. Keep is an action even though its outcome can match the starting state. Remove is a different action that can also leave an already-absent field unchanged.
Starting from nickname = "Ada", Keep preserves "Ada", Set(null) produces a present null field, Set("") produces a present empty string, and Remove produces absence. Starting from absence, Set("Ada") creates the field. Starting from null, Keep preserves null while Remove removes its entry. The same action needs a defined result for every admitted starting state.
Business validation can forbid particular states, but that is another contract. If the profile requires a nickname, Remove may be rejected. If null is prohibited, Set(null) may be rejected. The format should still distinguish the requested action so that rejection explains the intended transition accurately. Silently treating an invalid null assignment as deletion changes what the caller asked for.
Define partial update semantics for an omission convention
One possible convention says an omitted field means Keep and a present value means Set of that value. Under this rule, an empty update object leaves nickname unchanged. A present null assigns null, and a present empty string assigns an empty string. The convention expresses assignments compactly but needs another representation for Remove.
Another convention uses omission for Keep and null as a deletion marker. It can leave a field unchanged, set a string, or remove the field. It cannot also use the same present null value to mean an ordinary null assignment without an additional distinction. A system supporting nullable state must decide whether that missing intention is acceptable.
Set(null) produces a present null entry; Remove produces no entry. Encoding both as {"nickname":null} leaves the receiver one instruction for two required effects. A deterministic contract must choose one meaning or add a distinguishing representation.
The online-migration guide supplies an intended-state comparison for partial update semantics. It treats updates and deletes as distinct captured changes and rejects stale work that would recreate a deleted row. The representation must retain the accepted change it is meant to install. Here Remove intends absence while Set(null) intends a present entry, so the instruction cannot erase that distinction. The peer coordinates stored-state authority and mutation ordering; it does not define this field-update grammar.
RFC 7396 gives a concrete JSON Merge Patch convention: a null-valued member in an object patch removes that member from the target. Its algorithm treats a non-object patch as replacement of the entire target. Those are the specified merge-patch rules, not universal meanings of null or omission. Our field model instead keeps null assignment available through a separate tagged action.
Use explicit deletion with an operation-tagged format
An operation-tagged representation can separate the actions directly. Stipulate that each mentioned field holds an instruction wrapper. The wrapper {"op":"set","value":null} means Set(null); {"op":"set","value":""} means Set of an empty string; and {"op":"remove"} means Remove. A field absent from the update object means Keep.
The wrapper namespace belongs to the update syntax. If a field type admits an object inside value, that object is assigned data; a property named op inside it does not automatically become another command. Our nickname field admits only null and strings. A more general format must still define that data-versus-instruction boundary.
Validate each instruction before applying it. A set without value is malformed, even if the server's language would return a default null for a missing property lookup. Remove with conflicting parameters should follow an explicit rejection or permitted-shape rule. Unknown operation tags should not silently become Keep. The action must be identified before value-type validation can enforce its requirements.
An operation-tagged format uses more structure than omission-based assignment. Its benefit is expressive completeness for the declared Keep, Set, and Remove transitions. That does not mean every application needs every transition. A nonnullable field with no deletion operation may intentionally use a simpler convention, provided clients and server agree on its narrower contract.
The runtime-replacement guide adds a representation-contract comparison for explicit deletion. A bare amount and a unit-tagged record require the corresponding reader to interpret their meaning. Recognizing a parameter or field name alone does not establish that interpretation. Here a client and endpoint likewise need to agree that the wrapper denotes a command, which command it denotes, and which content is assigned data. This is an action-meaning question within the declared update contract, without requiring a runtime replacement or deriving the grammar from the peer.
Evaluate replacement separately from partial updating
A full replacement request can have a different omission rule. If the replacement object contains no nickname, the new complete object may have no nickname. Under the partial-update convention, that same omission means Keep. An endpoint must declare whether it accepts a replacement state or a transition document; calling both inputs "profile data" leaves the difference unresolved.
For example, start with nickname = "Ada" and city = "Oslo". A partial update containing only city = "Rome" preserves nickname. A complete replacement containing only city = "Rome" removes nickname under the stipulated replacement contract. Both parse successfully, and both can be internally consistent. The endpoint contract selects the required result.
Nested fields need their own scope rule. Does mentioning an address object replace the whole address, or recursively update only its listed fields? A request changing only its postal code might otherwise preserve or remove the street unexpectedly. Recursive merging is not implied merely because the transport can carry nested objects. The update grammar and state transition must define it.
Collections introduce another independent choice. Setting an empty array can mean a present collection with zero elements; removing the collection field means absence. Appending an element is a different operation from assigning a complete array. This example does not prescribe a collection update language, but it shows why empty data cannot be treated as one universal deletion marker.
Analyze repeated application and composition
At the field-state level, the stipulated actions are idempotent. Applying Set("Ada") twice leaves the same nickname as applying it once. Applying Remove twice leaves it absent. Applying Keep twice preserves its starting state. These statements concern the resulting field value and presence, not external notifications, version counters, audit entries, or every effect a request may produce.
Order still matters between different actions. Starting from any state, Set("Ada") followed by Remove produces absence. Remove followed by Set("Ada") produces a present "Ada" field. Keep before or after an action is an identity operation for this field model. Set(null) followed by Set("") produces an empty string, and the reverse order produces null.
A server combining queued updates must preserve the declared order or derive an equivalent composed action. It cannot sort operation names or discard the earlier action solely because both target nickname without checking the semantics. For these overwrite/remove operations, the last non-Keep action determines the final field state. Other action families, such as increments or appends, require different composition rules.
Concurrency adds an admission question separate from action syntax. A caller may intend to update only if the profile still has a particular version. A version-conditioned request can reject when that prerequisite no longer holds under the server's atomic check-and-apply contract. The format's clear null/removal distinction does not itself prevent overwriting another caller's intervening assignment.
Test resulting state with presence and value checks
Construct a bounded matrix with the four starting state categories and actions Keep, Set(null), Set(""), Set("Ada"), and Remove. For each case, record both field presence and value. A getter that returns null for an absent property can hide the difference between absence and present null, so the test must use a presence check too.
Include rejection cases for missing set value, an unknown operation, a prohibited value type, and repeated action-field occurrences. Verify that malformed requests do not partially apply a result that contradicts the endpoint's declared atomicity. If the endpoint permits per-field partial success, its result must identify which transitions were accepted; that is another explicit contract.
Test the two-operation sequences in both orders and compare the final object. Also distinguish a partial city update from a whole-object replacement. Passing these cases establishes behavior for the exercised rules. It does not prove a different endpoint, client serializer, or untested nested-field path follows the same convention.
In a field-transition worksheet for the ASUS Vivobook 14 FHD laptop, keep the presence test separate from the returned value. The product description names Core 5 120U and lists 16GB DDR4 memory. Start with nickname "Ada", then record present-null after Set(null) and absent after Remove in separate rows. A getter returning null for both rows would hide the distinction. The worksheet checks the declared transition model, without establishing a server or serializer contract from the host description.
Compare action order beside the endpoint's replacement rule in a worksheet using the Lenovo IdeaPad 15.6-inch FHD touch laptop. Action-order rows should retain both requested transitions. Its processor is listed as i5-1334U, memory as 16GB DDR4 and storage as a 512GB PCIe SSD. Record Set("Ada") then Remove as absent, and the reversed sequence as present "Ada". These are state-model results, not a consequence of the listed hardware.