JSON to C#

Generate C# classes from a JSON sample — PascalCase properties pinned to their JSON names with [JsonPropertyName], nullable value types where keys come and go or turn null, List<T> for arrays, and object for every slot the sample cannot settle, reported in the notes. Free, no sign-up — runs locally in your browser.

Your work is auto-saved in this browser only, so a refresh won't lose it. Nothing is uploaded.

Why generate C# classes from a sample?

C# wants its model classes up front, and a real API response does not care: it hands you camelCase keys, nested objects, arrays of things that only sometimes have all their fields, and the occasional value that is null until it isn't. Writing the DTOs by hand means transcribing all of that into PascalCase properties, deciding which ones must be nullable, and remembering to pin every renamed property to its wire name. The sample already contains those decisions as facts. This converter reads them off mechanically: objects become classes, arrays become List<T>, absence and null become ? on value types, and renames get their [JsonPropertyName] attributes written for you — with the judgement calls listed, not hidden.

How to use this tool

  1. Paste the JSON. A real API response is ideal; include several array elements if you have them, because absence across elements is how nullable properties are discovered.
  2. Name the root class. Root is fine for a quick look; for code you will keep, use the name it will wear in your project.
  3. Press Generate C#. The root class comes first, the classes it references after it.
  4. Read the notes if any appear. They list every slot that became object, every reference type whose key sometimes goes missing, and every name collision — nothing was decided silently.
  5. Copy or download the result, then refine it: narrow long where your domain knows better, swap in DateTimeOffset or Guid where strings are really those, and let the compiler take it from there.

The rules it follows

Numbers. Whole numbers become long; any fraction widens the slot to double — a JSON 1.0 is the number 1 to every parser, so a field your API always sends as 2.0 reads as an integer sample unless a real fraction appears somewhere. Names. Classes and properties are PascalCase; whenever that changes the spelling, a [JsonPropertyName] pins the original key, and the System.Text.Json.Serialization using appears only when at least one attribute does. Absence and null. Missing from some elements or sometimes null → long? / double? / bool? for value types; reference types stay plain and the absence is noted. Null everywhere or genuinely mixed types → object, always noted. Merging. Identical shapes — same keys, same types — share one class instead of being emitted twice.

Worked example

Given this sample (the second user has no nickname, and the keys are camelCase):

{
  "teamName": "Platform",
  "users": [
    { "id": 1, "firstName": "Ada", "nickname": "ada" },
    { "id": 2, "firstName": "Grace" }
  ]
}

the converter emits:

using System.Collections.Generic;
using System.Text.Json.Serialization;

public class Root
{
    [JsonPropertyName("teamName")]
    public string TeamName { get; set; }
    [JsonPropertyName("users")]
    public List<User> Users { get; set; }
}

public class User
{
    [JsonPropertyName("id")]
    public long Id { get; set; }
    [JsonPropertyName("firstName")]
    public string FirstName { get; set; }
    [JsonPropertyName("nickname")]
    public string Nickname { get; set; }
}

The attributes are the API's camelCase talking. The Nickname property stays string — reference types can be null already — and the notes record that the key is missing from some elements, so a null nickname in your data means “absent”, not “broken”.

Frequently asked questions

Which serializer are the generated classes written for?

System.Text.Json, the serializer built into modern .NET — that is why renamed properties carry [JsonPropertyName] rather than Newtonsoft's [JsonProperty]. If your project is on Newtonsoft.Json, the class shapes are identical and only the attribute differs: swap JsonPropertyName for JsonProperty and the using for Newtonsoft.Json. Every renamed property carries its attribute visibly in the output, which is exactly the set you would need to touch.

Why does every property have a [JsonPropertyName] attribute?

Because System.Text.Json matches names case-sensitively by default, and C# properties are PascalCase while JSON keys usually are not: without the attribute, "firstName" would not bind to FirstName unless you configure a camelCase naming policy. The attribute pins each property to its key explicitly, so the classes work with a default JsonSerializer and no options object. When a key is already PascalCase the attribute would spell the same name, so it is omitted — and so is the using directive when nothing in the file needs it.

When does a property become long? or double? instead of long or double?

When the sample shows the value can fail to be there: the key is missing from some elements of an array, or it is present but sometimes null. Value types cannot be null in C#, so the nullable form is the honest type. Reference types (string, classes, lists) can already be null, so they keep their plain type and the notes record the absence instead — read them before assuming every property is always populated.

Why long for integers and not int?

Because the sample can only prove 'whole number'. long is the width System.Text.Json itself reaches for when it meets a JSON integer without a schema, and it holds every integer a JSON document can realistically carry. If a field is truly an int counter or a short code, narrow it after generating — that is a fact about your domain the sample cannot know, and guessing narrower widths is how silent overflow bugs are born.

What does object mean where it appears in the output?

That the sample gave no usable evidence: the key was null everywhere it appeared, or it held genuinely different types across elements (a number in one, a string in another). object is C#'s honest 'anything' — the property will deserialize, but you get no compile-time help with it. Every object slot is listed in the notes with its reason; the fix is a more representative sample, not a cleverer guess.

Can I use records or init-only setters instead?

Yes — the generated shape is deliberately plain (public auto-properties with get; set;) because it is the form every C# developer recognizes and every serializer accepts. Converting to a record is a mechanical pass once the names and types are settled: change class to record and the setters can go. The generator does not choose for you, because record semantics (value equality, immutability) are a design decision, not a formatting one.

Related tools