JSON to C# Classes: Generate .NET Types Fast
You have a JSON payload from an API and you need to deserialize it in C#. Writing the class by hand means converting camelCase JSON keys to PascalCase properties, adding [JsonPropertyName] attributes for every field that doesn't match, figuring out which fields can be null, and defining sub-classes for nested objects. For a response with 15 fields and two nested objects, that's 30+ lines of boilerplate before you write a single line of logic.
Generate the scaffold from your JSON instead, then refine it.
JSON Types to C# Types
The mapping from JSON primitives to C# types is mostly direct, with a few decisions to make.
| JSON type | C# type | Notes |
|---|---|---|
string | string | Nullable in C# 8+ unless initialized |
number (integer) | int or long | Use long if values exceed 2,147,483,647 |
number (float) | double or decimal | decimal for financial data |
boolean | bool | Direct mapping |
null | T? | Make the type nullable |
object | nested class | Define a separate class |
array | List | T is the element type |
number. JSON doesn't distinguish between integers and floats - 42 and 42.0 are both valid. When you see 42 in a payload, use int. When you see 42.5 or you know the field can be fractional, use double. For currency, always use decimal.
A Real Example: Stripe Charge Object
Take a simplified Stripe charge response:
The generated C# class with System.Text.Json attributes:
Paste your own API response into the JSON to C# class generator to produce this in one click.
System.Text.Json vs Newtonsoft.Json
.NET 5+ ships System.Text.Json in the standard library. Newtonsoft.Json (Json.NET) is the older library that dominated for years and is still common in legacy codebases and NuGet packages.
The attribute names differ:
| Purpose | System.Text.Json | Newtonsoft.Json |
|---|---|---|
| Map JSON key to property | [JsonPropertyName("key")] | [JsonProperty("key")] |
| Ignore a property | [JsonIgnore] | [JsonIgnore] |
| Enum as string | [JsonConverter(typeof(JsonStringEnumConverter))] | [JsonConverter(typeof(StringEnumConverter))] |
System.Text.Json. It's faster, allocates less, and has no extra dependency. The only reason to reach for Newtonsoft is if you need features like polymorphic deserialization with $type discriminators in .NET 6 or earlier (STJ added limited support in .NET 7).
Handling Nullable Fields
C# 8 introduced nullable reference types. If your project has in the .csproj (the default for new .NET 6+ projects), non-nullable string properties will produce compiler warnings if they might be null.
For fields the API guarantees will always be present, initialize with a default:
For fields that can legitimately be null (like failure_message above), use the nullable form:
Don't use string? everywhere just to silence warnings. That hides real nullability information and leads to null reference exceptions downstream. If you know the API always returns a value, make the property non-nullable.
Arrays and Lists
When a JSON field is an array of objects, the C# property should be List where T is a class matching the element shape.
Initialize list properties with = new() to avoid null reference issues when the JSON field is absent or the array is empty.
Deserializing the Result
Once you have the class, deserialization is one line:
For Newtonsoft.Json:
If you're using HttpClient in an ASP.NET Core project, skip the intermediate string and deserialize directly from the response:
ReadFromJsonAsync handles encoding, buffering, and disposal automatically.
One thing to know: if the JSON contains fields not in your class, System.Text.Json silently ignores them by default. Usually that's fine. If you want strict validation (fail on unknown fields), configure the serializer:
UnmappedMemberHandling requires .NET 8+. In earlier versions, use a custom converter or switch to Newtonsoft, which has MissingMemberHandling.Error.
Use the JSON to C# generator to get the initial classes, then run your payload through the JSON validator to confirm it's well-formed before you start wiring up deserialization. If you're also generating TypeScript interfaces for a frontend that talks to the same API, you can paste the same JSON into both tools and get consistent types across your stack.