Data and Formats · Lesson 4

Inspect a JSON document

Read selected fields without defining a complete object model.

Inspect a small JSON shape without building a model

JsonDocument provides a DOM-style view of parsed JSON. This can be useful when a tool needs to inspect a few members dynamically rather than define a complete model type first. The sample asks for one property named count and attempts to read an integer. Dynamic inspection still has a contract: the root should be an object, the property name matters, and its value must have an appropriate kind and range. A DOM does not remove those requirements; it makes the code responsible for expressing them.

Presence and integer conversion both succeed

The payload is an object containing numeric count three and string name rune. TryGetProperty finds count, so the right side of the short-circuiting condition is evaluated. TryGetInt32 represents that JSON number as an int, assigning value three. Only then does WriteLine run, producing 3. The unused name member remains available in the document but does not affect this calculation. The successful output proves the requested property was found and read for this payload, not that the entire object was validated.

if (document.RootElement.TryGetProperty("count", out var count) && count.TryGetInt32(out var value))

The document owns its element views

The using declaration keeps document alive for the inspection scope and disposes its owned resources afterward. RootElement and the selected count are views into that document’s data. Copying a JsonElement value does not independently copy all JSON storage. If a helper returns an element that must remain usable after disposing its document, it should return an appropriate clone or extract a standalone value while the document is alive. In this sample, the integer is extracted and printed before disposal, so that lifetime boundary remains clear.

TryGetInt32 does not accept every JSON kind

The word Try does not mean the entire operation is exception-free for every possible element. TryGetInt32 requires a JSON Number value; a string token containing the characters three can throw InvalidOperationException rather than simply return false. A nonobject root also violates TryGetProperty’s object requirement. For a defensive dynamic parser, check the relevant ValueKind before those accessors and distinguish wrong kind from an out-of-range or fractional number. The baseline payload satisfies those preconditions, which is why its compact condition works.

Practice: vary one structural fact at a time

Remove count and predict that the if body prints nothing because property lookup returns false. Restore count and change it to a fractional number such as three point five, then to a number outside int range. Compare those numeric conversion failures with changing count to a quoted string, which violates the accessor’s kind precondition. Keep the cases separate so an exception is not mistaken for an ordinary missing-property result, and restore the original payload after recording the behavior.

  • Record root kind, property presence, property kind, and conversion result as separate columns.
  • Add an explicit message for each rejected category when turning this inspection into a user-facing parser.

Practice: return useful data across a helper boundary

As an independent exercise, move parsing into a helper that returns the extracted integer rather than a JsonElement. Explain why the integer remains usable after the helper’s using scope ends. Then explore returning a selected element directly and compare it with returning Clone. The task is to choose the result the caller actually needs, not to keep every document alive indefinitely. A scalar, a copied subtree, and transferred document ownership represent three different lifetime contracts.

  • Test the helper result after the helper returns; inspecting only inside its using scope cannot expose a borrowed-element lifetime bug.
  • Keep ownership decisions explicit even though JsonElement itself is a value type.

Preserve meaningful failure distinctions

A small dynamic reader can remain simple while stating its preconditions precisely. Check shape, presence, kind, range, and document lifetime at the boundary where each matters. The sample’s expected number is easy to verify, but realistic input tables should include missing members, wrong roots, wrong kinds, invalid numeric ranges, and malformed syntax. These checks use managed JSON features available in the browser runtime; they do not require desktop file APIs or imply that every arbitrary JSON structure fits the application’s domain.

  • Baseline output is exactly 3.
  • Presence lookup and numeric conversion establish different facts.
  • JsonElement access depends on its owning document, and Try-style accessors still have documented preconditions.

A working example

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

using var document = JsonDocument.Parse("""{"count":3,"name":"rune"}""");
if (document.RootElement.TryGetProperty("count", out var count) && count.TryGetInt32(out var value))
    Console.WriteLine(value);

Expected output

3
Try this
Read JsonElement values while their owning document is alive.

Further reading