鸿蒙Flutter JSON数据模型设计
·


引言
在Flutter应用开发中,数据模型是连接JSON数据和业务逻辑的桥梁。良好的数据模型设计不仅能提高代码的可读性和可维护性,还能确保类型安全和数据完整性。特别是在天气查询应用中,数据模型需要处理复杂的嵌套结构、可选字段和各种数据类型。
本章节将深入探讨JSON数据模型设计的核心原则和最佳实践,结合天气查询应用的实际场景,帮助读者掌握设计高质量数据模型的方法。
1. 数据模型设计原则
在开始设计数据模型之前,需要明确一些基本原则:
1.1 单一职责原则
每个数据模型类应该只负责一个实体的数据:
// 正确:每个类负责一个实体
class Weather {
final String city;
final double temperature;
Weather({required this.city, required this.temperature});
}
class Wind {
final String direction;
final double speed;
Wind({required this.direction, required this.speed});
}
// 错误:一个类负责多个实体
class WeatherAndWind {
final String city;
final double temperature;
final String windDirection;
final double windSpeed;
}
1.2 不可变性原则
使用final字段确保数据不可变,避免意外的数据修改:
// 正确:所有字段都是final
class Weather {
final String city;
final double temperature;
final DateTime lastUpdated;
Weather({
required this.city,
required this.temperature,
required this.lastUpdated,
});
}
// 错误:字段可以被修改
class MutableWeather {
String city = "";
double temperature = 0.0;
void updateTemperature(double newTemp) {
temperature = newTemp;
}
}
1.3 类型安全原则
明确指定字段类型,避免使用dynamic:
// 正确:明确的类型
class Weather {
final String city;
final double temperature;
final int humidity;
Weather({
required this.city,
required this.temperature,
required this.humidity,
});
}
// 错误:使用dynamic
class DynamicWeather {
dynamic city;
dynamic temperature;
dynamic humidity;
}
1.4 可扩展性原则
考虑未来可能的字段变更,合理设计模型结构:
class Weather {
final String city;
final double temperature;
final String condition;
final int humidity;
final double windSpeed;
final String description;
final DateTime lastUpdated;
Weather({
required this.city,
required this.temperature,
required this.condition,
required this.humidity,
required this.windSpeed,
this.description = "",
required this.lastUpdated,
});
}
1.5 错误处理原则
处理可选字段和类型转换错误:
class SafeWeather {
final String city;
final double temperature;
final String condition;
SafeWeather._({
required this.city,
required this.temperature,
required this.condition,
});
factory SafeWeather.fromJson(Map<String, dynamic> json) {
return SafeWeather._(
city: _parseString(json["city"]),
temperature: _parseDouble(json["temperature"]),
condition: _parseString(json["condition"]),
);
}
}
String _parseString(dynamic value) {
if (value is String) return value;
return "";
}
double _parseDouble(dynamic value) {
if (value is num) return value.toDouble();
return 0.0;
}
2. 标准数据模型设计
标准数据模型是最常用的设计方式,包含fromJson构造函数和toJson方法。
2.1 基础模型设计
class Weather {
final String city;
final double temperature;
final String condition;
final int humidity;
final double windSpeed;
final String description;
final DateTime lastUpdated;
Weather({
required this.city,
required this.temperature,
required this.condition,
required this.humidity,
required this.windSpeed,
this.description = "",
required this.lastUpdated,
});
factory Weather.fromJson(Map<String, dynamic> json) {
return Weather(
city: json["city"] as String,
temperature: (json["temperature"] as num).toDouble(),
condition: json["condition"] as String,
humidity: json["humidity"] as int,
windSpeed: (json["wind_speed"] as num?)?.toDouble() ?? 0.0,
description: json["description"] as String? ?? "",
lastUpdated: DateTime.parse(json["last_updated"] as String),
);
}
Map<String, dynamic> toJson() {
return {
"city": city,
"temperature": temperature,
"condition": condition,
"humidity": humidity,
"wind_speed": windSpeed,
"description": description,
"last_updated": lastUpdated.toIso8601String(),
};
}
}
2.2 模型组合
当JSON结构包含嵌套对象时,需要组合多个模型:
class Wind {
final String direction;
final double speed;
Wind({required this.direction, required this.speed});
factory Wind.fromJson(Map<String, dynamic> json) {
return Wind(
direction: json["direction"] as String,
speed: (json["speed"] as num).toDouble(),
);
}
Map<String, dynamic> toJson() {
return {"direction": direction, "speed": speed};
}
}
class CurrentWeather {
final double temp;
final int humidity;
final String condition;
final Wind wind;
final String updateTime;
CurrentWeather({
required this.temp,
required this.humidity,
required this.condition,
required this.wind,
required this.updateTime,
});
factory CurrentWeather.fromJson(Map<String, dynamic> json) {
return CurrentWeather(
temp: (json["temp"] as num).toDouble(),
humidity: json["humidity"] as int,
condition: json["condition"] as String,
wind: Wind.fromJson(json["wind"]),
updateTime: json["updateTime"] as String,
);
}
}
class WeatherResponse {
final CurrentWeather current;
final List<DailyForecast> forecast;
final DateTime lastUpdated;
WeatherResponse({
required this.current,
required this.forecast,
required this.lastUpdated,
});
factory WeatherResponse.fromJson(Map<String, dynamic> json) {
List<dynamic> forecastList = json["forecast"] as List;
return WeatherResponse(
current: CurrentWeather.fromJson(json["current"]),
forecast: forecastList
.map((e) => DailyForecast.fromJson(e))
.toList(),
lastUpdated: DateTime.parse(json["last_updated"] as String),
);
}
}
2.3 数组处理
处理JSON中的数组数据:
class DailyForecast {
final String date;
final int high;
final int low;
final String condition;
DailyForecast({
required this.date,
required this.high,
required this.low,
required this.condition,
});
factory DailyForecast.fromJson(Map<String, dynamic> json) {
return DailyForecast(
date: json["date"] as String,
high: json["high"] as int,
low: json["low"] as int,
condition: json["condition"] as String,
);
}
}
class HourlyForecast {
final String time;
final int temp;
HourlyForecast({required this.time, required this.temp});
factory HourlyForecast.fromJson(Map<String, dynamic> json) {
return HourlyForecast(
time: json["time"] as String,
temp: json["temp"] as int,
);
}
}
3. 使用freezed实现不可变模型
freezed是一个强大的代码生成库,可以帮助我们实现不可变数据模型。
3.1 添加依赖
首先需要在pubspec.yaml中添加依赖:
dependencies:
freezed_annotation: ^2.4.4
json_annotation: ^4.9.0
dev_dependencies:
build_runner: ^2.4.8
freezed: ^2.5.7
json_serializable: ^6.8.0
3.2 定义freezed模型
import 'package:freezed_annotation/freezed_annotation.dart';
part 'weather.freezed.dart';
part 'weather.g.dart';
class Weather with _$Weather {
const factory Weather({
required String city,
required double temperature,
required String condition,
required int humidity,
(0.0) double windSpeed,
("") String description,
required DateTime lastUpdated,
}) = _Weather;
factory Weather.fromJson(Map<String, dynamic> json) =>
_$WeatherFromJson(json);
}
class Wind with _$Wind {
const factory Wind({
required String direction,
required double speed,
}) = _Wind;
factory Wind.fromJson(Map<String, dynamic> json) =>
_$WindFromJson(json);
}
3.3 freezed的优势
| 特性 | 手动实现 | freezed |
|---|---|---|
| 不可变性 | 需要手动声明final | 自动实现 |
| 相等性判断 | 需要重写==和hashCode | 自动生成 |
| copyWith | 需要手动实现 | 自动生成 |
| toString | 默认实现不友好 | 自动生成可读格式 |
| 模式匹配 | 不支持 | 支持when/maybeWhen |
| 代码量 | 多 | 少 |
3.4 使用copyWith更新数据
void copyWithDemo() {
Weather weather = Weather(
city: "北京",
temperature: 28.5,
condition: "晴",
humidity: 65,
lastUpdated: DateTime.now(),
);
Weather updatedWeather = weather.copyWith(
temperature: 30.0,
condition: "多云",
);
print('原始温度: ${weather.temperature}');
print('更新温度: ${updatedWeather.temperature}');
}
3.5 使用模式匹配
void patternMatchingDemo(Weather weather) {
String status = weather.when(
sunny: (weather) => "天气晴朗",
rainy: (weather) => "正在下雨",
cloudy: (weather) => "多云天气",
snowy: (weather) => "雪花飘飘",
);
print(status);
}
4. 错误安全的模型设计
在实际开发中,JSON数据可能包含错误或缺失的字段,需要设计具有错误处理能力的数据模型。
4.1 安全解析函数
class JsonParser {
static String parseString(dynamic value, {String defaultValue = ""}) {
if (value is String) return value;
return defaultValue;
}
static int parseInt(dynamic value, {int defaultValue = 0}) {
if (value is int) return value;
if (value is double) return value.toInt();
if (value is String) return int.tryParse(value) ?? defaultValue;
return defaultValue;
}
static double parseDouble(dynamic value, {double defaultValue = 0.0}) {
if (value is double) return value;
if (value is int) return value.toDouble();
if (value is String) return double.tryParse(value) ?? defaultValue;
return defaultValue;
}
static bool parseBool(dynamic value, {bool defaultValue = false}) {
if (value is bool) return value;
if (value is String) return value.toLowerCase() == "true";
if (value is int) return value == 1;
return defaultValue;
}
static DateTime? parseDateTime(dynamic value) {
if (value is String) {
return DateTime.tryParse(value);
}
if (value is int) {
return DateTime.fromMillisecondsSinceEpoch(value);
}
return null;
}
}
4.2 使用安全解析函数
class SafeWeather {
final String city;
final double temperature;
final String condition;
final int humidity;
final DateTime? lastUpdated;
SafeWeather({
required this.city,
required this.temperature,
required this.condition,
required this.humidity,
this.lastUpdated,
});
factory SafeWeather.fromJson(Map<String, dynamic> json) {
return SafeWeather(
city: JsonParser.parseString(json["city"]),
temperature: JsonParser.parseDouble(json["temperature"]),
condition: JsonParser.parseString(json["condition"]),
humidity: JsonParser.parseInt(json["humidity"]),
lastUpdated: JsonParser.parseDateTime(json["last_updated"]),
);
}
}
4.3 验证数据完整性
class ValidatedWeather {
final String city;
final double temperature;
final String condition;
ValidatedWeather._({
required this.city,
required this.temperature,
required this.condition,
});
static ValidatedWeather? fromJson(Map<String, dynamic> json) {
String? city = JsonParser.parseString(json["city"]);
if (city == null || city.isEmpty) {
print("城市名称无效");
return null;
}
double temperature = JsonParser.parseDouble(json["temperature"]);
if (temperature < -100 || temperature > 100) {
print("温度值超出范围");
return null;
}
String? condition = JsonParser.parseString(json["condition"]);
if (condition == null || condition.isEmpty) {
print("天气状况无效");
return null;
}
return ValidatedWeather._(
city: city,
temperature: temperature,
condition: condition,
);
}
}
5. 模型扩展方法
使用扩展方法为模型添加额外的功能,保持模型类的简洁。
5.1 添加计算属性
extension WeatherExtension on Weather {
String get temperatureStatus {
if (temperature > 30) return "炎热";
if (temperature > 20) return "舒适";
if (temperature > 10) return "凉爽";
return "寒冷";
}
bool get isSunny => condition == "晴" || condition == "sunny";
bool get isRainy => condition == "雨" || condition == "rainy";
String formatLastUpdated() {
return "${lastUpdated.month}月${lastUpdated.day}日 ${lastUpdated.hour}:${lastUpdated.minute.toString().padLeft(2, "0")}";
}
String get conditionEmoji {
switch (condition) {
case "晴":
case "sunny":
return "☀️";
case "多云":
case "cloudy":
return "⛅";
case "雨":
case "rainy":
return "🌧️";
case "雪":
case "snowy":
return "❄️";
default:
return "🌤️";
}
}
}
5.2 使用扩展方法
void extensionUsage() {
Weather weather = Weather(
city: "北京",
temperature: 28.5,
condition: "晴",
humidity: 65,
lastUpdated: DateTime.now(),
);
print('${weather.city}的温度状态: ${weather.temperatureStatus}');
print('是否晴天: ${weather.isSunny}');
print('更新时间: ${weather.formatLastUpdated()}');
print('天气图标: ${weather.conditionEmoji}');
}
5.3 添加转换方法
extension WeatherConversion on Weather {
Map<String, dynamic> toSimpleJson() {
return {
"city": city,
"temperature": temperature,
"condition": condition,
};
}
Weather copyWithTemp(double newTemp) {
return Weather(
city: city,
temperature: newTemp,
condition: condition,
humidity: humidity,
windSpeed: windSpeed,
description: description,
lastUpdated: lastUpdated,
);
}
}
6. 泛型数据模型
使用泛型可以创建通用的数据模型,避免重复代码。
6.1 创建泛型响应模型
class ApiResponse<T> {
final int code;
final String message;
final T? data;
ApiResponse({
required this.code,
required this.message,
this.data,
});
factory ApiResponse.fromJson(
Map<String, dynamic> json,
T Function(Map<String, dynamic>) fromJsonT,
) {
return ApiResponse<T>(
code: json["code"] as int,
message: json["message"] as String? ?? "",
data: json["data"] != null ? fromJsonT(json["data"]) : null,
);
}
bool get isSuccess => code == 200;
}
6.2 使用泛型响应模型
void genericResponseDemo() {
String jsonStr = '''{
"code": 200,
"message": "success",
"data": {"city": "北京", "temperature": 28.5}
}''';
Map<String, dynamic> data = json.decode(jsonStr);
ApiResponse<Weather> response = ApiResponse.fromJson(
data,
(json) => Weather.fromJson(json),
);
if (response.isSuccess && response.data != null) {
print('城市: ${response.data!.city}');
print('温度: ${response.data!.temperature}');
} else {
print('请求失败: ${response.message}');
}
}
6.3 创建泛型列表响应模型
class ApiListResponse<T> {
final int code;
final String message;
final List<T>? data;
ApiListResponse({
required this.code,
required this.message,
this.data,
});
factory ApiListResponse.fromJson(
Map<String, dynamic> json,
T Function(Map<String, dynamic>) fromJsonT,
) {
List<dynamic>? dataList = json["data"] as List?;
return ApiListResponse<T>(
code: json["code"] as int,
message: json["message"] as String? ?? "",
data: dataList?.map((e) => fromJsonT(e)).toList(),
);
}
bool get isSuccess => code == 200;
int get count => data?.length ?? 0;
}
7. 数据模型版本管理
随着API的迭代,数据模型可能需要进行版本管理,确保向后兼容。
7.1 版本字段
class Weather {
final String city;
final double temperature;
final String condition;
final int apiVersion;
Weather({
required this.city,
required this.temperature,
required this.condition,
this.apiVersion = 1,
});
factory Weather.fromJson(Map<String, dynamic> json) {
int version = json["api_version"] as int? ?? 1;
if (version == 1) {
return Weather(
city: json["city"] as String,
temperature: (json["temp"] as num).toDouble(),
condition: json["condition"] as String,
apiVersion: version,
);
} else {
return Weather(
city: json["city_name"] as String,
temperature: (json["temperature"] as num).toDouble(),
condition: json["weather_condition"] as String,
apiVersion: version,
);
}
}
}
7.2 迁移工具
class WeatherMigration {
static Weather migrateFromV1(Map<String, dynamic> v1Data) {
return Weather(
city: v1Data["city"] as String,
temperature: (v1Data["temp"] as num).toDouble(),
condition: v1Data["condition"] as String,
apiVersion: 1,
);
}
static Weather migrateFromV2(Map<String, dynamic> v2Data) {
return Weather(
city: v2Data["city_name"] as String,
temperature: (v2Data["temperature"] as num).toDouble(),
condition: v2Data["weather_condition"] as String,
apiVersion: 2,
);
}
static Weather fromJson(Map<String, dynamic> json) {
int version = json["api_version"] as int? ?? 1;
if (version == 1) {
return migrateFromV1(json);
} else {
return migrateFromV2(json);
}
}
}
8. 数据模型测试
为数据模型编写测试是确保代码质量的重要环节。
8.1 单元测试
import 'package:test/test.dart';
import 'dart:convert';
void main() {
group('Weather.fromJson', () {
test('should parse basic weather data', () {
String jsonStr = '''{
"city": "北京",
"temperature": 28.5,
"condition": "晴",
"humidity": 65,
"wind_speed": 3.5,
"description": "晴朗天气",
"last_updated": "2024-01-15T10:00:00Z"
}''';
Weather weather = Weather.fromJson(json.decode(jsonStr));
expect(weather.city, equals("北京"));
expect(weather.temperature, equals(28.5));
expect(weather.condition, equals("晴"));
expect(weather.humidity, equals(65));
expect(weather.windSpeed, equals(3.5));
expect(weather.description, equals("晴朗天气"));
});
test('should handle missing optional fields', () {
String jsonStr = '''{
"city": "北京",
"temperature": 28.5,
"condition": "晴",
"humidity": 65,
"last_updated": "2024-01-15T10:00:00Z"
}''';
Weather weather = Weather.fromJson(json.decode(jsonStr));
expect(weather.windSpeed, equals(0.0));
expect(weather.description, equals(""));
});
test('should handle null fields', () {
String jsonStr = '''{
"city": "北京",
"temperature": 28.5,
"condition": "晴",
"humidity": 65,
"wind_speed": null,
"description": null,
"last_updated": "2024-01-15T10:00:00Z"
}''';
Weather weather = Weather.fromJson(json.decode(jsonStr));
expect(weather.windSpeed, equals(0.0));
expect(weather.description, equals(""));
});
});
group('Weather.toJson', () {
test('should serialize weather data', () {
Weather weather = Weather(
city: "北京",
temperature: 28.5,
condition: "晴",
humidity: 65,
windSpeed: 3.5,
description: "晴朗天气",
lastUpdated: DateTime.parse("2024-01-15T10:00:00Z"),
);
Map<String, dynamic> json = weather.toJson();
expect(json["city"], equals("北京"));
expect(json["temperature"], equals(28.5));
expect(json["condition"], equals("晴"));
expect(json["wind_speed"], equals(3.5));
});
});
group('WeatherExtension', () {
test('temperatureStatus should return correct status', () {
Weather hotWeather = Weather(
city: "北京",
temperature: 35.0,
condition: "晴",
humidity: 65,
lastUpdated: DateTime.now(),
);
expect(hotWeather.temperatureStatus, equals("炎热"));
Weather comfortableWeather = Weather(
city: "北京",
temperature: 25.0,
condition: "晴",
humidity: 65,
lastUpdated: DateTime.now(),
);
expect(comfortableWeather.temperatureStatus, equals("舒适"));
});
});
}
8.2 属性测试
void fuzzTest() {
for (int i = 0; i < 100; i++) {
String jsonStr = generateRandomWeatherJson();
try {
Weather weather = Weather.fromJson(json.decode(jsonStr));
expect(weather.city, isNotEmpty);
expect(weather.temperature, isNotNull);
expect(weather.condition, isNotEmpty);
} catch (e) {
print('测试用例失败: $jsonStr');
print('错误信息: $e');
rethrow;
}
}
}
String generateRandomWeatherJson() {
List<String> cities = ["北京", "上海", "广州", "深圳", "杭州"];
List<String> conditions = ["晴", "多云", "雨", "雪", "阴"];
return '''{
"city": "${cities[Random().nextInt(cities.length)]}",
"temperature": ${10 + Random().nextDouble() * 30},
"condition": "${conditions[Random().nextInt(conditions.length)]}",
"humidity": ${30 + Random().nextInt(70)},
"last_updated": "${DateTime.now().toIso8601String()}"
}''';
}
9. 实战案例:天气查询应用完整数据模型
结合天气查询应用的实际场景,展示完整的数据模型设计:
class WeatherLocation {
final String city;
final String cityId;
final double latitude;
final double longitude;
WeatherLocation({
required this.city,
required this.cityId,
required this.latitude,
required this.longitude,
});
factory WeatherLocation.fromJson(Map<String, dynamic> json) {
return WeatherLocation(
city: JsonParser.parseString(json["city"]),
cityId: JsonParser.parseString(json["city_id"]),
latitude: JsonParser.parseDouble(json["latitude"]),
longitude: JsonParser.parseDouble(json["longitude"]),
);
}
}
class WeatherCondition {
final String text;
final String icon;
final int code;
WeatherCondition({
required this.text,
required this.icon,
required this.code,
});
factory WeatherCondition.fromJson(Map<String, dynamic> json) {
return WeatherCondition(
text: JsonParser.parseString(json["text"]),
icon: JsonParser.parseString(json["icon"]),
code: JsonParser.parseInt(json["code"]),
);
}
}
class AirQuality {
final int aqi;
final String level;
final String primary;
AirQuality({
required this.aqi,
required this.level,
required this.primary,
});
factory AirQuality.fromJson(Map<String, dynamic> json) {
return AirQuality(
aqi: JsonParser.parseInt(json["aqi"]),
level: JsonParser.parseString(json["level"]),
primary: JsonParser.parseString(json["primary"]),
);
}
String get healthAdvice {
if (aqi <= 50) return "空气质量优,适合户外活动";
if (aqi <= 100) return "空气质量良,正常活动";
if (aqi <= 150) return "轻度污染,敏感人群减少户外活动";
if (aqi <= 200) return "中度污染,减少户外活动";
if (aqi <= 300) return "重度污染,避免户外活动";
return "严重污染,留在室内";
}
}
class CurrentWeather {
final WeatherLocation location;
final WeatherCondition condition;
final double temp;
final double feelsLike;
final int humidity;
final int windDirection;
final double windSpeed;
final double visibility;
final double pressure;
final double uvIndex;
final DateTime updateTime;
CurrentWeather({
required this.location,
required this.condition,
required this.temp,
required this.feelsLike,
required this.humidity,
required this.windDirection,
required this.windSpeed,
required this.visibility,
required this.pressure,
required this.uvIndex,
required this.updateTime,
});
factory CurrentWeather.fromJson(Map<String, dynamic> json) {
return CurrentWeather(
location: WeatherLocation.fromJson(json["location"]),
condition: WeatherCondition.fromJson(json["condition"]),
temp: JsonParser.parseDouble(json["temp"]),
feelsLike: JsonParser.parseDouble(json["feels_like"]),
humidity: JsonParser.parseInt(json["humidity"]),
windDirection: JsonParser.parseInt(json["wind_direction"]),
windSpeed: JsonParser.parseDouble(json["wind_speed"]),
visibility: JsonParser.parseDouble(json["visibility"]),
pressure: JsonParser.parseDouble(json["pressure"]),
uvIndex: JsonParser.parseDouble(json["uv_index"]),
updateTime: JsonParser.parseDateTime(json["update_time"]) ?? DateTime.now(),
);
}
}
class DailyForecast {
final DateTime date;
final WeatherCondition condition;
final int high;
final int low;
final int humidity;
final int windDirection;
final double windSpeed;
final int pop;
final double uvIndex;
DailyForecast({
required this.date,
required this.condition,
required this.high,
required this.low,
required this.humidity,
required this.windDirection,
required this.windSpeed,
required this.pop,
required this.uvIndex,
});
factory DailyForecast.fromJson(Map<String, dynamic> json) {
return DailyForecast(
date: JsonParser.parseDateTime(json["date"]) ?? DateTime.now(),
condition: WeatherCondition.fromJson(json["condition"]),
high: JsonParser.parseInt(json["high"]),
low: JsonParser.parseInt(json["low"]),
humidity: JsonParser.parseInt(json["humidity"]),
windDirection: JsonParser.parseInt(json["wind_direction"]),
windSpeed: JsonParser.parseDouble(json["wind_speed"]),
pop: JsonParser.parseInt(json["pop"]),
uvIndex: JsonParser.parseDouble(json["uv_index"]),
);
}
String get weekday {
List<String> weekdays = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"];
return weekdays[date.weekday % 7];
}
}
class HourlyForecast {
final DateTime time;
final WeatherCondition condition;
final int temp;
final int feelsLike;
final int humidity;
final int windDirection;
final double windSpeed;
final int pop;
HourlyForecast({
required this.time,
required this.condition,
required this.temp,
required this.feelsLike,
required this.humidity,
required this.windDirection,
required this.windSpeed,
required this.pop,
});
factory HourlyForecast.fromJson(Map<String, dynamic> json) {
return HourlyForecast(
time: JsonParser.parseDateTime(json["time"]) ?? DateTime.now(),
condition: WeatherCondition.fromJson(json["condition"]),
temp: JsonParser.parseInt(json["temp"]),
feelsLike: JsonParser.parseInt(json["feels_like"]),
humidity: JsonParser.parseInt(json["humidity"]),
windDirection: JsonParser.parseInt(json["wind_direction"]),
windSpeed: JsonParser.parseDouble(json["wind_speed"]),
pop: JsonParser.parseInt(json["pop"]),
);
}
String get hourString {
return "${time.hour.toString().padLeft(2, "0")}:00";
}
}
class WeatherResponse {
final int code;
final String message;
final CurrentWeather? current;
final List<DailyForecast>? daily;
final List<HourlyForecast>? hourly;
final AirQuality? airQuality;
WeatherResponse({
required this.code,
required this.message,
this.current,
this.daily,
this.hourly,
this.airQuality,
});
factory WeatherResponse.fromJson(Map<String, dynamic> json) {
List<dynamic>? dailyList = json["daily"] as List?;
List<dynamic>? hourlyList = json["hourly"] as List?;
return WeatherResponse(
code: JsonParser.parseInt(json["code"]),
message: JsonParser.parseString(json["message"]),
current: json["current"] != null
? CurrentWeather.fromJson(json["current"])
: null,
daily: dailyList?.map((e) => DailyForecast.fromJson(e)).toList(),
hourly: hourlyList?.map((e) => HourlyForecast.fromJson(e)).toList(),
airQuality: json["air_quality"] != null
? AirQuality.fromJson(json["air_quality"])
: null,
);
}
bool get isSuccess => code == 200;
}
10. 数据模型设计最佳实践总结
10.1 设计流程
- 分析JSON结构:仔细分析API返回的JSON数据结构
- 划分实体:将JSON结构划分为多个实体类
- 定义字段:为每个实体定义字段,使用final确保不可变性
- 实现fromJson:实现从JSON到对象的转换
- 实现toJson:实现从对象到JSON的转换
- 添加扩展方法:使用扩展方法添加额外功能
- 编写测试:为数据模型编写单元测试
10.2 命名规范
- 类名使用PascalCase
- 字段名使用camelCase
- JSON键名使用snake_case
- 使用@JsonKey注解映射不同的命名风格
10.3 工具选择
| 场景 | 推荐工具 |
|---|---|
| 简单项目 | 手动实现 |
| 中等项目 | json_serializable |
| 需要不可变模型 | freezed |
| 需要极致性能 | built_value |
10.4 常见错误及解决方案
| 错误类型 | 原因 | 解决方案 |
|---|---|---|
| 字段命名不匹配 | JSON键名与Dart字段名不同 | 使用@JsonKey注解 |
| 类型转换错误 | JSON值类型与Dart字段类型不匹配 | 使用安全解析函数 |
| 空值错误 | 访问了可能为null的字段 | 使用空值检查和默认值 |
| 嵌套对象错误 | 嵌套对象为null | 在访问前检查嵌套对象 |
| 数组处理错误 | JSON数组格式不正确 | 添加类型检查和错误处理 |
11. 总结
良好的数据模型设计是高效JSON解析的基础。本章介绍了:
- 数据模型设计原则:单一职责、不可变性、类型安全、可扩展性、错误处理
- 标准数据模型设计:包含fromJson和toJson方法的标准模型
- 使用freezed实现不可变模型:代码生成方式实现不可变数据模型
- 错误安全的模型设计:安全解析函数和数据验证
- 模型扩展方法:使用扩展方法添加额外功能
- 泛型数据模型:创建通用的数据模型
- 数据模型版本管理:处理API版本迭代
- 数据模型测试:单元测试和属性测试
通过掌握本章内容,读者可以在天气查询应用及其他Flutter项目中设计出高质量、可维护的数据模型,为应用的稳定运行打下坚实基础。
更多推荐



所有评论(0)