Dart中同键不同类型的JSON解析及最优实现方案问询
Dart中多类型JSON字段的规范解析方案
针对同一个key对应不同类型值的JSON解析场景,我们可以通过强类型分层建模+类型判断逻辑替代dynamic,保证代码的可维护性和类型安全。以下是具体实现思路:
一、基础抽象类定义
先定义一个抽象基类,统一所有可能的选项类型:
abstract class BaseOption {}
二、具体模型类实现
针对每种JSON结构创建对应的具体类,继承自BaseOption,每个类只处理自己对应的解析逻辑:
// Options1对应字符串的情况 class Option1String extends BaseOption { final String value; Option1String(this.value); factory Option1String.fromJson(String json) => Option1String(json); } // Options1对应address嵌套对象的情况 class Option1Address extends BaseOption { final String key1; Option1Address(this.key1); factory Option1Address.fromJson(Map<String, dynamic> json) { return Option1Address(json['address']['key1'] as String); } } // Options2对应address1嵌套对象的情况 class Option2Address1 extends BaseOption { final String key1; final String token; Option2Address1(this.key1, this.token); factory Option2Address1.fromJson(Map<String, dynamic> json) { final address1 = json['address1'] as Map<String, dynamic>; return Option2Address1( address1['key1'] as String, address1['token'] as String, ); } } // Options2对应address2嵌套对象的情况 class Option2Address2 extends BaseOption { final String key1; Option2Address2(this.key1); factory Option2Address2.fromJson(Map<String, dynamic> json) { final selectItem = json['address2']['SelectItem1'] as Map<String, dynamic>; return Option2Address2(selectItem['key1'] as String); } }
三、核心解析逻辑实现
创建Choosen类处理最外层的choosen字段,通过判断key类型和值的类型,选择对应的具体模型类:
class Choosen { final String optionType; // 记录是Options1还是Options2 final BaseOption option; Choosen({required this.optionType, required this.option}); factory Choosen.fromJson(Map<String, dynamic> json) { // 取choosen下的唯一键值对 final entry = json.entries.first; final optionType = entry.key; final value = entry.value; late BaseOption option; switch (optionType) { case 'Options1': if (value is String) { option = Option1String.fromJson(value); } else if (value is Map<String, dynamic>) { option = Option1Address.fromJson(value); } else { throw FormatException('不支持的Options1值类型'); } break; case 'Options2': if (value is Map<String, dynamic>) { if (value.containsKey('address1')) { option = Option2Address1.fromJson(value); } else if (value.containsKey('address2')) { option = Option2Address2.fromJson(value); } else { throw FormatException('不支持的Options2子类型'); } } else { throw FormatException('不支持的Options2值类型'); } break; default: throw FormatException('未知的选项类型: $optionType'); } return Choosen(optionType: optionType, option: option); } }
四、进阶优化:使用Freezed实现密封类
如果想进一步简化代码并获得更便捷的模式匹配能力,可以使用freezed包创建密封类(sealed class):
1. 添加依赖
在pubspec.yaml中添加:
dependencies: freezed_annotation: ^2.4.1 json_annotation: ^4.8.1 dev_dependencies: build_runner: ^2.4.4 freezed: ^2.4.5 json_serializable: ^6.7.0
2. 定义密封类模型
import 'package:freezed_annotation/freezed_annotation.dart'; part 'models.freezed.dart'; part 'models.g.dart'; @freezed class BaseOption with _$BaseOption { // 对应Options1字符串类型 const factory BaseOption.option1String(String value) = Option1String; // 对应Options1 address类型 const factory BaseOption.option1Address(String key1) = Option1Address; // 对应Options2 address1类型 const factory BaseOption.option2Address1(String key1, String token) = Option2Address1; // 对应Options2 address2类型 const factory BaseOption.option2Address2(String key1) = Option2Address2; factory BaseOption.fromJson(Map<String, dynamic> json) => _$BaseOptionFromJson(json); }
3. 简化Choosen解析逻辑
class Choosen { final String optionType; final BaseOption option; Choosen({required this.optionType, required this.option}); factory Choosen.fromJson(Map<String, dynamic> json) { final entry = json.entries.first; final optionType = entry.key; final value = entry.value; late BaseOption option; switch (optionType) { case 'Options1': option = value is String ? BaseOption.option1String(value) : BaseOption.option1Address((value as Map)['address']['key1'] as String); break; case 'Options2': final valueMap = value as Map<String, dynamic>; option = valueMap.containsKey('address1') ? BaseOption.option2Address1( valueMap['address1']['key1'] as String, valueMap['address1']['token'] as String) : BaseOption.option2Address2( valueMap['address2']['SelectItem1']['key1'] as String); break; default: throw FormatException('未知的选项类型: $optionType'); } return Choosen(optionType: optionType, option: option); } }
4. 使用时的模式匹配
密封类可以让后续的类型判断更简洁:
void processOption(BaseOption option) { switch (option) { case Option1String(:final value): print('Options1字符串值: $value'); case Option1Address(:final key1): print('Options1地址key: $key1'); case Option2Address1(:final key1, :final token): print('Options2地址1: key=$key1, token=$token'); case Option2Address2(:final key1): print('Options2地址2key: $key1'); } }
方案优势
- 类型安全:全程避免dynamic,编译期即可发现类型错误
- 职责单一:每个模型类只处理对应JSON结构,代码清晰易维护
- 扩展性强:新增类型时只需添加对应的子类和解析分支,不影响现有逻辑
内容的提问来源于stack exchange,提问作者Vladimir
相关产品推荐
相关产品推荐

