Skip to content
Fundamentals

null vs Missing vs Empty String in JSON: Which One Should Your API Send?

·7 min read
null vs Missing vs Empty String in JSON: Which One Should Your API Send? - Fundamentals guide from JSON Prettifier

Short answer: JSON can express "nothing" three ways, and they are not interchangeable. null means the field exists and is deliberately empty. A missing key means the field was not provided, which in a PATCH means "leave it alone". An empty string "" is a real value of length zero. There is no undefined in JSON; JavaScript drops those keys when serializing. Pick one meaning per field, write it in the schema, and send [] rather than null for empty lists.

A mobile client sends {"nickname": null} to clear a nickname. The server's update code does if (body.nickname) user.nickname = body.nickname, sees a falsy value, and skips it. The nickname never clears. Two weeks later another client sends {"nickname": ""} and that one clears it, but now the profile page shows an empty badge where it used to show nothing. All three representations are "nothing", and the code treated them as the same thing.

Use JSON Compare to inspect added, removed, and changed values in two non-sensitive sample documents.

The Three Ways to Say Nothing

{"nickname": null}   // 1. explicit null: the value is known to be empty
{}                   // 2. missing key: nothing was said about nickname
{"nickname": ""}     // 3. empty string: a string value with zero characters

From a parser's point of view these are three different documents. From a consumer's point of view the difference is intent. The first was a decision, the second was silence, and the third is data. A fourth option, undefined, exists only in JavaScript memory; the moment you serialize, it collapses into option two.

JSON.stringify({ a: null, b: undefined, c: "" });
// {"a":null,"c":""}            <- b vanished

JSON.stringify([null, undefined, ""]);
// [null,null,""]               <- in arrays, undefined becomes null

That asymmetry (dropped in objects, nulled in arrays) is the source of a lot of confusion, and it is covered with the rest of the serializer rules in the JSON.parse and JSON.stringify guide.

How Each Language Maps Them

LanguagenullMissing keyEmpty string
JavaScriptnullundefined (key dropped by stringify)""
TypeScriptstring | nullfield?: stringstring
PythonNoneKeyError / dict.get() -> None""
Gonil pointer (*string)omitempty / omitzero"" (zero value)
Java (Jackson)nullNON_NULL / NON_ABSENT include rule""
SQLNULLNo column / DEFAULT'' (not NULL)

Python: None goes both ways

json.dumps({"a": None}) gives {"a": null}, and a missing key is simply not in the dict. The trap is on the read side: data.get("a") returns None for both a null value and an absent key, so if you need to tell them apart, use "a" in data first. The Python json module guide has more on the type mapping.

Go: omitempty is not what you think

Go's omitempty tag drops the field when it holds the type's zero value. For a string that is "", for an int it is 0, and for a bool it is false, so a user who turns a setting off produces a payload with no setting at all. To represent all three states you need a pointer:

type Profile struct {
    Nickname *string `json:"nickname,omitempty"`
}
// nil pointer            -> key omitted
// pointer to ""          -> "nickname": ""
// no way to emit null with omitempty; drop the tag to get "nickname": null for nil

Newer Go releases add omitzero, which treats a nil pointer and a zero struct the same way and works better with time values; check the encoding/json docs for your version.

Java and TypeScript: making the states explicit

Jackson's @JsonInclude(JsonInclude.Include.NON_NULL) turns every Java null into a missing key, which is convenient until you need to send a deliberate null. For three-state fields, the usual approaches are Optional<T> with the jdk8 module and Include.NON_ABSENT, or a wrapper such as JsonNullable from the OpenAPI generator. TypeScript can express it in the type itself:

interface ProfilePatch {
  nickname?: string | null;
  // undefined -> not sent (leave alone)
  // null      -> sent as null (clear)
  // string    -> sent as value (set)
}

With the exactOptionalPropertyTypes compiler flag, nickname?: string no longer accepts an explicit undefined, which keeps "not provided" and "provided as nothing" from blurring together in your own code.

PATCH Semantics: null Clears, Missing Leaves Alone

This is the place where the distinction stops being philosophical. JSON Merge Patch (RFC 7386) defines it precisely: a key set to null in the patch removes that member from the target; a key that is not in the patch is untouched.

// Current resource
{"name": "Priya", "nickname": "pri", "phone": "+91 98..."}

// PATCH body (Merge Patch)
{"nickname": null, "phone": "+91 99..."}

// Result
{"name": "Priya", "phone": "+91 99..."}

An update handler that uses a truthiness check breaks this contract on the first null. The correct test is key presence:

// Express
for (const field of ["nickname", "phone"]) {
  if (Object.hasOwn(req.body, field)) {
    user[field] = req.body[field]; // null is a valid instruction here
  }
}

If you need to set a field to null and address array elements, Merge Patch cannot express the second part; that is what JSON Patch is for. The JSON Patch vs Merge Patch post compares the two.

Declaring It in JSON Schema

A schema is where the decision gets written down. "May be null" and "may be missing" are two separate keywords:

