.NET Code Tips

Free .NET developer tool

JSON to C# Record & Class Generator

Paste a JSON payload and get strongly typed C# with System.Text.Json attributes — records or classes, nullable types, List<T>, dates and GUIDs detected.

Runs entirely in your browser — nothing you paste is uploaded.

3 types · Root.cs
using System.Text.Json.Serialization;

public record Root
{
    [JsonPropertyName("id")]
    public Guid Id { get; init; }

    [JsonPropertyName("orderNumber")]
    public int OrderNumber { get; init; }

    [JsonPropertyName("customer")]
    public Customer Customer { get; init; } = default!;

    [JsonPropertyName("createdAt")]
    public DateTimeOffset CreatedAt { get; init; }

    [JsonPropertyName("total")]
    public double Total { get; init; }

    [JsonPropertyName("lines")]
    public List<Line> Lines { get; init; } = [];

    [JsonPropertyName("tags")]
    public List<string> Tags { get; init; } = [];
}

public record Customer
{
    [JsonPropertyName("name")]
    public string Name { get; init; } = "";

    [JsonPropertyName("email")]
    public string Email { get; init; } = "";

    [JsonPropertyName("vip")]
    public bool Vip { get; init; }
}

public record Line
{
    [JsonPropertyName("sku")]
    public string Sku { get; init; } = "";

    [JsonPropertyName("quantity")]
    public int Quantity { get; init; }

    [JsonPropertyName("unitPrice")]
    public double UnitPrice { get; init; }

    [JsonPropertyName("notes")]
    public object? Notes { get; init; }
}

Using the generated types

The generated types work with System.Text.Json, built into .NET since .NET Core 3.0 — no Newtonsoft.Json package needed. Deserialize with the web defaults to get camelCase names and case-insensitive matching, the same settings ASP.NET Core uses for request and response bodies:

Program.cs
using System.Text.Json;

// Web defaults: camelCase names, case-insensitive matching, numbers from strings allowed.
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web);

Root? order = JsonSerializer.Deserialize<Root>(json, options);
string roundTrip = JsonSerializer.Serialize(order, options);

Calling a JSON API with HttpClient

The System.Net.Http.Json extension methods serialize and deserialize in one call, so posting JSON to a Web API no longer needs StringContent and a manual JsonConvert.SerializeObject. Create clients through IHttpClientFactory rather than new HttpClient() per request, which exhausts sockets under load.

OrdersClient.cs
using System.Net.Http.Json;

// Register once: builder.Services.AddHttpClient("orders", c => c.BaseAddress = new("https://api.contoso.com/"));
HttpClient http = httpClientFactory.CreateClient("orders");

// GET and deserialize in one call (uses JsonSerializerDefaults.Web).
Root? order = await http.GetFromJsonAsync<Root>("orders/10248", cancellationToken);

// POST an object as JSON and read the JSON response.
using HttpResponseMessage response = await http.PostAsJsonAsync("orders", order, cancellationToken);
response.EnsureSuccessStatusCode();
Root? created = await response.Content.ReadFromJsonAsync<Root>(cancellationToken);

Coming from the old "post JSON with HttpClient" tip?

The 2017 version of this site showed the .NET Framework pattern below. It still works, but the one-line PostAsJsonAsync replaces it and avoids buffering the JSON as a string.

Before and after
// .NET Framework era: serialize yourself and wrap in StringContent.
var json = JsonConvert.SerializeObject(order);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync("api/orders", content);

// Modern .NET equivalent of all three lines:
var response = await client.PostAsJsonAsync("api/orders", order);

How the types are inferred

  • Whole numbers become int, or long beyond ±2,147,483,647; fractional numbers become double (or decimal for money).
  • Strings in ISO 8601 date format become DateTime, or DateTimeOffset when they carry a Z or offset; GUID-shaped strings become Guid.
  • Every object in an array is merged, so a property that is missing or null in any item becomes nullable. Values with mixed types become JsonElement.
  • Objects with the same set of properties share one type, and array element types are named from the singular of the property (lines → Line).

Frequently asked questions

Should I generate C# records or classes from JSON?

Records are the better default for data transfer objects: they have value equality, a readable ToString, and init-only properties that make a deserialized response effectively immutable. Use classes when you need to mutate the object after deserializing it, or when a library (some ORMs, older serializers) requires a parameterless constructor with settable properties. System.Text.Json handles both.

Do I need [JsonPropertyName] if I use JsonSerializerDefaults.Web?

Not for simple camelCase names: JsonSerializerDefaults.Web (the default in ASP.NET Core and in HttpClient’s GetFromJsonAsync) turns on camelCase naming and case-insensitive matching, so a property named OrderNumber reads "orderNumber". You still need the attribute for names that don’t map by casing alone, such as snake_case keys like "order_number" or names that aren’t valid C# identifiers. Emitting it everywhere makes the contract explicit and survives option changes.

Why are some properties generated as nullable?

A property is made nullable when the JSON sample contains null for it, or when it is missing from at least one object in an array. Both mean real responses can omit it, so a non-nullable property would either hold a default value silently or, with the required modifier, make deserialization throw. Paste a representative sample with several array items to get the most accurate types.

How do I make System.Text.Json deserialization work with trimming and Native AOT?

Use the source generator: declare a partial class deriving from JsonSerializerContext, list each root type with [JsonSerializable(typeof(T))], and pass the generated type info to JsonSerializer, for example JsonSerializer.Deserialize(json, AppJsonContext.Default.Order). Turn on the "Source-generation context" option to have the stub generated for your types.