/// Parse a value that may be [int], [num], or [String] to [int]. /// Used as `@JsonKey(fromJson: flexibleInt)` and in manual `fromJson` factories /// to handle Plex API responses where numeric fields may arrive as strings /// (XML-to-JSON conversion). int? flexibleInt(Object? v) => switch (v) { final num n => n.toInt(), final String s => int.tryParse(s), _ => null, }; /// Parse a value that may be [bool], [int] (0/1), or [String] ('1') to [bool]. /// Returns `false` for `null` or unrecognised values. /// Handles Plex API responses where boolean fields may arrive as integers. bool flexibleBool(Object? v) => switch (v) { final bool b => b, final int n => n == 1, final String s => s == '1', _ => false, }; /// Parse a value that may be [bool], [int] (0/1), or [String] ('1') to [bool]. /// Returns `null` for `null` or unrecognised values. bool? flexibleBoolNullable(Object? v) => switch (v) { final bool b => b, final int n => n == 1, final String s => s == '1', _ => null, }; /// Parse a value that may be [double], [num], or [String] to [double]. double? flexibleDouble(Object? v) => switch (v) { final num n => n.toDouble(), final String s => double.tryParse(s), _ => null, }; /// `@JsonKey(readValue:)` adapter — coerces the named field to a String via /// `toString()` before the generated cast. Use for required `String` fields /// that Plex may return as int in some endpoints. Object? readStringField(Map json, String key) => json[key]?.toString(); /// Coerce a value that may be a single Map or a List of Maps into a `List`. /// Plex often returns `{"Part": {...}}` for single-part media and /// `{"Part": [{...}, {...}]}` for multi-part — this normalises both shapes. /// Returns `null` when the value is `null`. List? flexibleList(Object? v) => switch (v) { null => null, final List l => l, _ => [v], }; /// Coerce a single String, a List of Strings, or null into `List?`. /// Non-string elements are dropped; an empty result (or null input) yields /// `null`. Typed sibling of [flexibleList] — a bare String is wrapped into a /// single-element list, so callers tolerate both single-value and list shapes. List? flexibleStringList(Object? v) { final list = flexibleList(v); if (list == null) return null; final result = [ for (final e in list) if (e is String) e, ]; return result.isEmpty ? null : result; } List? stringListFromRaw(Object? raw, {String? mapKey, bool stringify = false, bool nullIfEmpty = false}) { if (raw is! List) return null; final result = []; for (final value in raw) { final source = mapKey != null && value is Map ? value[mapKey] : value; final string = stringify ? source?.toString() : source is String ? source : null; if (string != null) result.add(string); } if (result.isEmpty && nullIfEmpty) return null; return result; } List? nullIfEmptyList(List values) => values.isEmpty ? null : values;