You write a model, the first API response parses fine, and a week later a crash report says:
type 'int' is not a subtype of type 'double' in type cast
Nothing in your code changed. The data did. Here are the three type errors I see most often with jsonDecode, why they happen, and how to write (or generate) models that survive real responses.
1. int vs double
Dart's jsonDecode reads the number type from the text. 1.0 is a double, 1 is an int:
import 'dart:convert';
void main() {
final a = jsonDecode('{"price": 1.0}') as Map<String, dynamic>;
print(a['price'].runtimeType); // double
final b = jsonDecode('{"price": 1}') as Map<String, dynamic>;
final price = b['price'] as double;
// type 'int' is not a subtype of type 'double' in type cast
}
Many backends drop the .0 for whole numbers, so a price of 3.0 arrives as 3. The safe cast for any field that is "a number with decimals" is:
final price = (json['price'] as num).toDouble();
2. List<dynamic> vs List<String>
jsonDecode has no idea what your list should contain, so every JSON array becomes List<dynamic>:
final json = jsonDecode('{"tags": ["a", "b"]}') as Map<String, dynamic>;
final tags = json['tags'] as List<String>;
// type 'List<dynamic>' is not a subtype of type 'List<String>' in type cast
Cast each item instead of the list:
final tags = (json['tags'] as List<dynamic>).map((e) => e as String).toList();
Lists of objects work the same way, with fromJson on each item:
final items = (json['items'] as List<dynamic>)
.map((e) => Item.fromJson(e as Map<String, dynamic>))
.toList();
3. The key that is only sometimes there
You build the model from one sample response. In that sample every item has a note. In production some items don't, and json['note'] as String throws because the value is null. Any key that can be missing or null needs a nullable type, String?, and a non-required constructor parameter.
The fix is to look at more than one sample. If a key is missing from any item, or null in any item, it's nullable.
A model that handles all three
Given this sample, where the second item has a note and the first doesn't:
{
"id": 7,
"price": 1.0,
"tags": ["new"],
"items": [
{ "sku": "A1", "qty": 2 },
{ "sku": "B2", "qty": 1, "note": "gift" }
]
}
a model that survives real responses looks like this:
class Order {
final int id;
final double price;
final List<String> tags;
final List<Item> items;
const Order({
required this.id,
required this.price,
required this.tags,
required this.items,
});
factory Order.fromJson(Map<String, dynamic> json) => Order(
id: json['id'] as int,
price: (json['price'] as num).toDouble(),
tags: (json['tags'] as List<dynamic>).map((e) => e as String).toList(),
items: (json['items'] as List<dynamic>).map((e) => Item.fromJson(e as Map<String, dynamic>)).toList(),
);
Map<String, dynamic> toJson() => {
'id': id,
'price': price,
'tags': tags,
'items': items.map((e) => e.toJson()).toList(),
};
}
class Item {
final String sku;
final int qty;
final String? note;
const Item({
required this.sku,
required this.qty,
this.note,
});
factory Item.fromJson(Map<String, dynamic> json) => Item(
sku: json['sku'] as String,
qty: json['qty'] as int,
note: json['note'] as String?,
);
Map<String, dynamic> toJson() => {
'sku': sku,
'qty': qty,
'note': note,
};
}
Decoding {"id": 8, "price": 3, "tags": [], "items": [{"sku": "C3", "qty": 1}]} with it gives price = 3.0 and note = null, with no exceptions.
Generating it
I wrote that model by hand far too many times, so I built a free JSON to Dart generator (disclosure: it's my project). The model above is its actual output for the sample. It:
- runs entirely in the browser, so API responses are never uploaded,
- reads number types from the raw text, so
1.0stays adouble(JavaScript'sJSON.parsewould turn it into1), - merges the keys of every item in an array and makes keys that are missing or
nullin some items nullable, - outputs plain Dart (no packages), json_serializable or freezed,
- can repair a map printed by
print()/debugPrint(like{id: 1, name: Ada}) into JSON first.
It does not guess DateTime or enums; dates stay String, so convert those by hand.
If you're debugging one of these errors right now, the guide Flutter JSON Errors: "is not a subtype" Explained covers more of them, including '_Map<String, dynamic>' is not a subtype of type ....
Top comments (0)