Keywords: Flutter | JSON Parsing | Type Error | Dart | Data Processing
Abstract: This article provides an in-depth analysis of the common JSON parsing error '_InternalLinkedHashMap<String, dynamic>' is not a subtype of 'List<dynamic>' in Flutter development. Through practical code examples, it explains the differences between JSON arrays and JSON objects, offering solutions for two common scenarios: proper property access when dealing with JSON arrays, and extracting nested list data from JSON objects. The article also covers best practices for type conversion and error handling to help developers avoid such runtime exceptions.
Problem Background and Error Analysis
In Flutter application development, handling JSON data is a common requirement. Many developers encounter type conversion errors when retrieving JSON responses from servers, specifically: Unhandled Exception: type '_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'List<dynamic>'. The core issue here is data type mismatch - the code expects a List but actually receives a Map.
JSON Data Structure Analysis
To understand this error, it's essential to distinguish between the two fundamental JSON structures: arrays and objects. In Dart, JSON arrays are parsed as List<dynamic> types, while JSON objects are parsed as Map<String, dynamic> types. When using the json.decode() method, Dart automatically selects the appropriate type based on the actual JSON structure.
Scenario 1: Proper Handling of JSON Arrays
When the server returns data in JSON array format, the structure typically looks like:
[
{
"name": "John",
"age": 25,
"email": "john@example.com"
},
{
"name": "Jane",
"age": 30,
"email": "jane@example.com"
}
]
In this case, json.decode(response.body) returns a List<dynamic> object. To access the name property of the first element, the correct approach is:
var data = json.decode(response.body);
print(data[0]["name"]);
It's important to note that you cannot directly use data[0].name because Dart's dynamic types don't automatically convert Map keys to object properties. Unless you perform explicit type casting:
var data = json.decode(response.body).cast<Map<String, dynamic>>();
// Or using custom classes
class User {
final String name;
final int age;
final String email;
User({required this.name, required this.age, required this.email});
factory User.fromJson(Map<String, dynamic> json) {
return User(
name: json['name'],
age: json['age'],
email: json['email']
);
}
}
var users = (json.decode(response.body) as List).map((i) => User.fromJson(i)).toList();
print(users[0].name);
Scenario 2: Handling Nested JSON Objects
Another common scenario is when the server returns a JSON object containing an array:
{
"status": "success",
"data": [
{
"name": "John",
"age": 25,
"email": "john@example.com"
},
{
"name": "Jane",
"age": 30,
"email": "jane@example.com"
}
],
"total": 2
}
In this situation, json.decode(response.body) returns a Map<String, dynamic>, not a List<dynamic>. The correct approach should be:
Map<String, dynamic> responseMap = json.decode(response.body);
List<dynamic> dataList = responseMap["data"];
print(dataList[0]["name"]);
Error Prevention and Best Practices
To avoid such runtime errors, consider adopting the following best practices:
- Validate Data Structure in Advance: Check the structure type of the response body before parsing JSON
- Use Type-Safe Parsing Methods: Create corresponding data model classes and use
fromJsonfactory methods - Add Error Handling: Use try-catch blocks to catch potential parsing exceptions
- Logging for Debugging: Print the complete response body during development to ensure the data structure meets expectations
Future<String> login() async {
try {
var response = await http.get(
Uri.parse("https://etrans.herokuapp.com/test/2"),
headers: {"Accept": "application/json"}
);
// Print complete response for debugging
print('Response body: ${response.body}');
var decodedData = json.decode(response.body);
if (decodedData is List) {
// Handle array case
setState(() {
data = decodedData;
});
print(data[0]["name"]);
} else if (decodedData is Map) {
// Handle object case
if (decodedData.containsKey('data') && decodedData['data'] is List) {
setState(() {
data = decodedData['data'];
});
print(data[0]["name"]);
} else {
throw FormatException('Unexpected JSON structure');
}
}
return "Success!";
} catch (e) {
print('Error: $e');
return "Failed: $e";
}
}
Conclusion
Understanding the correspondence between JSON data structures and Dart's type system is crucial for avoiding such errors. By adopting type-safe parsing methods, adding appropriate error handling, and following best practices, developers can significantly reduce runtime exceptions and improve application stability and maintainability. In practical development, it's recommended to always use explicit data models and type conversions rather than relying on implicit conversions of dynamic types.