JSON to C# classes for an order
{
"orderId": 981,
"placedAt": "2026-11-02T16:05:00Z",
"customer": {
"id": "C-311",
"fullName": "Kofi Mensah",
"loyaltyTier": "gold"
},
"lines": [
{
"productId": "P-9",
"description": "Running shoes",
"quantity": 1,
"unitPrice": 119.99
},
{
"productId": "P-12",
"description": "Socks (3-pack)",
"quantity": 2,
"unitPrice": 12.5,
"discount": 0.1
}
],
"paid": true
}
With Root name set to Order, the root class comes out as:
using System.Text.Json.Serialization;
public class Order
{
[JsonPropertyName("orderId")]
public long OrderId { get; set; }
[JsonPropertyName("placedAt")]
public string PlacedAt { get; set; } = string.Empty;
[JsonPropertyName("customer")]
public Customer Customer { get; set; } = new();
[JsonPropertyName("lines")]
public List<Line> Lines { get; set; } = new();
[JsonPropertyName("paid")]
public bool Paid { get; set; }
}
and the customer as:
public class Customer
{
[JsonPropertyName("id")]
public string Id { get; set; } = string.Empty;
[JsonPropertyName("fullName")]
public string FullName { get; set; } = string.Empty;
[JsonPropertyName("loyaltyTier")]
public string LoyaltyTier { get; set; } = string.Empty;
}
The lines array also produces a Line class. Its discount key is only on the second line, so it becomes double?: array items are merged first, and a key that is missing or null anywhere turns nullable (full rules on JSON to TypeScript).
Choices the generator makes
- Numbers:
longfor whole numbers,doublefor decimals. - Arrays:
List<T>, with the item class named in the singular (linesgivesList<Line>). - Initializers: new projects enable nullable reference types, which warn (CS8618) when a non-nullable property could be null after construction. Strings therefore start as
string.Empty, objects and lists asnew(). - Nullable values:
?, as indouble?orCustomer?. - Names: PascalCase, with the exact JSON key in the attribute. A member can’t share its class’s name (CS0542), so such a property gets
Valueappended. - Mixed or null-only values:
object. - Dates:
placedAtstays astring. Both serializers read ISO 8601 text like2026-11-02T16:05:00Zinto aDateTimeOffsetif you change the type.
System.Text.Json or Newtonsoft.Json
Style only swaps the attribute and its using: [JsonPropertyName("orderId")] from System.Text.Json.Serialization, or [JsonProperty("orderId")] from Newtonsoft.Json. System.Text.Json matches names case-sensitively by default, so without the attribute orderId never fills OrderId. Newtonsoft.Json ignores case but still needs it for keys like loyalty_tier. ASP.NET Core’s web defaults ignore case too.
Deserialize the JSON
using System.Text.Json;
string json = File.ReadAllText("order.json");
Order order = JsonSerializer.Deserialize<Order>(json)!;
Console.WriteLine($"{order.Customer.FullName}: {order.Lines.Count} lines");
Deserialize returns null only for the literal JSON null, hence the !. In Newtonsoft.Json the call is JsonConvert.DeserializeObject<Order>(json). List<T> needs System.Collections.Generic, which SDK-style projects import implicitly. For other languages, the JSON Schema generator describes the same payload and JSON to Java builds the Jackson version.