Practical .NET · Lesson 15

A JSON contract with explicit property names

Keep the wire format separate from an internal object model.

How it works

A serialized object is a data contract, not merely a convenient dump of every public property. External field names may need to stay stable even when internal C# names change. JsonPropertyName assigns an explicit wire name to a property, while JsonIgnore can exclude a property that belongs only to the in-memory model.

This separation is useful for small exported documents and messages. It lets the program keep descriptive C# naming conventions without making every refactor a format change. The attributes describe serialization behavior for the selected serializer; they do not automatically validate business invariants, encrypt values, or define a migration policy for old documents. Those responsibilities remain explicit parts of the application.

Walk through the example. The StockItem model exposes Code, Quantity, and an internal display note. Its attributes rename the first two serialized fields to lowercase names and exclude the note. Serializing the prepared instance therefore produces a compact object with only code and quantity. Deserializing that same text reconstructs the typed data, and the report prints the restored values separately from the JSON text.

Watch the boundary. Adding or removing a serialized property can affect existing readers even when the C# code compiles. Decide which fields are required, what a missing field means, and how unknown fields should be handled. Do not put secrets into a model and assume that a display-only property name will keep them out of an export; make exclusion explicit and verify the actual serialized output.

Practice independently. Create a message with an identifier, a quantity, and a computed display label. Define explicit wire names and omit the display-only label. Round-trip a prepared instance and compare the domain values. Then feed a document missing quantity and decide whether its default is acceptable or should be rejected by a separate validation step before the message enters the rest of the program.

A working example

using System;
using System.Collections.Generic;
using System.Linq;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;

var item = new StockItem { Code = "A1", Quantity = 3, Note = "screen only" };
string json = JsonSerializer.Serialize(item);
Console.WriteLine(json);
var restored = JsonSerializer.Deserialize<StockItem>(json)!;
Console.WriteLine($"{restored.Code}:{restored.Quantity}");
class StockItem
{
    [JsonPropertyName("code")] public string Code { get; set; } = "";
    [JsonPropertyName("quantity")] public int Quantity { get; set; }
    [JsonIgnore] public string Note { get; set; } = "";
}

Expected output

{"code":"A1","quantity":3}
A1:3
Try this
Define and test the serialized contract independently from display-only model details.