refactor(json): share scalar adapters
This commit is contained in:
@@ -11,7 +11,7 @@ part 'media_part.g.dart';
|
||||
@JsonSerializable(includeIfNull: false)
|
||||
class MediaPart {
|
||||
/// Backend-opaque part identifier.
|
||||
@JsonKey(fromJson: _stringFromJson)
|
||||
@JsonKey(fromJson: stringOrEmpty)
|
||||
final String id;
|
||||
|
||||
/// Backend-specific path used to construct a direct stream URL — e.g. Plex's
|
||||
@@ -58,5 +58,3 @@ class MediaPart {
|
||||
/// metadata request includes `checkFiles=1`.
|
||||
bool get isPlayable => accessible != false && exists != false;
|
||||
}
|
||||
|
||||
String _stringFromJson(Object? raw) => (raw ?? '').toString();
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'media_part.dart';
|
||||
// **************************************************************************
|
||||
|
||||
MediaPart _$MediaPartFromJson(Map<String, dynamic> json) => MediaPart(
|
||||
id: _stringFromJson(json['id']),
|
||||
id: stringOrEmpty(json['id']),
|
||||
streamPath: json['streamPath'] as String?,
|
||||
file: json['file'] as String?,
|
||||
sizeBytes: flexibleInt(json['sizeBytes']),
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import '../utils/json_utils.dart';
|
||||
|
||||
part 'media_role.g.dart';
|
||||
|
||||
/// A cast or crew member attached to a media item.
|
||||
@JsonSerializable(includeIfNull: false)
|
||||
class MediaRole {
|
||||
final String? id;
|
||||
@JsonKey(fromJson: _stringFromJson)
|
||||
@JsonKey(fromJson: stringOrEmpty)
|
||||
final String tag;
|
||||
final String? role;
|
||||
final String? thumbPath;
|
||||
@@ -17,5 +19,3 @@ class MediaRole {
|
||||
|
||||
Map<String, dynamic> toJson() => _$MediaRoleToJson(this);
|
||||
}
|
||||
|
||||
String _stringFromJson(Object? raw) => (raw ?? '').toString();
|
||||
|
||||
@@ -8,7 +8,7 @@ part of 'media_role.dart';
|
||||
|
||||
MediaRole _$MediaRoleFromJson(Map<String, dynamic> json) => MediaRole(
|
||||
id: json['id'] as String?,
|
||||
tag: _stringFromJson(json['tag']),
|
||||
tag: stringOrEmpty(json['tag']),
|
||||
role: json['role'] as String?,
|
||||
thumbPath: json['thumbPath'] as String?,
|
||||
);
|
||||
|
||||
@@ -28,7 +28,7 @@ String _videoResolutionDisplayLabel(String resolution) {
|
||||
@JsonSerializable(includeIfNull: false, explicitToJson: true)
|
||||
class MediaVersion {
|
||||
/// Backend-opaque version identifier.
|
||||
@JsonKey(fromJson: _stringFromJson)
|
||||
@JsonKey(fromJson: stringOrEmpty)
|
||||
final String id;
|
||||
@JsonKey(fromJson: flexibleInt)
|
||||
final int? width;
|
||||
@@ -140,7 +140,6 @@ class MediaVersion {
|
||||
}
|
||||
}
|
||||
|
||||
String _stringFromJson(Object? raw) => (raw ?? '').toString();
|
||||
|
||||
List<MediaPart> _partsFromJson(Object? raw) {
|
||||
return raw is List
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'media_version.dart';
|
||||
// **************************************************************************
|
||||
|
||||
MediaVersion _$MediaVersionFromJson(Map<String, dynamic> json) => MediaVersion(
|
||||
id: _stringFromJson(json['id']),
|
||||
id: stringOrEmpty(json['id']),
|
||||
width: flexibleInt(json['width']),
|
||||
height: flexibleInt(json['height']),
|
||||
videoResolution: json['videoResolution'] as String?,
|
||||
|
||||
@@ -7,7 +7,7 @@ part 'plex_home.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class PlexHome {
|
||||
@JsonKey(fromJson: _intOr0)
|
||||
@JsonKey(fromJson: flexibleIntOrZero)
|
||||
final int id;
|
||||
@JsonKey(readValue: readStringField, defaultValue: '')
|
||||
final String name;
|
||||
@@ -52,5 +52,3 @@ class PlexHome {
|
||||
|
||||
bool get hasMultipleUsers => users.length > 1;
|
||||
}
|
||||
|
||||
int _intOr0(Object? value) => flexibleInt(value) ?? 0;
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'plex_home.dart';
|
||||
// **************************************************************************
|
||||
|
||||
PlexHome _$PlexHomeFromJson(Map<String, dynamic> json) => PlexHome(
|
||||
id: _intOr0(json['id']),
|
||||
id: flexibleIntOrZero(json['id']),
|
||||
name: readStringField(json, 'name') as String? ?? '',
|
||||
guestUserID: flexibleInt(json['guestUserID']),
|
||||
guestUserUUID: readStringField(json, 'guestUserUUID') as String? ?? '',
|
||||
|
||||
@@ -9,7 +9,7 @@ part 'plex_home_user.g.dart';
|
||||
/// coerce tolerantly instead of hard-casting.
|
||||
@JsonSerializable()
|
||||
class PlexHomeUser {
|
||||
@JsonKey(fromJson: _intOr0)
|
||||
@JsonKey(fromJson: flexibleIntOrZero)
|
||||
final int id;
|
||||
@JsonKey(readValue: readStringField, defaultValue: '')
|
||||
final String uuid;
|
||||
@@ -63,5 +63,3 @@ class PlexHomeUser {
|
||||
bool get isGuestUser => guest;
|
||||
bool get requiresPassword => protected;
|
||||
}
|
||||
|
||||
int _intOr0(Object? v) => flexibleInt(v) ?? 0;
|
||||
|
||||
@@ -7,7 +7,7 @@ part of 'plex_home_user.dart';
|
||||
// **************************************************************************
|
||||
|
||||
PlexHomeUser _$PlexHomeUserFromJson(Map<String, dynamic> json) => PlexHomeUser(
|
||||
id: _intOr0(json['id']),
|
||||
id: flexibleIntOrZero(json['id']),
|
||||
uuid: readStringField(json, 'uuid') as String? ?? '',
|
||||
title: readStringField(json, 'title') as String? ?? 'Unknown',
|
||||
username: readStringField(json, 'username') as String?,
|
||||
|
||||
@@ -4,11 +4,9 @@ import '../../utils/json_utils.dart';
|
||||
|
||||
part 'plex_subtitle_search_result.g.dart';
|
||||
|
||||
int _flexibleIntOrZero(Object? v) => flexibleInt(v) ?? 0;
|
||||
|
||||
@JsonSerializable()
|
||||
class PlexSubtitleSearchResult {
|
||||
@JsonKey(fromJson: _flexibleIntOrZero)
|
||||
@JsonKey(fromJson: flexibleIntOrZero)
|
||||
final int id;
|
||||
@JsonKey(readValue: readStringField, defaultValue: '')
|
||||
final String key;
|
||||
|
||||
@@ -9,7 +9,7 @@ part of 'plex_subtitle_search_result.dart';
|
||||
PlexSubtitleSearchResult _$PlexSubtitleSearchResultFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => PlexSubtitleSearchResult(
|
||||
id: _flexibleIntOrZero(json['id']),
|
||||
id: flexibleIntOrZero(json['id']),
|
||||
key: readStringField(json, 'key') as String? ?? '',
|
||||
codec: readStringField(json, 'codec') as String?,
|
||||
language: readStringField(json, 'language') as String?,
|
||||
|
||||
@@ -17,7 +17,7 @@ class PlexUserProfile implements MediaServerUserProfile {
|
||||
@JsonKey(fromJson: _boolOrTrue)
|
||||
@override
|
||||
final bool autoSelectAudio;
|
||||
@JsonKey(fromJson: _intOr0)
|
||||
@JsonKey(fromJson: flexibleIntOrZero)
|
||||
final int defaultAudioAccessibility;
|
||||
@JsonKey(fromJson: _flexibleLanguage)
|
||||
@override
|
||||
@@ -31,15 +31,15 @@ class PlexUserProfile implements MediaServerUserProfile {
|
||||
@JsonKey(fromJson: flexibleCsvStringList)
|
||||
@override
|
||||
final List<String>? defaultSubtitleLanguages;
|
||||
@JsonKey(fromJson: _intOr0)
|
||||
@JsonKey(fromJson: flexibleIntOrZero)
|
||||
final int autoSelectSubtitle;
|
||||
@JsonKey(fromJson: _intOr0)
|
||||
@JsonKey(fromJson: flexibleIntOrZero)
|
||||
final int defaultSubtitleAccessibility;
|
||||
@JsonKey(fromJson: _intOr1)
|
||||
final int defaultSubtitleForced;
|
||||
@JsonKey(fromJson: _intOr1)
|
||||
final int watchedIndicator;
|
||||
@JsonKey(fromJson: _intOr0)
|
||||
@JsonKey(fromJson: flexibleIntOrZero)
|
||||
final int mediaReviewsVisibility;
|
||||
@JsonKey(fromJson: flexibleCsvStringList)
|
||||
final List<String>? mediaReviewsLanguages;
|
||||
@@ -88,5 +88,4 @@ class PlexUserProfile implements MediaServerUserProfile {
|
||||
String? _flexibleLanguage(Object? v) => flexibleCsvStringList(v)?.first;
|
||||
|
||||
bool _boolOrTrue(Object? v) => flexibleBoolNullable(v) ?? true;
|
||||
int _intOr0(Object? v) => flexibleInt(v) ?? 0;
|
||||
int _intOr1(Object? v) => flexibleInt(v) ?? 1;
|
||||
|
||||
@@ -10,18 +10,22 @@ PlexUserProfile _$PlexUserProfileFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => PlexUserProfile(
|
||||
autoSelectAudio: _boolOrTrue(json['autoSelectAudio']),
|
||||
defaultAudioAccessibility: _intOr0(json['defaultAudioAccessibility']),
|
||||
defaultAudioAccessibility: flexibleIntOrZero(
|
||||
json['defaultAudioAccessibility'],
|
||||
),
|
||||
defaultAudioLanguage: _flexibleLanguage(json['defaultAudioLanguage']),
|
||||
defaultAudioLanguages: flexibleCsvStringList(json['defaultAudioLanguages']),
|
||||
defaultSubtitleLanguage: _flexibleLanguage(json['defaultSubtitleLanguage']),
|
||||
defaultSubtitleLanguages: flexibleCsvStringList(
|
||||
json['defaultSubtitleLanguages'],
|
||||
),
|
||||
autoSelectSubtitle: _intOr0(json['autoSelectSubtitle']),
|
||||
defaultSubtitleAccessibility: _intOr0(json['defaultSubtitleAccessibility']),
|
||||
autoSelectSubtitle: flexibleIntOrZero(json['autoSelectSubtitle']),
|
||||
defaultSubtitleAccessibility: flexibleIntOrZero(
|
||||
json['defaultSubtitleAccessibility'],
|
||||
),
|
||||
defaultSubtitleForced: _intOr1(json['defaultSubtitleForced']),
|
||||
watchedIndicator: _intOr1(json['watchedIndicator']),
|
||||
mediaReviewsVisibility: _intOr0(json['mediaReviewsVisibility']),
|
||||
mediaReviewsVisibility: flexibleIntOrZero(json['mediaReviewsVisibility']),
|
||||
mediaReviewsLanguages: flexibleCsvStringList(json['mediaReviewsLanguages']),
|
||||
);
|
||||
|
||||
|
||||
@@ -48,8 +48,6 @@ Map<String, dynamic> _obfuscatePlaylistJson(Map<String, dynamic> json) {
|
||||
return copy;
|
||||
}
|
||||
|
||||
int _flexibleIntOrZero(Object? v) => flexibleInt(v) ?? 0;
|
||||
|
||||
Map? _firstPartMap(Object? raw) {
|
||||
final parts = _partMaps(raw);
|
||||
return parts.isEmpty ? null : parts.first;
|
||||
@@ -307,7 +305,7 @@ class PlexRoleDto {
|
||||
|
||||
@JsonSerializable(createToJson: false)
|
||||
class PlexMediaVersionDto {
|
||||
@JsonKey(fromJson: _flexibleIntOrZero)
|
||||
@JsonKey(fromJson: flexibleIntOrZero)
|
||||
final int id;
|
||||
@JsonKey(readValue: readStringField)
|
||||
final String? videoResolution;
|
||||
@@ -506,7 +504,7 @@ class PlexHubDto {
|
||||
@JsonKey(defaultValue: 'hub')
|
||||
final String type;
|
||||
final String? hubIdentifier;
|
||||
@JsonKey(fromJson: _flexibleIntOrZero)
|
||||
@JsonKey(fromJson: flexibleIntOrZero)
|
||||
final int size;
|
||||
@JsonKey(fromJson: flexibleBool)
|
||||
final bool more;
|
||||
|
||||
@@ -18,7 +18,7 @@ PlexRoleDto _$PlexRoleDtoFromJson(Map<String, dynamic> json) => PlexRoleDto(
|
||||
|
||||
PlexMediaVersionDto _$PlexMediaVersionDtoFromJson(Map<String, dynamic> json) =>
|
||||
PlexMediaVersionDto(
|
||||
id: _flexibleIntOrZero(json['id']),
|
||||
id: flexibleIntOrZero(json['id']),
|
||||
videoResolution: readStringField(json, 'videoResolution') as String?,
|
||||
videoCodec: readStringField(json, 'videoCodec') as String?,
|
||||
bitrate: flexibleInt(json['bitrate']),
|
||||
@@ -73,7 +73,7 @@ PlexHubDto _$PlexHubDtoFromJson(Map<String, dynamic> json) => PlexHubDto(
|
||||
title: _hubTitleFromJson(json['title']),
|
||||
type: json['type'] as String? ?? 'hub',
|
||||
hubIdentifier: json['hubIdentifier'] as String?,
|
||||
size: _flexibleIntOrZero(json['size']),
|
||||
size: flexibleIntOrZero(json['size']),
|
||||
more: flexibleBool(json['more']),
|
||||
items: _hubItemsFromJson(_readHubItems(json, 'items')),
|
||||
);
|
||||
|
||||
@@ -8,6 +8,10 @@ int? flexibleInt(Object? v) => switch (v) {
|
||||
_ => null,
|
||||
};
|
||||
|
||||
int flexibleIntOrZero(Object? v) => flexibleInt(v) ?? 0;
|
||||
|
||||
String stringOrEmpty(Object? v) => (v ?? '').toString();
|
||||
|
||||
/// Parse a value that may be [bool], [int] (0/1), or [String] ('1'/'true'/'false') to [bool].
|
||||
/// Returns `false` for `null` or unrecognised values.
|
||||
/// Handles Plex API responses where boolean fields may arrive as integers.
|
||||
|
||||
@@ -33,6 +33,21 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
test('flexibleIntOrZero defaults unsupported values to zero', () {
|
||||
expect(flexibleIntOrZero(3.9), 3);
|
||||
expect(flexibleIntOrZero('42'), 42);
|
||||
expect(flexibleIntOrZero(null), 0);
|
||||
expect(flexibleIntOrZero('bad'), 0);
|
||||
expect(flexibleIntOrZero(true), 0);
|
||||
});
|
||||
|
||||
test('stringOrEmpty stringifies values and defaults null', () {
|
||||
expect(stringOrEmpty('value'), 'value');
|
||||
expect(stringOrEmpty(42), '42');
|
||||
expect(stringOrEmpty(true), 'true');
|
||||
expect(stringOrEmpty(null), '');
|
||||
});
|
||||
|
||||
group('flexibleBool', () {
|
||||
test('returns bool as-is', () {
|
||||
expect(flexibleBool(true), isTrue);
|
||||
|
||||
Reference in New Issue
Block a user