Flutter 一招教你解决json_annotation类型解析失败问题

1,570 阅读5分钟

json_annotation 是一个 Dart 库,通常用于生成 JSON 序列化和反序列化的代码。它提供了一系列注解,帮助开发者更方便地将 Dart 对象与 JSON 数据进行相互转换。

更多的类型转换代码,请看这里

安装依赖

pubspec.yaml 文件中添加 json_annotationjson_serializable 依赖:

dependencies:
  json_annotation: ^4.8.1

dev_dependencies:
  build_runner: ^2.4.6
  json_serializable: ^6.6.1

基础使用

假设我们要创建一个简单的 Person 类,包含 nameage 两个属性。

import 'package:json_annotation/json_annotation.dart';

// 生成的序列化代码文件
part 'person.g.dart';

// 使用 @JsonSerializable 注解标记该类
@JsonSerializable()
class Person {
  // 类的属性
  final String name;
  final int age;

  // 构造函数
  Person(this.name, this.age);

  // 工厂方法,用于从 JSON 数据反序列化对象
  factory Person.fromJson(Map<String, dynamic> json) => _$PersonFromJson(json);

  // 方法,用于将对象序列化为 JSON 数据
  Map<String, dynamic> toJson() => _$PersonToJson(this);
}

在上述代码中:

  • part 'person.g.dart'; 表示引入生成的序列化代码文件。
  • @JsonSerializable()json_annotation 提供的注解,用于标记需要进行 JSON 序列化和反序列化的类。
  • factory Person.fromJson(Map<String, dynamic> json) => _$PersonFromJson(json); 是一个工厂构造函数,用于从 JSON 数据创建 Person 对象。
  • Map<String, dynamic> toJson() => _$PersonToJson(this); 方法用于将 Person 对象转换为 JSON 数据。

常用注解

@JsonKey

用于指定 JSON 键名、默认值、忽略序列化等。

const JsonKey({
    @Deprecated('Has no effect') bool? nullable,
    this.defaultValue,
    this.disallowNullValue,
    this.fromJson,
    @Deprecated(
      'Use `includeFromJson` and `includeToJson` with a value of `false` '
      'instead.',
    )
    this.ignore,
    this.includeFromJson,
    this.includeIfNull,
    this.includeToJson,
    this.name,
    this.readValue,
    this.required,
    this.toJson,
    this.unknownEnumValue,
  });
  • name 指定该属性在 JSON 数据中对应的键名。当 Dart 类属性名与 JSON 键名不一致时,可使用此参数进行映射。

    // 指定 JSON 键名
      @JsonKey(name: 'product_name')
      final String name;
    
  • defaultValue 指定该属性在 JSON 数据中不存在时的默认值。当反序列化时,如果 JSON 中没有对应的键,会使用此默认值。

    // 指定默认值
      @JsonKey(defaultValue: true)
      final bool isEnabled;
    
  • ignore 如果设置为 true,则该属性在序列化和反序列化过程中会被忽略,不会出现在生成的 JSON 数据中,也不会从 JSON 数据中读取

    // 忽略序列化和反序列化
      @JsonKey(ignore: true)
      final String password;
    
  • toJsonfromJson 分别指定自定义的序列化和反序列化函数。当默认的序列化和反序列化规则不满足需求时,可使用这两个参数自定义转换逻辑。

    // 自定义日期反序列化函数
    DateTime _dateFromJson(String json) => DateTime.parse(json);
    
    // 自定义日期序列化函数
    String _dateToJson(DateTime date) => date.toIso8601String();
    
    @JsonSerializable()
    class DateExample {
      @JsonKey(fromJson: _dateFromJson, toJson: _dateToJson)
      final DateTime date;
    
      DateExample(this.date);
    
      factory DateExample.fromJson(Map<String, dynamic> json) => _$DateExampleFromJson(json);
      Map<String, dynamic> toJson() => _$DateExampleToJson(this);
    }
    
  • includeIfNull 如果设置为 false,当该属性的值为 null 时,在序列化过程中不会将该属性包含在生成的 JSON 数据中。

@JsonKey(includeIfNull: false)
  final String description;

@JsonSerializable

这是 json_annotation 中最核心的注解,用于标记需要进行 JSON 序列化和反序列化的类。它会告诉 json_serializable 代码生成器为该类生成相应的 fromJsontoJson 方法

 const JsonSerializable({
    this.anyMap,
    this.checked,
    this.constructor,
    this.createFieldMap,
    this.createJsonKeys,
    this.createFactory,
    this.createToJson,
    this.disallowUnrecognizedKeys,
    this.explicitToJson,
    this.fieldRename,
    this.ignoreUnannotated,
    this.includeIfNull,
    this.converters,
    this.genericArgumentFactories,
    this.createPerFieldToJson,
  });
  • anyMap 默认值:false 如果设置为 true,生成的代码将支持 Map<dynamic, dynamic> 类型的 JSON 数据,而不仅仅是 Map<String, dynamic>。
  • checked 默认值:false 如果设置为 true,生成的代码在反序列化时会进行类型检查,若类型不匹配会抛出异常。
  • createFactory 默认值:true 如果设置为 false,将不会生成 fromJson 工厂构造函数。
  • createToJson 默认值:true 如果设置为 false,将不会生成 toJson 方法。
  • disallowUnrecognizedKeys 默认值:false 如果设置为 true,在反序列化时遇到 JSON 数据中存在类中未定义的键,会抛出异常。
  • explicitToJson 默认值:false 如果设置为 true,生成的 toJson 方法只会包含类中使用 @JsonKey 注解明确指定的属性。
  • fieldRename 默认值:FieldRename.none 指定属性名在 JSON 中的重命名策略,常见的策略有 FieldRename.snake(蛇形命名)、FieldRename.camel(驼峰命名)等。

