Reading a model is not the same as validating it
Deserialization translates an external representation into a .NET model, while validation decides whether that model is acceptable for the application. The sample performs both steps, but its validation is deliberately small: the resulting item must be nonnull and have a positive Count. Representation and domain meaning remain separate boundaries. Valid JSON can describe an invalid stock item, and a correctly constructed record can still contain values the application should reject. Identify the actual condition being checked rather than treating the method name Deserialize as a guarantee of complete input safety.
View full size ↗Read the diagram
The diagram begins with JSON text containing age = 24. Deserialization maps it to a DTO with Age = 24. Map the JSON property name age explicitly, for example with JsonPropertyName, or configure an appropriate naming policy; the default serializer is case-sensitive.
The application then checks its rule age >= 0. The value 24 passes and can become the domain value Age(24). This is a separate conceptual example from the runnable lesson.
Malformed JSON is rejected at the deserialization boundary. A value such as age = -2 can be a valid JSON integer and deserialize successfully, but it fails this domain rule and produces a validation error instead.
Deserialization and domain validation are separate boundaries. Missing JSON properties can become default DTO values, depending on the contract. Define required values, allowed ranges and relationships explicitly; the serializer does not automatically enforce business rules.
Illustration created with ImageGen for SharpRune.
Follow the exact payload into the record
The JSON object supplies Name as rune and Count as three. JsonSerializer constructs the StockItem record from those members. The property pattern then matches the nonnull item because three is greater than zero, so the conditional expression selects item.Name and prints rune. If Count were zero or negative with the rest unchanged, the pattern would fail and the program would print invalid. The successful baseline demonstrates one accepted payload and one particular positive-count rule, not a complete schema for every possible stock item.
Console.WriteLine(item is { Count: > 0 } ? item.Name : "invalid");Keep syntax, shape, and domain failures distinguishable
Malformed JSON can throw during deserialization before the conditional expression runs. A JSON null root can instead produce a null result that the property pattern rejects. A nonnull model with a nonpositive Count reaches the same invalid label through a domain condition. These paths may deserve different diagnostics in an import tool. The sample’s compact output does not expose all of them, so a larger boundary should decide which distinctions callers need rather than catching everything and pretending every rejected input has the same cause.
The sample does not validate the name
A positive Count alone does not prove Name is nonblank or even nonnull under the ordinary serializer options used here. Supplying an empty or null name can satisfy the displayed Count pattern and lead to a blank printed line. A nonnullable C# annotation is not an automatic universal runtime validator; .NET9 has explicit serializer nullability options with documented limits. The lesson’s honest conclusion is that one rule was checked. Add name, presence, and schema rules deliberately when the application’s contract requires them.
Practice: isolate the quantity rule
Keep Name fixed as rune and test counts negative one, zero, one, and three. Write the expected invalid or rune label for each before running. Then change the Count value kind to a string in a separate input and observe the representation boundary instead of confusing it with a nonpositive number. Restore the numeric baseline after the experiment. This table makes it clear which failures happen while mapping JSON and which happen after a model has been created successfully.
- Use the unchanged baseline payload to verify the expected single output line
rune. - Keep malformed JSON tests separate from valid JSON whose values violate a domain condition.
Practice: define a fuller stock-item contract
For an independent extension, require a nonblank Name and a Count between one and one hundred. Test missing, null, empty, whitespace-only, and accepted names alongside boundary counts. Decide whether missing required members should be a serializer contract or a post-deserialization validation result. Give rejected cases a meaningful reason rather than allowing an empty line to masquerade as success. The purpose is to design the intended input model, not to accumulate checks without knowing what each guarantees.
- Do not silently replace a missing name with a plausible product name unless that fallback is an explicit requirement.
- If several validation errors are useful to the user, collect them without losing which input member each concerns.
State exactly what successful parsing proves
A reliable boundary explains its accepted JSON shape, member mapping, presence rules, and domain restrictions. The sample uses local System.Text.Json and a simple record, which fits this browser runtime without relying on a remote compiler or desktop file access. Extend its guarantees one layer at a time and test each layer independently. A useful success result means the agreed rules passed; it should not claim more merely because a serializer returned an object or a pattern happened to match one property.
- Baseline output is
runebecause Count three satisfies the displayed pattern. - Malformed JSON and a rejected domain value are different failure categories.
- The existing sample checks positive Count, not a complete nonnull-name and schema contract.
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;
var item = JsonSerializer.Deserialize<StockItem>("""{"Name":"rune","Count":3}""");
Console.WriteLine(item is { Count: > 0 } ? item.Name : "invalid");
record StockItem(string Name, int Count);
Expected output
rune
Decoding a type does not validate the meaning of every value.