Skip to content

Formatters & Code

JSON to C# Record

Modern C# records with JsonPropertyName attributes.

Runs in your browser
JSON · source
lines: 17chars: 261size: 261 B
C# record · result
lines: 19chars: 757size: 757 B
live

Understanding JSON → C# record

A class, but the values mean it.

What C# 9's record keyword changed, the positional-constructor syntax, and why it's the right shape for JSON DTOs in 90 % of cases.

Records in one line.

A record is a class with value-equality semantics, automatic Equals/GetHashCode/ToString, and an optional positional-constructor shorthand. The positional form public record User(int UserId, string Name); generates a constructor, init-only properties, and value-based equality in one line. It's the same trade-off Java records introduced: collapse boilerplate to a single declaration.

Record vs class for DTOs.

Use records for JSON DTOs. They're immutable by default, which prevents the common bug of one part of the system mutating a deserialized payload before another part sees it. They serialize correctly with System.Text.Json (.NET 7+) and Newtonsoft. They model "data" instead of "behaviour", which is what a DTO actually is. Use classic classes only when you need inheritance from a non-record base, or when the object holds significant logic alongside its data.

A worked example.

From { "user_id": 7, "name": "Q", "avatar": null } a positional record: public record User( [property: JsonPropertyName("user_id")] int UserId, string Name, string? Avatar ); The [property: ...] syntax is the funny bit — positional record parameters need it to attach attributes to the generated property, not the constructor parameter. Without that prefix, the attribute attaches to the wrong element and Jackson- I mean System.Text.Json - silently ignores it.

Positional record

public record User(int UserId, string Name);

Init-only props, value equality, automatic deconstruction.

3-field record = 3 lines including the closing semicolon

= Immutable DTO

With-expression mutation

user with { Name = ... }

Creates a new record with one field changed.

var u2 = u with { Name = "R" };

= Functional update style

The with-expression.

Records have a unique syntax for "make a copy with one thing different": var updated = user with { Name = "R" };. The result is a new record with every field copied from user except Name. This makes immutable records ergonomic for the cases where you'd otherwise want a mutator — you can update one field without writing a setter. The pattern works recursively for nested records.

Value equality, surprises included.

Two records with the same field values are equal — even if they came from different sources, even if they're not the same object reference. This is usually what you want for DTOs (compare two API responses for equality without writing per-field checks). It can surprise you when the record contains a mutable collection: two records holding "the same" list compare equal only if the lists are reference equal, not value equal. For lists/dicts inside records, use immutable collection types (ImmutableArray<T>) or accept the gotcha.

Sealed by default.

Records are not sealed by default — they support inheritance from other records, with the same value-equality rules extended. For DTOs you almost always want sealed record: prevents accidental subclassing that would break serializer assumptions and is closer to the data-only intent. Codegen tools that emit sealed record by default are picking the right convention.

When to fall back to classes.

Records aren't perfect for everything. Inheritance hierarchies with deep value chains, types that integrate with Entity Framework (EF Core 7+ supports records, older versions don't), DTOs with reference cycles, types that need to overrideEquals for domain-specific reasons — all of these are better as classes. For 90 % of JSON-shape needs, records are the right shape; for the 10 % where they're not, classes still exist.

Frequently asked questions

Quick answers.

How are JSON types mapped to C#?

Strings map to `string`, numbers to `double` or `int`, booleans to `bool`, and arrays to `List<T>`. Null values or missing data default to nullable types where appropriate.

Does this tool support nested objects?

Yes. The converter recursively parses nested JSON structures and generates a separate record definition for each unique object found.

Why are attributes added to each property?

The `JsonPropertyName` attribute ensures that the C# property follows .NET PascalCase naming conventions while correctly mapping to the original JSON key during serialisation.

Is my data sent to a server for processing?

No. The conversion is performed locally using JavaScript in your browser. No sensitive data or API payloads are transmitted over the network.

People also search for

Related tools

More in this room.

See all in Formatters & Code