防止类型解析失败:定义转换器

对于上面的person对象,我们解析json生成响应model

// 定义常量 JSON 数据
  const jsonData = {'name': 'Alice', 'age': "25"};
  Person p = Person.fromJson(jsonData);

image-20250219141237683

类型解析失败,因为我们json定义的age是字符串

我们怎么解决这个问题呢?

我们可以通过json_annotation定义一些转换器来解决

  • 1、定义一个SafeNumConverter转换器

import 'package:json_annotation/json_annotation.dart';

class SafeNumConverter extends JsonConverter<num, dynamic> {
  final num defaultValue;

  const SafeNumConverter({this.defaultValue = -10086});

  @override
  num fromJson(dynamic json) {
    if (json is num) {
      return json;
    }
    if (json is String) {
      try {
        return num.parse(json);
      } catch (e) {
        return defaultValue;
      }
    }
    return defaultValue;
  }

  @override
  dynamic toJson(num object) {
    return object;
  }
}
  • 2、在pubspec.yaml中配置转换器
# 配置 json_annotation 的全局转换器
json_annotation:
  options:
    # 启用全局转换器
    converters:
      - 'package:flutter_module/lib/core/jsonConverter/safe_dateTime_converter.dart::SafeDateTimeConverter'
  • 3、在model注解中添加转换器

    // 使用 @JsonSerializable 注解标记该类
    @JsonSerializable(genericArgumentFactories: true, converters: [SafeNumConverter()])
    class Person {
      // 类的属性
      final String name;
      final num age;
    
      // 构造函数
      Person(this.name, this.age);
    
      // 工厂方法,用于从 JSON 数据反序列化对象
      factory Person.fromJson(Map<String, dynamic> json) => _$PersonFromJson(json);
    
      // 方法,用于将对象序列化为 JSON 数据
      Map<String, dynamic> toJson() => _$PersonToJson(this);
    }
    
  • 4、重新执行生成命令flutter pub run build_runner build --delete-conflicting-outputs

image-20250219141928055

这个时候我们json转model就不会失败了。

扩展

我这边定义了几个转换器,方便拿来即食

SafeMapConverter

import 'dart:convert';
import 'package:json_annotation/json_annotation.dart';

class SafeMapConverter extends JsonConverter<Map<String, dynamic>, dynamic> {
  const SafeMapConverter();

  @override
  Map<String, dynamic> fromJson(dynamic json) {
    if (json is Map<String, dynamic>) {
      return json;
    }
    try {
      if (json is String) {
        final decoded = jsonDecode(json);
        if (decoded is Map<String, dynamic>) {
          return decoded;
        }
      }
    } catch (e) {
      // 处理解析异常
    }
    return {};
  }

  @override
  dynamic toJson(Map<String, dynamic> object) {
    return object;
  }
}

SafeListConverter

import 'dart:convert';
import 'package:json_annotation/json_annotation.dart';

class SafeListConverter extends JsonConverter<List<dynamic>, dynamic> {
  const SafeListConverter();

  @override
  List<dynamic> fromJson(dynamic json) {
    if (json is List<dynamic>) {
      return json;
    }
    try {
      if (json is String) {
        final decoded = jsonDecode(json);
        if (decoded is List<dynamic>) {
          return decoded;
        }
      }
    } catch (e) {
      // 捕获解析过程中可能出现的异常
    }
    return [];
  }

  @override
  dynamic toJson(List<dynamic> object) {
    return object;
  }
}

SafeDateTimeConverter

class SafeDateTimeConverter implements JsonConverter<DateTime?, dynamic> {
  const SafeDateTimeConverter();

  @override
  DateTime? fromJson(dynamic json) {
    if (json is DateTime) return json;
    if (json is String) return DateTime.tryParse(json);
    if (json is int) return DateTime.fromMillisecondsSinceEpoch(json);
    return null;
  }

  @override
  dynamic toJson(DateTime? date) => date?.toIso8601String();
}

JsonTypeAdapter

class JsonTypeAdapter {
  // 安全转换数字(处理int/double/字符串数字混合场景)
  static num? safeParseNumber(dynamic value) {
    if (value is num) return value;
    if (value is String) {
      return num.tryParse(value);
    }
    return null;
  }

  // 安全日期解析(支持多种日期格式)
  static DateTime? safeParseDate(dynamic value) {
    if (value is DateTime) return value;
    if (value is String) return DateTime.tryParse(value);
    if (value is int) return DateTime.fromMillisecondsSinceEpoch(value);
    return null;
  }

  // 布尔类型安全转换
  static bool safeParseBool(dynamic value) {
    if (value is bool) return value;
    if (value is int) return value != 0;
    if (value is String) {
      return value.toLowerCase() == 'true';
    }
    return false;
  }
}