Flutter JSON解码报错:Null无法转为String,求解决方案
解决Flutter动态JSON键的解码问题
作为Flutter新手,解码给定JSON数据时遇到困难:json.decode返回List<dynamic>,需要将其映射为对应模型,但JSON中的动态键(如"Product line #")需同时作为ProductLine的name和PartNumber的productLine值,这些键是不确定的字符串。
给定的JSON数据
[ { "Product line 1": [ { "partNumber": "160-9013-900", "orderable": true, "description": "Part Number Description" }, { "partNumber": "160-9104-900", "orderable": true, "description": "Part Number Description" }, { "partNumber": "160-9105-900", "orderable": false, "description": "Part Number Description" } ] }, { "Product line 2": [ { "partNumber": "160-9113-900", "orderable": true, "description": "Part Number Description" }, { "partNumber": "160-9114-900", "orderable": true, "description": "Part Number Description" }, { "partNumber": "160-9115-900", "orderable": false, "description": "Part Number Description" } ] }, { "Product line 3": [ { "partNumber": "160-9205-900", "orderable": true, "description": "Part Number Description" }, { "partNumber": "160-9211-900", "orderable": true, "description": "Part Number Description" }, { "partNumber": "160-9212-900", "orderable": false, "description": "Part Number Description" } ] } ]
已定义的模型代码
const uuid = Uuid(); @JsonSerializable() class PartNumber { PartNumber({ String? id, required this.partNumber, required this.orderable, required this.description, required this.productLine, }) : id = id ?? uuid.v4(); final String id; final String partNumber; final bool orderable; final String description; final String productLine; factory PartNumber.fromJson(Map<String, dynamic> json) => _$PartNumberFromJson(json); Map<String, dynamic> toJson() => _$PartNumberToJson(this); PartNumber _$PartNumberFromJson(Map<String, dynamic> json) => PartNumber( id: json['id'] as String?, partNumber: json['partNumber'] as String, orderable: json['orderable'] as bool, description: json['description'] as String, productLine: json['productLine'] as String, ); Map<String, dynamic> _$PartNumberToJson(PartNumber instance) => <String, dynamic>{ 'id': instance.id, 'partNumber': instance.partNumber, 'orderable': instance.orderable, 'description': instance.description, 'productLine': instance.productLine, }; } @JsonSerializable() class ProductLine { ProductLine({ String? id, required this.name, required this.partNumbers, }) : id = id ?? uuid.v4(); final String id; final String name; final List<PartNumber> partNumbers; factory ProductLine.fromJson(Map<String, dynamic> json) => _$ProductLineFromJson(json); Map<String, dynamic> toJson() => _$ProductLineToJson(this); ProductLine _$ProductLineFromJson(Map<String, dynamic> json) => ProductLine( id: json['id'] as String?, name: json['name'] as String, partNumbers: (json['partNumbers'] as List<dynamic>) .map((e) => PartNumber.fromJson(e as Map<String, dynamic>)) .toList(), ); Map<String, dynamic> _$ProductLineToJson(ProductLine instance) => <String, dynamic>{ 'id': instance.id, 'name': instance.name, 'partNumbers': instance.partNumbers, }; }
遇到的错误
E/flutter (18120): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: type 'Null' is not a subtype of type 'String' in type cast E/flutter (18120): #0 _$ProductLineFromJson (package:test_app/models/product_line.g.dart:11:26) E/flutter (18120): #1 new ProductLine.fromJson (package:test_app/models/product_line.dart:24:7) E/flutter (18120): #2 PartNumberManager.formatPartNumbers (package:test_app/managers/part_number_manager.dart:62:39) E/flutter (18120): E/flutter (18120): #3 PartNumberManager.loadPartNumbers (package:test_app/managers/part_number_manager.dart:36:5)
当前的JSON处理代码
class PartNumberManager { List<PartNumber>? partNumbers; Future<bool> loadPartNumbers() async { await formatPartNumbers(); final partNumbersLoaded = await batchInsertPartNumbers(); return partNumbersLoaded; } // Read part_numbers.json file before adding data to the sql database Future<List<dynamic>> readJson() async { try { final String response = await rootBundle.loadString('assets/part_numbers.json'); final data = (json.decode(response) as List<dynamic>); return data; } catch (error) { throw Exception( 'PartNumberManager, Unexpected error reading JSON: $error'); } } Future<void> formatPartNumbers() async { List<ProductLine> tmpProductLines = []; List<dynamic> data = await readJson(); for (var i = 0; i < data.length; i++) { final productLine = ProductLine.fromJson(data[i]); tmpProductLines.add(productLine); } for (var product in tmpProductLines) { for (var pnum in product.partNumbers) { final partNumber = PartNumber( partNumber: pnum.partNumber, orderable: pnum.orderable, description: pnum.description, productLine: product.name, ); partNumbers!.add(partNumber); } } } }
问题根源
- 结构不匹配:直接调用
ProductLine.fromJson(data[i])错误,因为data[i]的结构是{动态键: [零件数组]},而非模型期望的{"name": "...", "partNumbers": [...]},导致解析时找不到name和partNumbers字段,触发类型转换错误。 - 空指针风险:
partNumbers变量未初始化,直接用partNumbers!.add会触发空指针异常。
修正后的处理代码
class PartNumberManager { // 初始化空列表,避免空指针 List<PartNumber> partNumbers = []; Future<bool> loadPartNumbers() async { await formatPartNumbers(); final partNumbersLoaded = await batchInsertPartNumbers(); return partNumbersLoaded; } Future<List<dynamic>> readJson() async { try { final String response = await rootBundle.loadString('assets/part_numbers.json'); final data = (json.decode(response) as List<dynamic>); return data; } catch (error) { throw Exception('PartNumberManager, Unexpected error reading JSON: $error'); } } Future<void> formatPartNumbers() async { List<ProductLine> tmpProductLines = []; List<dynamic> data = await readJson(); for (var item in data) { // 提取动态产品线名称和对应的零件列表 MapEntry<String, dynamic> entry = item.entries.first; String productLineName = entry.key; List<dynamic> partsJson = entry.value; // 构建零件列表,同时绑定产品线名称 List<PartNumber> partNumbersList = partsJson.map((partJson) { return PartNumber( partNumber: partJson['partNumber'] as String, orderable: partJson['orderable'] as bool, description: partJson['description'] as String, productLine: productLineName, ); }).toList(); // 构建ProductLine对象 ProductLine productLine = ProductLine( name: productLineName, partNumbers: partNumbersList, ); tmpProductLines.add(productLine); } // 合并所有零件到全局列表 for (var product in tmpProductLines) { partNumbers.addAll(product.partNumbers); } } // 补充数据库插入逻辑(原代码未实现) Future<bool> batchInsertPartNumbers() async { // 这里编写你的数据库插入代码 return true; } }
额外说明
- 由于JSON结构与模型结构不匹配,无法直接用
JsonSerializable的fromJson解析顶层动态键,手动遍历键值对是最直接的解决方案。 - 若后续需要用
JsonSerializable处理动态键,可以自定义JsonConverter,但当前场景手动解析更简洁。
内容的提问来源于stack exchange,提问作者Joe G
相关产品推荐
相关产品推荐

