Flutter本地存储报错:_InternalLinkedHashMap非Map<String,double>子类型
根因分析
异常直接触发点是Product.fromJson构造函数中productPrices字段的强制类型转换,本质是Dart内置jsonDecode的反序列化规则导致的类型不匹配:
- 调用
jsonDecode解析本地存储的JSON字符串时,所有嵌套的Map结构默认会被解析为_InternalLinkedHashMap<String, dynamic>类型。即使序列化前对象是Map<String, double>类型,反序列化后内层Map的value类型也会被重置为dynamic,不会自动保留double泛型。 - 原有代码直接执行
map['productPrices'] as Map<String, double>强转,相当于把Map<String, dynamic>直接转换为Map<String, double>,运行时类型不匹配直接抛出异常。 - 代码还存在一处隐藏逻辑缺陷:静态内存集合
_shoppingListSet不会在App启动时从本地文件加载初始化,App重启后第一次执行写入操作会直接覆盖本地原有数据。
可落地修复方案
1. 修复Product类反序列化的类型转换逻辑
禁止直接强转嵌套Map的泛型,需要逐元素做类型转换,同时兼容数字类型的自动适配(JSON序列化时整数类型的价格可能被存为int而非double)。
将Product.fromJson原有实现替换为如下代码:
Product.fromJson(Map<String, dynamic> map) : user = map['user'] as String, id = (map['id'] as num).toInt(), name = map['name'] as String, image = map['image'] as String, brandName = map['brandName'] as String, // 先转成dynamic类型的Map,再逐个将value转为double productPrices = Map<String, double>.from( (map['productPrices'] as Map<String, dynamic>).map( (key, value) => MapEntry(key, (value as num).toDouble()), ), );
2. 补全ShoppingListHandler的内存集合初始化逻辑
解决App重启后内存集合为空导致的数据覆盖问题:
- 新增初始化方法,在App启动完成后第一时间调用,将本地存储的数据加载到内存:
// 新增初始化方法,建议在runApp执行后立即调用 Future<void> init() async { final storedProducts = await readShoppingListProducts(); _shoppingListSet = storedProducts.toSet(); }
- 修改
readShoppingListProducts方法,读取本地数据后同步更新内存集合,保证内存数据和本地文件一致:
Future<List<ShoppingListProduct>> readShoppingListProducts() async { final File fl = await file; final _content = await fl.readAsString(); List<dynamic> _jsonData = []; if(_content.isNotEmpty){ _jsonData = jsonDecode(_content); } final List<ShoppingListProduct> _shoppingListProducts = _jsonData .map( (e) => ShoppingListProduct.fromJson(e as Map<String, dynamic>), ) .toList(); // 同步更新内存集合 _shoppingListSet = _shoppingListProducts.toSet(); return _shoppingListProducts; }
3. (可选)增加旧数据兼容兜底
如果应用已经发布过旧版本,本地可能存在格式异常的历史数据,可以给类型转换增加兜底逻辑,避免极端场景崩溃:
// 带异常兜底的productPrices转换逻辑 productPrices = () { final rawData = map['productPrices']; // 字段不存在或类型不对时返回空Map if (rawData is! Map) return <String, double>{}; return Map<String, double>.from( rawData.map( (key, value) { // 兼容字符串、int、double等多种可能的value格式 final parsedPrice = num.tryParse(value.toString())?.toDouble() ?? 0.0; return MapEntry(key.toString(), parsedPrice); }, ), ); }(),
内容的提问来源于stack exchange,提问作者Jack .T
相关产品推荐
相关产品推荐

