Absent, Null or Blank: A Truth Table for Three Kinds of Nothing in an Import
An import reads JSON from an API and writes to a local record. A field can arrive in three quiet ways: the key is missing, the key is present with null, or the key is present with an empty string. Code that tests only for falsy values treats all three alike, and then an import silently erases data that a source never meant to touch. This article proposes a truth table that decides each case on purpose. It is a conditional design suggestion. Whether a given source distinguishes these states is something you must check in that source's own documentation or samples.
Step one: classify each field
Pick a policy per field, not per import.
- Optional text, clearable: absence means no change, null means clear, empty string means clear to blank.
- Optional text, not clearable by import: absence and null mean no change, empty string is flagged.
- Required: absence means no change, null and empty string are rejected for review.
The truth table
Use three hypothetical fields on a local record that already holds values: phone (optional, clearable), note (optional, not clearable by import), display name (required). Local values before import: phone 0000 111, note Gate code at side door, display name Rana Shop.
Incoming state, then result:
- phone missing: keep 0000 111.
- phone null: clear to no value.
- phone empty string: set blank, recorded as blank and not as no value.
- note missing: keep existing note.
- note null: keep existing note.
- note empty string: keep existing note and raise a review item.
- display name missing: keep Rana Shop.
- display name null: keep Rana Shop and reject the change.
- display name empty string: keep Rana Shop and reject the change.
That is 9 cells, 3 fields times 3 states. Count them in the fixture so none is forgotten.
Worked hypothetical
A fictional payload is: { phone: null, note: '', displayName missing }. Applying the table gives phone cleared, note unchanged with 1 review item, display name unchanged with no rejection since the key was absent. Outcome counts: 1 field changed, 2 fields unchanged, 1 review item, 0 rejections. If a tester expects the note to be wiped, the table, not the code, is the place to argue.
Decision branches
Branch A, the parser cannot tell missing from null: some libraries flatten them. Then the table cannot be honoured. Change the reader to keep key presence, or treat every ambiguous case as no change and say so.
Branch B, the source documents that null means unknown: map null to no change, not to clear, and write that in the policy.
Branch C, a partial update versus a full replacement payload: in a full replacement the source may omit keys it means to clear. Decide per source, and never infer it from one sample.
Stop conditions
Stop the import and hold the batch if more rows than you chose as a limit hit the reject or review outcomes, since a mapping fault is more likely than that many bad records. Do not auto retry writes to a remote system after an uncertain response.
Proposed acceptance tests
These are proposals, not results.
- Run all 9 combinations against a record with the values above and compare with the table cell by cell.
- Confirm phone empty string stores blank and phone null stores no value, and that a display shows them as intended.
- Confirm that after an import with every field missing, the record is byte for byte unchanged.
- Feed a reader that drops key presence and check the run reports Branch A and does not clear anything.
- Repeat the same payload twice and check the second run changes nothing.
Keep it visible
Store the table next to the mapping code and show review items to an operator with the field name and incoming state, so the three kinds of nothing stay distinguishable.