JSON
null vs. missing: the JSON difference that breaks APIs
In JSON, {"nickname": null} and {} are not the same document. The first says "this field exists and has no value"; the second says nothing about the field at all. APIs routinely treat them differently, most visibly in PATCH requests and JSON Merge Patch, where null means "delete this field" and a missing key means "leave it alone", so any tool that compares JSON must keep the two cases apart.
This post explains where the difference matters, the JavaScript and serializer habits that blur it, and how JSON Mirror reports each case when you diff two documents.
What null and a missing key mean in JSON
JSON, as defined in RFC 8259, has six kinds of value: object, array, string, number, the literals true and false, and the literal null. There is no undefined.
- Explicit null: the key is present in the object and its value is
null.Object.keyslists it; the document is one member larger. - Missing key: the key is absent. The document makes no statement about it.
Whether those two states mean different things is up to the API. But because they can mean different things, a parser, a serializer, or a diff that silently converts one into the other is changing the message.
JSON Merge Patch: null means remove
The clearest example is JSON Merge Patch, specified in RFC 7396 and sent with the media type application/merge-patch+json. A merge patch looks like a partial copy of the resource, and the rules are short:
- A member with a non-null value in the patch replaces (or adds) that member in the target. Nested objects are merged recursively.
- A member set to
nullin the patch removes that member from the target. - A member that is absent from the patch is left unchanged.
Given this stored user:
{"name": "Alex", "nickname": "Al", "email": "alex@example.com"}
these three patches do three different things:
{} -> nothing changes
{"nickname": "Lex"} -> nickname becomes "Lex"
{"nickname": null} -> nickname is deleted:
{"name": "Alex", "email": "alex@example.com"}
One consequence the RFC itself points out: merge patch is a poor fit for documents that rely on explicit null values, because you cannot use it to set a member to null. Arrays are also replaced wholesale rather than edited. If you need either of those, JSON Patch (RFC 6902) uses explicit add, remove and replace operations instead.
PATCH endpoints and partial updates
Plenty of PATCH endpoints follow the same convention without formally implementing RFC 7396: send only the fields you want to change, send null to clear a field. That convention only works if every layer between the client and the database preserves the difference between "absent" and "null". Common ways it breaks:
- The server deserializes the body into a typed object where a missing field and a null field both end up as
null. The handler then clears every field the client did not send. - The client builds the body from a form model and serializes every property, so untouched fields go out as
nulland wipe stored data. - The client's serializer is configured to omit nulls, so a deliberate "clear this field" request arrives as an empty object and does nothing.
The fix on the server side is usually to inspect the raw JSON tree for key presence (for example Jackson's JsonNode.has("nickname"), or 'nickname' in body in Python) before mapping it into a model.
JSON null vs undefined in JavaScript
JavaScript has two "empty" values, and the language's own operators make it easy to merge them by accident:
const missing = JSON.parse('{}');
const isNull = JSON.parse('{"nickname": null}');
missing.nickname === undefined; // true
isNull.nickname === undefined; // false (it is null)
missing.nickname == null; // true
isNull.nickname == null; // true (null == undefined is true)
'nickname' in missing; // false
Object.hasOwn(isNull, 'nickname'); // true
Things to remember:
obj.key === undefinedis true both when the key is missing and when it exists with the valueundefined. It cannot tell you whether the key is there.null == undefinedistrue, so a loose== nullcheck treats null and missing identically.null === undefinedisfalse.JSON.stringifydrops object properties whose value isundefined:JSON.stringify({nickname: undefined})produces{}, whileJSON.stringify({nickname: null})produces{"nickname":null}. (Inside arrays,undefinedbecomesnull.)- To test for presence, use
Object.hasOwn(obj, 'key')or'key' in obj. Theinoperator also looks up the prototype chain, which rarely matters for parsed JSON but is worth knowing.
So if you want a JavaScript client to send "clear this field", set it to null. If you want "leave it alone", delete the property or never set it; undefined also works, but only because JSON.stringify happens to drop it.
Serializers that omit null fields
Many JSON serializers have an option to omit null fields from the output, often to make payloads smaller. In Java's Jackson, for example, annotating a class or property with @JsonInclude(JsonInclude.Include.NON_NULL) leaves null-valued properties out entirely.
That setting is harmless for read-only responses where clients treat null and missing the same way. It is risky for:
- request bodies sent to a PATCH or merge-patch endpoint, where null carries meaning;
- responses whose consumers check for key presence, such as schemas with required-but-nullable fields;
- snapshot or contract tests, where flipping the setting changes every fixture at once.
If you change a serializer setting like this, diff a before and after payload to see exactly which keys appeared or disappeared.
How a JSON diff should report null vs missing
A diff that treats null and missing as equal will hide exactly the changes described above. JSON Mirror checks key presence first and compares values second, so each case gets its own entry. Here is what it reports for the field $.nickname:
| Original | Updated | Kind | Explanation shown |
|---|---|---|---|
{} | {"nickname": null} | added (value null) | This value exists only in the updated document. |
{"nickname": null} | {} | removed (value null) | This value was removed from the original document. |
{"nickname": null} | {"nickname": "Sam"} | changed | The value type changed from null to string. |
{"nickname": "Sam"} | {"nickname": null} | changed | The value type changed from string to null. |
Two points worth noting:
- An added or removed
nullis still a real change. If an API response starts including"nickname": nullwhere it used to omit the key, that shows up as an added entry, not as "no difference". - Going from
nullto a value is a changed entry with a type change, becausenullis its own JSON type. Comparing{"nickname": null}with{"nickname": null}reports nothing.
Key order and whitespace are ignored, so only presence and value differences appear; see why key order and whitespace shouldn't show up in a JSON diff. Paths such as $.nickname are explained in reading JSON paths in a diff.
Checking null vs missing without a diff tool
If you only need to check one field, a one-liner is enough. In jq, .nickname returns null for both cases, so use has:
jq 'has("nickname")' user.json
In Python, d.get("nickname") returns None for both, while "nickname" in d tells them apart:
import json
d = json.loads('{"nickname": null}')
print("nickname" in d, d.get("nickname")) # True None
For whole documents, a structural diff is easier than writing these checks by hand.
Try it
Paste a request body before and after a serializer change, or an API response from two releases, into JSON Mirror and press Compare JSON. Keys that went from missing to null, or from null to missing, appear as separate added and removed entries, each with its path and both values side by side. Everything runs in your browser tab and nothing is uploaded.
Frequently asked questions
Is a null value the same as a missing key in JSON?
No. A key with a null value is present in the object and explicitly has no value, while a missing key is simply not there. Many APIs give them different meanings, for example clearing a field versus leaving it alone.
What does null mean in a JSON Merge Patch?
Under RFC 7396, a member set to null in the patch tells the server to remove that member from the target document. A member that does not appear in the patch is left unchanged.
Does JSON have undefined?
No. JSON only has objects, arrays, strings, numbers, true, false and null. JavaScript's JSON.stringify drops object properties whose value is undefined, so they come out as missing keys rather than null.
How do I check whether a key exists in a JavaScript object, even if its value is null?
Use Object.hasOwn(obj, 'key') or 'key' in obj. Comparing obj.key to undefined cannot tell a missing key from one explicitly set to undefined, and obj.key == null is true for both null and missing.
How does JSON Mirror report a key that changes from missing to null?
It reports an added entry at that path with the value null. The reverse, a null key that disappears, is reported as removed, and null becoming a string is a changed entry with a type change from null to string.