Flutter/Dart Data Class
Stefan Bogdanescu
Founder & Senior Architect · 2026-06-29
# Mastering Data Classes in Flutter: Solving Complex API Mapping Issues with Dart
As developers building complex applications—especially those bridging frontend frameworks like Flutter with robust backends like Laravel—the process of mapping raw JSON data into strongly-typed Dart objects is often where the real friction occurs. You’ve hit a very common roadblock: dealing with dynamic types and list casting errors during deserialization.
This post dives deep into why you encountered the `type 'List' is not a subtype of type 'List'` error, analyzes your model structure, and provides a robust, production-ready solution for handling complex API responses in Flutter using Dart Data Classes.
## The Pitfall of Dynamic Casting in JSON Deserialization
When you receive data from an API (typically as a `Map` in Dart), the values are inherently untyped. When you try to force these dynamic types into specific concrete types, such as `List` or `List`, Dart throws an error if the underlying structure doesn't match expectations exactly.
Your specific error stems from this line within your `Orders.fromMap` factory:
```dart
List.from(
(map['orders'] as List).map(
(x) => OrderItem.fromMap(x as Map),
),
),
```
The error occurs because the API response for `pid` seems inconsistent—sometimes it’s a JSON array of numbers, and sometimes it's a string representation of an array (e.g., `"\[2,3\]"`). When Dart attempts to treat this heterogeneous input as a direct `List`, the type mismatch causes the crash.
## The Solution: Defensive Mapping with Safe Type Checks
The key to solving this is moving away from direct, unchecked casting (`as List`) and implementing defensive parsing logic within your data class constructors or factory methods. We need to inspect the incoming data before attempting to cast it.
Here is how we refine your `OrderItem` and `Orders` classes to safely handle potentially messy JSON data sent from a backend, much like ensuring type safety on the Laravel side as well.
### Refactoring OrderItem for Robust Parsing
We will focus on making the `fromMap` methods resilient by explicitly handling string-to-list conversions if necessary. For numeric lists, we must ensure that every element is indeed a number before inclusion.
```dart
class OrderItem {
final num id;
final List pid; // Use 'num' for flexibility in JSON parsing
OrderItem({required this.id, required this.pid});
// ... copyWith implementation remains the same ...
Map toMap() {
return {
'id': id,
'pid': pid.toList(), // Ensure we serialize the list correctly
};
}
factory OrderItem.fromMap(Map map) {
// 1. Safely extract 'id' and ensure it's a number
final id = map['id'] as num;
// 2. Handle the potentially complex 'pid' field
final pidData = map['pid'];
List pids;
if (pidData is List) {
// Case 1: Input is already a list of numbers (ideal case)
pids = List.from(pidData);
} else if (pidData is String) {
// Case 2: Input is a string that needs parsing (e.g., "[2,3]")
// We use jsonDecode to safely parse the string representation of the list.
try {
pids = List.from(jsonDecode(pidData));
} catch (e) {
// Handle error if string format is invalid
throwFormatException('Invalid list format for pid: $pidData');
}
} else {
// Case 3: Input is null or unexpected type
pids = [];
}
return OrderItem(
id: id,
pid: pids,
);
}
String toJson() => json.encode(toMap());
// ... other methods (fromJson, hashCode)
}
```
### Refactoring Orders for Cohesive Mapping
By implementing the robust logic in `OrderItem`, the parent class becomes much simpler and safer. The main fix here is to rely on this safe factory method:
```dart
class Orders {
final List orders;
Orders(this.orders);
Map toMap() {
return {
'orders': orders.map((x) => x.toMap()).toList(),
};
}
factory Orders.fromMap(Map map) {
// Safely cast the 'orders' list, trusting that OrderItem.fromMap handles the internal complexity.
final List orderList = map['orders'] as List? ?? [];
final List items = orderList
.map((x) => OrderItem.fromMap(x as Map))
.toList();
return Orders(items);
}
String toJson() => json.encode(toMap());
// ... other methods
}
```
## Conclusion: The Power of Defensive Data Handling
Dealing with data from external sources is an exercise in defensive programming. When working with APIs, especially those built on robust frameworks like Laravel where data structures can occasionally be slightly inconsistent across different endpoints or versions, relying on strict type casting is a recipe for runtime errors.
By implementing factory methods like `fromMap` that explicitly check the *type* of incoming data (list vs. string) and use safe parsing techniques (`jsonDecode`), you transform brittle code into resilient, production-ready models. This approach ensures that your Flutter application remains stable, regardless of minor inconsistencies in the API response structure, giving you a solid foundation for building scalable e-commerce features. Remember, clean data contracts between your Laravel backend and your Flutter frontend are just as important as the code doing the mapping itself—always build robust parsing logic!