59 lines
1.9 KiB
Dart
59 lines
1.9 KiB
Dart
/// Defensive JSON helpers for the Subsonic protocol.
|
|
///
|
|
/// Subsonic servers are inconsistent (Timbre handles the same quirks in
|
|
/// `timbre-subsonic/src/models.rs` via `OneOrMany` / `deserialize_flexible_id`):
|
|
/// * a field that is sometimes a single object and sometimes an array
|
|
/// * IDs that are sometimes strings and sometimes integers
|
|
/// Every accessor below tolerates nulls and mixed types rather than throwing.
|
|
library;
|
|
|
|
/// Coerce a value that may be a single object, a list, or null into a list of
|
|
/// `T`, applying [fromJson] to each map element.
|
|
List<T> oneOrMany<T>(
|
|
dynamic value,
|
|
T Function(Map<String, dynamic>) fromJson,
|
|
) {
|
|
if (value == null) return const [];
|
|
if (value is List) {
|
|
return value
|
|
.whereType<Map>()
|
|
.map((e) => fromJson(e.cast<String, dynamic>()))
|
|
.toList();
|
|
}
|
|
if (value is Map) {
|
|
return [fromJson(value.cast<String, dynamic>())];
|
|
}
|
|
return const [];
|
|
}
|
|
|
|
/// Read a string that the server might encode as an int (e.g. IDs).
|
|
String? asString(dynamic value) {
|
|
if (value == null) return null;
|
|
if (value is String) return value;
|
|
return value.toString();
|
|
}
|
|
|
|
/// Read an int from a value that might be an int, num, or numeric string.
|
|
int? asInt(dynamic value) {
|
|
if (value == null) return null;
|
|
if (value is int) return value;
|
|
if (value is num) return value.toInt();
|
|
if (value is String) return int.tryParse(value);
|
|
return null;
|
|
}
|
|
|
|
/// Read a double from an int/num/numeric-string.
|
|
double? asDouble(dynamic value) {
|
|
if (value == null) return null;
|
|
if (value is double) return value;
|
|
if (value is num) return value.toDouble();
|
|
if (value is String) return double.tryParse(value);
|
|
return null;
|
|
}
|
|
|
|
/// Read a bool that might arrive as a bool or the string "true"/"false".
|
|
bool asBool(dynamic value, {bool orElse = false}) {
|
|
if (value is bool) return value;
|
|
if (value is String) return value.toLowerCase() == 'true';
|
|
return orElse;
|
|
}
|