Dart解析JSON报FormatException: Unexpected character错误
问题根因
报错和Dart的JSON格式化能力无关,核心原因是本地存储的JSON文件格式非法,同时现有代码存在逻辑缺陷会持续触发这类问题:
- 从贴出的文件内容可以看到,在第722个字符位置出现了
}]{结构:前半段是包含2个商品的JSON数组[{...},{...}],数组闭合符]后直接拼接了第3个商品的JSON对象开头{,最后又多了一个数组闭合符]。标准JSON仅允许单个根节点,这种多段JSON拼接、括号不匹配的内容无法被jsonDecode解析,直接抛出格式异常。 - 这类损坏数据一般是历史写入逻辑误用了
FileMode.append追加模式导致:每次写入新商品时没有覆盖旧的完整JSON,而是把新的JSON字符串直接拼接到旧文件末尾,最终形成非法格式。 - 现有代码存在逻辑漏洞:内存中维护的静态集合
_shoppingListSet在APP冷启动后为空,读取文件时没有把解析出的商品数据同步回这个集合,后续写入、删除操作都是基于空集合执行,要么覆盖丢失旧数据,要么配合错误的追加模式持续生成损坏的JSON。
修复方案
- 第一步:清理存量损坏数据
直接删除应用本地的旧存储文件,或者手动修正文件内容为合法JSON格式:把所有商品对象放到同一个根数组[]内,对象之间用逗号分隔,删除多余的][拼接字符,示例正确格式:[ {"ID":0, "product":{...}, "ticked":false}, {"ID":1, "product":{...}, "ticked":false}, {"ID":2, "product":{...}, "ticked":false} ] - 第二步:修复代码逻辑漏洞
修改readShoppingListProducts方法,读取文件后把解析出的商品列表同步到内存集合,保证内存数据和文件数据一致,修正后的代码如下:
注意:APP启动后、第一次执行写入/删除/更新操作前,必须先调用一次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; }readShoppingListProducts完成内存数据初始化,避免内存集合为空导致的数据覆盖问题。也可以在文件初始化完成后自动触发读取逻辑,减少手动调用遗漏。
写入操作保持默认的覆盖模式即可(writeAsString默认使用FileMode.write,会全量覆盖旧文件内容),不要使用追加模式写JSON数组,避免再次出现拼接问题。 - 第三步:(可选)增加异常容错
读取文件时增加格式异常捕获,遇到损坏文件时自动备份并重置为空文件,避免APP直接崩溃:Future<List<ShoppingListProduct>> readShoppingListProducts() async { final File fl = await file; final _content = await fl.readAsString(); List<dynamic> _jsonData = []; if(_content.isNotEmpty){ try { _jsonData = jsonDecode(_content); } on FormatException { // 备份损坏的旧文件,方便后续排查 final backupFile = File('${fl.path}.bak.${DateTime.now().millisecondsSinceEpoch}'); await fl.copy(backupFile.path); // 重置为空的合法JSON数组 await fl.writeAsString(jsonEncode([])); } } final List<ShoppingListProduct> _shoppingListProducts = _jsonData .map( (e) => ShoppingListProduct.fromJson(e as Map<String, dynamic>), ) .toList(); _shoppingListSet = _shoppingListProducts.toSet(); return _shoppingListProducts; }
验证方式
- 写入多个商品后打开本地存储文件,确认整个文件是单个完整JSON数组,所有商品对象都在同一个
[]内,没有多余的拼接字符 - 冷重启APP后读取商品列表,确认无格式异常,之前写入的商品全部存在
- 多次执行新增、删除、更新操作后重启APP,确认数据不丢失、文件格式始终合法
内容的提问来源于stack exchange,提问作者Jack .T
相关产品推荐
相关产品推荐

