JSON to Dart

Generate Dart classes from a JSON sample — camelCase final fields with the original JSON key kept in a comment wherever it had to change, nullable types where keys come and go or turn null, const constructors with required named parameters only where the sample proves presence, and dynamic reported wherever it had to appear. 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 Dart classes from a sample?

Dart's null safety is a contract, and JSON APIs break it for a living: a key that is present in nine responses and absent in the tenth will crash a model class that declared it non-nullable. Getting the classes right means reading the sample for exactly that evidence — which keys come and go, which ones turn null, which number slots ever show a fraction — and transcribing it into camelCase fields and constructor parameters without losing the original key names. That transcription is mechanical, and the sample already contains every fact it needs. This converter does it: objects become classes with final fields, absence becomes a ? and a seat outside the constructor's required list, and every slot that had to become dynamic is reported with its reason.

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 fields 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 Dart. The root class comes first, the classes it references after it.
  4. Read the notes if any appear. They list every dynamic slot, every keyword escape and every name collision — nothing was decided silently.
  5. Copy or download the result, then add your serialization: hand-written fromJson using the key comments, or annotations for your code generator of choice.

The rules it follows

Numbers. Whole numbers become int; 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. Names. Classes are PascalCase after the key that holds them; fields are camelCase, with the original JSON key kept in a trailing comment whenever camelCasing changes the spelling (a Dart keyword gains an underscore instead). Absence and null. Missing from some elements or sometimes null → a nullable type, and the constructor parameter leaves the required set. Null everywhere or genuinely mixed types → dynamic, 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):

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

the converter emits:

class Root {
  final String teamName;
  final List<User> users;

  const Root({required this.teamName, required this.users});
}

class User {
  final int id;
  final String firstName;
  final String? nickname;

  const User({required this.id, required this.firstName, this.nickname});
}

The question mark and the missing required are the same fact seen twice: the second user proves nickname is sometimes absent, so the type admits null and the constructor does not demand it.

Frequently asked questions

Where are fromJson and toJson?

Deliberately not generated — and the page says so rather than pretending otherwise. Hand-writing them for a class with two fields is easy; for the nested, nullable, renamed shapes real APIs produce, the ecosystem's answer is code generation (json_serializable / freezed) driven off annotations, and those tools disagree with each other about style. What this converter gives you is the part every approach needs first and nobody enjoys typing: the class shapes with correct names, correct nullability, and the original JSON key preserved in a comment wherever the field name had to change — which is exactly the information a @JsonKey annotation would need.

When does a field become nullable (String? instead of String)?

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. Dart's null safety makes that distinction load-bearing — a non-nullable field fed a missing key throws at runtime — so the generator treats absence as evidence and writes the question mark. The field also leaves the constructor's required list: a parameter the data itself says is sometimes absent should not be one every caller is forced to pass.

Why a const constructor with named parameters?

Because it is the shape Flutter code expects: immutable final fields, a const constructor when every field allows it, and named parameters so construction sites read like the JSON they mirror. Required parameters are exactly the fields the sample proves are always present and non-null; everything else is an optional named parameter. If you later add fromJson by hand or via codegen, this constructor shape is what those patterns consume.

What does dynamic mean where it appears?

That the sample could not settle the slot: null everywhere it appeared, or genuinely different types across elements (a number here, a string there). dynamic is Dart's honest anything — the field will hold the value, and the compiler will not help you with it. Every dynamic slot is listed in the notes with its reason; a more representative sample is the fix, not a guess typed hopefully as String.

Why int for some numbers and double for others?

Dart, like JSON tooling generally, treats 1 and 1.5 as different commitments. A slot that only ever shows whole numbers becomes int; a single fractional sighting widens it to double — and a mixed list of ints and doubles also lands on double, since List<int> cannot hold the 2.5 your API demonstrably sends. Beware the mirror-image trap the notes cannot see: JSON 1.0 is the number 1 to every parser, so a field your API always sends as 2.0 samples as an int here.

One of my fields is called class_ — what happened?

Your JSON has a key that camelCases to a Dart keyword (class, in, switch, new…). The field gains a trailing underscore, keeps the original key in a trailing comment, and the change is listed in the notes. When you wire up serialization, that comment is the name your @JsonKey or hand-written lookup must use — the underscore exists only on the Dart side.

Related tools