Quick answer
Dart's jsonDecode returns untyped maps, so Flutter apps convert JSON into model classes with a fromJson factory and a toJson method. Paste a sample into Playxoft's free JSON to Dart generator to get them: final, null-safe fields, a safe cast for every value, DateTime parsing for ISO dates, nested models and lists, and an optional copyWith. Switch on json_serializable to get annotated classes for build_runner instead.
- Null-safe classes
- fromJson and toJson
- copyWith
- json_serializable mode
- DateTime parsing
- Nested models and lists
- Download models.dart
How to use the JSON to Dart
- 1Paste a JSON response into JSON sample and set the Root type name.
- 2Keep the hand-written output, or switch on json_serializable and set the File name used by the part directive.
- 3Toggle copyWith and DateTime to match your app.
- 4Copy the code or download models.dart into your
libfolder. - 5Decode with
Root.fromJson(jsonDecode(source) as Map<String, dynamic>).
How does JSON parsing work in Dart and Flutter?
jsonDecode from dart:convert returns plain, untyped data: a Map<String, dynamic> for objects and a List<dynamic> for arrays. Reading data['user']['name'] compiles, but every typo and wrong type becomes a runtime crash. Model classes fix that by converting the map once, at the edge of your app, into typed objects:
import 'dart:convert';
import 'package:http/http.dart' as http;
final response = await http.get(Uri.parse('https://api.example.com/me'));
final user = User.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
print(user.displayName); // typed, autocompleted and null-safeThe generator writes a fromJson factory and a toJson method for every class, converting nested objects and lists of objects recursively, so the whole response becomes typed in one call. The output is already formatted the way dart format would format it.
Hand-written fromJson or json_serializable?
| Hand-written (default) | json_serializable | |
|---|---|---|
| Dependencies | None | json_annotation, plus build_runner and json_serializable for development |
| Build step | None | dart run build_runner build |
| Extra file | None | A generated .g.dart part |
| Best for | Small apps and prototypes | Large apps with many models |
Switch on json_serializable and the generator writes annotated classes instead: @JsonSerializable(), @JsonKey(name: ...) for renamed keys, and a part directive that must match your file name. Then add the packages and run the builder:
dart pub add json_annotation dev:build_runner dev:json_serializable
dart run build_runner build --delete-conflicting-outputsIf you also want value equality and union types, freezed builds on the same annotations.
How are JSON types mapped to Dart?
| JSON in your sample | Dart type | Read with |
|---|---|---|
| String | String | json['k'] as String |
| ISO 8601 date-time | DateTime | DateTime.parse(json['k'] as String) |
| Whole number | int | json['k'] as int |
| Decimal number | double | (json['k'] as num).toDouble() |
| Boolean | bool | json['k'] as bool |
| Array | List<T> | .map(...).toList() |
| Object | A model class | Model.fromJson(...) |
Mixed values, or only null | dynamic | json['k'] |
The num cast for decimals is deliberate. jsonDecode turns 10 into an int and 10.5 into a double, so a price that happens to be a whole number would crash a plain as double cast with "type 'int' is not a subtype of type 'double'".
How does null safety affect JSON models?
Every field is final and non-nullable unless your samples prove otherwise. A key that was null or missing in any sample becomes nullable (String?), drops required from the constructor and is read with a null-aware cast. In toJson, keys that were missing from some samples are written only when they have a value:
Map<String, dynamic> toJson() {
return {
'id': id,
if (couponCode != null) 'couponCode': couponCode,
};
}On the web, Dart's int is a JavaScript number, so integers above 2^53 lose precision there just as they do in JavaScript. If your Flutter app also runs in the browser, ask the API to send large IDs as strings.
How do I parse large JSON without dropping frames?
jsonDecode runs on the isolate that calls it, and a multi-megabyte response can take long enough to drop frames. Move the work to a background isolate:
// Dart 2.19 and later
final user = await Isolate.run(
() => User.fromJson(jsonDecode(body) as Map<String, dynamic>),
);
// Flutter's compute() also works on the web (where it runs on the main thread)
final user = await compute(parseUser, body);For small responses, decoding inline is fine: starting an isolate has a cost of its own. To check a large response before you model it, the JSON Formatter shows its structure as a collapsible tree.
Frequently asked questions
Last updated by the Playxoft engineering team.