Data and Formats · Lesson 13

Missing properties and explicit JSON null

Preserve the difference between absent, null, and ordinary default values.

How it works

JSON can distinguish an absent property from a property explicitly containing null. A property holding zero is a third, ordinary numeric case. These distinctions often matter for partial updates, imports, and configuration. An absent setting may mean keep the existing value, while an explicit null may mean clear it.

TryGetProperty returns false for missing without throwing. The present property exists and has JsonValueKind.Null. The number property also exists, but its integer value is zero. Looking only at a deserialized integer's default value could lose the evidence that distinguishes a missing field from an explicitly supplied zero.

GetProperty is useful when presence is already guaranteed by the contract, but it throws if the requested name is absent. TryGetProperty expresses optional presence more directly. After finding a property, still validate its ValueKind before reading it as a number, string, array, or object.

Do not use null as a universal error marker without considering what the input format already means by null. A parser result should communicate malformed structure, absent data, explicit null, and accepted values in a way the next stage can handle deliberately. Collapsing those cases too early makes later validation ambiguous.

Practice by parsing four payloads: an empty object, a null present property, a numeric present property, and a string present property. Write an inspection function that returns a distinct label for each permitted case. Decide which cases are invalid for your chosen application contract instead of guessing from their default C# values.

Finally, design a small partial-update model containing a presence flag and a possible replacement value. Explain how the model represents no change versus clearing the value. The essential lesson is that a useful parser preserves meaningful distinctions until the domain boundary explicitly decides which ones may be combined.

A working example

using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;

using var document = JsonDocument.Parse("""{"present":null,"number":0}""");
JsonElement root = document.RootElement;
Console.WriteLine(root.TryGetProperty("missing", out _));
Console.WriteLine(root.GetProperty("present").ValueKind);
Console.WriteLine(root.TryGetProperty("number", out var number));
Console.WriteLine(number.GetInt32());

Expected output

False
Null
True
0
Try this
Check presence separately from the value kind of a property that exists.