Flutter中无法将JSON响应映射为类对象的问题求助
问题背景
我遇到了Flutter JSON解析的类型不匹配错误:Error: Expected a value of type 'Map<String, dynamic>', but got one of type 'String'。查过相关资料、跟着官方Cookbook示例尝试后依然没解决。
我想调用一个返回嵌套列表结构的API,原本每个内层字符串列表对应我定义的Provider类对象。我写了两个工厂方法做JSON转对象:
factory Provider.fromJsonMap(Map<String, dynamic> json):优先想用的方式,通过字段名访问数据,比索引更灵活factory Provider.fromJsonListDynamic(List<dynamic> json):这个方法能正常工作,但依赖数组索引,扩展性差
API更新后的新问题
之前有人指出我Python API返回的JSON没有结构,没法用json["name"]这种方式访问。于是我修改了Python API,返回序列化后的强类型Provider对象列表,Python端返回前的数据如下:
[ Provider(name='Google Translate API', id='google.translate_api.v3', img_url='ggl.png'), Provider(name='Microsoft Translator', id='microsoft.translator.v3', img_url='mcs.png'), Provider(name='Amazon Translate API', id='amazon.translate.api.v2', img_url='aws.png'), ]
但Flutter端用jsonDecode解析后,得到的却是字符串列表,每个元素是Provider对象的字符串表示:
final List<dynamic> listProviders = jsonDecode(response.body); // listProviders实际内容 [ "Provider(name='Google Translate API', id='google.translate_api.v3', img_url='ggl.png')", "Provider(name='Microsoft Translator', id='microsoft.translator.v3', img_url='mcs.png')", "Provider(name='Amazon Translate API', id='amazon.translate.api.v2', img_url='aws.png')", ]
尝试的代码与错误
我尝试把字符串列表转成Map列表再映射成Provider对象,代码如下:
final List<dynamic> listProviders = jsonDecode(response.body); final List<Map<String, dynamic>> listMap = listProviders.cast<Map<String, dynamic>>(); var providerMap = listMap.map<Provider>((json) => Provider.fromJsonMap(json)); List<Provider> providers = providerMap.toList();
运行后直接抛出之前的类型不匹配错误。
我的代码实现
Provider类
class Provider{ final String name; final String id; final String imageUrl; Provider( {required this.name, required this.id, required this.imageUrl }); // 调用失败的方法 factory Provider.fromJsonMap(Map<String, dynamic> json) { return Provider( name: json["name"] as String, id: json["id"] as String, imageUrl: json["imageUrl"] as String, ); } // 可以正常运行的方法 factory Provider.fromJsonListDynamic(List<dynamic> json) { return Provider( name: json[0] as String, id: json[1] as String, imageUrl: json[2] as String, ); } }
页面State中的异步调用代码
class _DetectorPageState extends State<DetectorPage>{ ... Future<List<Provider>> getMtProviders() async { Uri url = Uri.parse(SOME_API_URL); http.Response response = await http.get(url); if (response.statusCode == 200) { // 无法运行的代码 final List<dynamic> listProviders = jsonDecode(response.body); final List<Map<String, dynamic>> listMap = listProviders.cast<Map<String, dynamic>>(); var providerMap = listMap.map<Provider>((json) => Provider.fromJsonMap(json)); List<Provider> providers = providerMap.toList(); // 可以运行但依赖索引的代码 // List<dynamic> listProviders = jsonDecode(response.body); // List<Provider> providers = listProviders.map((provider) => Provider.fromJsonListDynamic(provider as List<dynamic>)).toList(); return providers; } else { print('Request failed with status: ${response.statusCode}.'); return []; } } }
问题根源与解决方案
1. 核心问题
你的Python API返回的不是标准JSON格式,而是把Provider对象直接转成了字符串(相当于Python中str(provider)的结果)。标准JSON应该是键值对结构的数组,比如:
[ {"name":"Google Translate API","id":"google.translate_api.v3","img_url":"ggl.png"}, {"name":"Microsoft Translator","id":"microsoft.translator.v3","img_url":"mcs.png"}, {"name":"Amazon Translate API","id":"amazon.translate.api.v2","img_url":"aws.png"} ]
2. 最优修复方案
第一步:修改Python API返回标准JSON
在Python端,不要直接返回Provider对象列表,而是将每个对象转成字典,再用json.dumps序列化。示例代码:
import json # 假设providers是你的Provider对象列表 providers_dict = [{"name": p.name, "id": p.id, "img_url": p.img_url} for p in providers] return json.dumps(providers_dict)
如果用Flask/Django这类框架,直接返回字典列表即可,框架会自动帮你序列化为标准JSON。
第二步:调整Flutter解析代码
API返回标准JSON后,Flutter端就可以正常使用fromJsonMap方法了,同时注意字段名对应(JSON里是img_url,你的类属性是imageUrl):
// 先修正Provider.fromJsonMap的字段映射 factory Provider.fromJsonMap(Map<String, dynamic> json) { return Provider( name: json["name"] as String, id: json["id"] as String, imageUrl: json["img_url"] as String, // 这里改成和JSON一致的字段名 ); } // 再修改调用代码 Future<List<Provider>> getMtProviders() async { Uri url = Uri.parse(SOME_API_URL); http.Response response = await http.get(url); if (response.statusCode == 200) { final List<dynamic> listProviders = jsonDecode(response.body); List<Provider> providers = listProviders .map((json) => Provider.fromJsonMap(json as Map<String, dynamic>)) .toList(); return providers; } else { print('Request failed with status: ${response.statusCode}.'); return []; } }
3. 临时替代方案(无法修改API时)
如果暂时没法修改Python API,可以通过正则表达式从字符串中提取字段值:
// 在Provider类中添加新的工厂方法 factory Provider.fromString(String str) { final nameMatch = RegExp(r"name='([^']+)'").firstMatch(str); final idMatch = RegExp(r"id='([^']+)'").firstMatch(str); final imgUrlMatch = RegExp(r"img_url='([^']+)'").firstMatch(str); return Provider( name: nameMatch?.group(1) ?? "", id: idMatch?.group(1) ?? "", imageUrl: imgUrlMatch?.group(1) ?? "", ); } // 调用时使用这个方法 List<Provider> providers = listProviders .map((str) => Provider.fromString(str as String)) .toList();
这种方法很脆弱,只要Python端的字符串格式变化就会失效,优先推荐修改API返回标准JSON。
内容的提问来源于stack exchange,提问作者Steve3p0

