You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 09:27:12