Files
AcWeb/ac_web/lib/Api/JsonReader.dart
T

145 lines
3.7 KiB
Dart

/// Small helper that makes reading .Net json payloads defensive.
///
/// The backend is still in development, so property names might arrive in
/// PascalCase or camelCase and single properties might be missing. These
/// helpers keep the dto factories short and readable.
class JsonReader {
const JsonReader._();
/// Looks a key up, ignoring casing differences between .Net and Dart.
static dynamic value(Map<String, dynamic> json, String key) {
if (json.containsKey(key)) {
return json[key];
}
final lowerKey = key.toLowerCase();
for (final entry in json.entries) {
if (entry.key.toLowerCase() == lowerKey) {
return entry.value;
}
}
return null;
}
static String requireString(Map<String, dynamic> json, String key) {
final raw = value(json, key);
if (raw == null) {
throw FormatException('Missing required property "$key".');
}
return raw.toString();
}
static String? optionalString(Map<String, dynamic> json, String key) {
final raw = value(json, key);
return raw?.toString();
}
static DateTime requireDateTime(Map<String, dynamic> json, String key) {
final parsed = optionalDateTime(json, key);
if (parsed == null) {
throw FormatException('Missing or invalid date property "$key".');
}
return parsed;
}
static DateTime? optionalDateTime(Map<String, dynamic> json, String key) {
final raw = value(json, key);
if (raw == null) {
return null;
}
if (raw is DateTime) {
return raw;
}
return DateTime.tryParse(raw.toString());
}
static bool? optionalBool(Map<String, dynamic> json, String key) {
final raw = value(json, key);
if (raw == null) {
return null;
}
if (raw is bool) {
return raw;
}
final text = raw.toString().toLowerCase();
if (text == 'true' || text == '1') {
return true;
}
if (text == 'false' || text == '0') {
return false;
}
return null;
}
static int? optionalInt(Map<String, dynamic> json, String key) {
final raw = value(json, key);
if (raw == null) {
return null;
}
if (raw is int) {
return raw;
}
if (raw is num) {
return raw.toInt();
}
return int.tryParse(raw.toString());
}
/// Maps a json array onto a list of models, tolerating a missing property.
static List<T> list<T>(
Map<String, dynamic> json,
String key,
T Function(Map<String, dynamic> json) fromJson,
) {
final raw = value(json, key);
if (raw is! List) {
return <T>[];
}
return raw
.whereType<Map>()
.map((entry) => fromJson(Map<String, dynamic>.from(entry)))
.toList(growable: false);
}
/// Resolves a .Net enum that can arrive either as its name or as its index.
static T enumValue<T>(
Map<String, dynamic> json,
String key,
List<T> values,
String Function(T value) nameOf,
T fallback,
) {
final raw = value(json, key);
if (raw == null) {
return fallback;
}
if (raw is num) {
final index = raw.toInt();
if (index >= 0 && index < values.length) {
return values[index];
}
return fallback;
}
final text = raw.toString().trim().toLowerCase();
final normalized = _normalizeEnumName(text);
for (final candidate in values) {
if (_normalizeEnumName(nameOf(candidate).toLowerCase()) == normalized) {
return candidate;
}
}
final index = int.tryParse(text);
if (index != null && index >= 0 && index < values.length) {
return values[index];
}
return fallback;
}
/// Removes separators so that `AC_API` also matches `acApi`.
static String _normalizeEnumName(String value) {
return value.replaceAll(RegExp(r'[^a-z0-9]'), '');
}
}