{
  "type": "object",
  "properties": {
    "name":     { "type": "string", "minLength": 1 },
    "nickname": { "type": ["string", "null"] },
    "phone":    { "type": "string" }
  },
  "required": ["name", "nickname"]
}
// name:     must be present, must be a non-empty string
// nickname: must be present, may be null
// phone:    may be missing; if present, must be a string (null rejected)

Note minLength: 1 on name. Without it, "" passes as a valid string, which is almost never what a required name means. OpenAPI 3.0 used a separate nullable: true keyword; OpenAPI 3.1 adopted the JSON Schema type array. If you are writing your first schema, JSON Schema explained covers required and the type keywords from the start.

Where the Database Fits In

SQL has its own version of this split, and it does not line up with JSON by accident of defaults. A NULL column maps naturally to JSON null, and an empty VARCHAR maps to "", but "missing key" has no column equivalent at all: a row always has every column. So an API that omits unset fields is doing that translation in application code, and the rule needs to be written down somewhere a new developer will find it.

-- These are different rows
SELECT * FROM users WHERE nickname IS NULL;   -- never set, or cleared
SELECT * FROM users WHERE nickname = '';      -- set to empty on purpose (or by a sloppy form)

-- One normalization, applied on write, removes the ambiguity
UPDATE users SET nickname = NULLIF(TRIM(nickname), '');

Document stores are looser. MongoDB distinguishes a field set to null from a field that does not exist, and { nickname: null } as a query matches both unless you add $exists. PostgreSQL's jsonb likewise keeps null values as members, so data ? 'nickname' is true for a null member and false for a missing one. Whatever the store, the API contract should not change based on which one is underneath.

Recommendation Table

SituationSendWhy
Value is known to be absent (no middle name)nullExplicit; client can distinguish from "not fetched"
Field not requested or not applicable to this typeOmit the keyKeeps payloads small; schema marks it optional
User typed nothing in a text field"" or null, pick one and document itMixing both doubles every client check
PATCH: clear a valuenullMerge Patch semantics: null means delete
PATCH: leave a value aloneOmit the keyMissing means untouched
Collection with no items[] (never null)Clients can iterate without a guard
Nested object not loadedOmit or null, consistentlyDocument which; never an empty {} that looks loaded

The row that generates the most argument is the empty text field. Either answer works; the failure mode is having both null and "" in the same column of the same table. Normalize at the boundary (for example, trim and convert empty input to null before it reaches the database) and the rest of the system only ever sees one form.

Common Mistakes

  • Truthiness checks on incoming fields. if (body.count) treats 0, "", false, and null identically. Test for key presence or !== undefined instead.
  • Returning null for empty arrays. {"items": null} forces every client to guard before .map(). Send []. The array vs object guide covers the related choice of container shape.
  • Depending on undefined surviving the wire. It never does. A TypeScript type that says undefined describes memory, not the payload.
  • Mixing conventions across endpoints. One endpoint omits unset fields, another sends them as null, and the shared client code has to know which is which. Decide once for the whole REST API.
  • Database defaults disguising the problem. A column with DEFAULT '' turns every omitted field into an empty string on insert, so the API appears to "convert" missing to empty. Check the schema before blaming the serializer.

When you are auditing an existing payload for mixed conventions, paste a few responses into JSON Prettifier with Sort Keys on; aligned keys make a null next to a missing key next to an "" easy to spot.

Query filters introduce another boundary: a present false-like value can behave differently from an absent property across evaluators. These JSONPath migration checks include explicit fixtures for null, false, zero, empty strings, and missing fields.

Frequently Asked Questions

What is the difference between null and undefined in JSON?
JSON has null but no undefined. In JavaScript, a property set to undefined is dropped entirely by JSON.stringify, so on the wire the only options are a null value or a missing key. null says the value is known to be empty; a missing key says nothing about it.
Should an API return null or omit the field?
Return null when the field exists for this resource but has no value, and omit it when the field does not apply or was not requested. For PATCH requests the rule is fixed by convention: null clears the field, a missing key leaves it unchanged.
Is an empty string the same as null in JSON?
No. "" is a string of length zero and null is the absence of a value. A database treats them differently (WHERE name = '' does not match NULL), validators treat them differently, and so should your API. Pick one meaning per field and document it.
How do I allow null in JSON Schema?
Use a type array: {"type": ["string", "null"]}. A field that may be missing is handled separately by leaving it out of the required array. OpenAPI 3.0 used nullable: true instead; OpenAPI 3.1 follows JSON Schema and uses the type array.
Why does JSON.stringify remove my undefined values?
Because JSON has no way to write undefined. JSON.stringify skips object properties whose value is undefined, a function, or a Symbol, and writes null for those values inside arrays. If you need the key present, set it to null before serializing.

Audit Your Payloads for Mixed Nulls

Format a response, sort the keys, and see which fields are null, missing, or empty at a glance. Runs entirely in your browser.

Format My JSON