< Summary - Jellyfin

Information
Class: Jellyfin.Server.Implementations.Item.BaseItemMapper
Assembly: Jellyfin.Server.Implementations
File(s): /srv/git/jellyfin/Jellyfin.Server.Implementations/Item/BaseItemMapper.cs
Line coverage
81%
Covered lines: 217
Uncovered lines: 49
Coverable lines: 266
Total lines: 506
Line coverage: 81.5%
Branch coverage
54%
Covered branches: 91
Total branches: 168
Branch coverage: 54.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/8/2026 - 12:15:13 AM Line coverage: 71.7% (183/255) Branch coverage: 54.7% (92/168) Total lines: 4915/20/2026 - 12:15:44 AM Line coverage: 71.7% (183/255) Branch coverage: 48.2% (81/168) Total lines: 4917/6/2026 - 12:16:28 AM Line coverage: 71.7% (183/255) Branch coverage: 50.5% (85/168) Total lines: 4918/2/2026 - 12:17:28 AM Line coverage: 74.9% (191/255) Branch coverage: 50.6% (84/166) Total lines: 4918/3/2026 - 12:16:46 AM Line coverage: 75.9% (202/266) Branch coverage: 51.1% (86/168) Total lines: 5068/9/2026 - 12:16:58 AM Line coverage: 81.5% (217/266) Branch coverage: 54.1% (91/168) Total lines: 506 5/8/2026 - 12:15:13 AM Line coverage: 71.7% (183/255) Branch coverage: 54.7% (92/168) Total lines: 4915/20/2026 - 12:15:44 AM Line coverage: 71.7% (183/255) Branch coverage: 48.2% (81/168) Total lines: 4917/6/2026 - 12:16:28 AM Line coverage: 71.7% (183/255) Branch coverage: 50.5% (85/168) Total lines: 4918/2/2026 - 12:17:28 AM Line coverage: 74.9% (191/255) Branch coverage: 50.6% (84/166) Total lines: 4918/3/2026 - 12:16:46 AM Line coverage: 75.9% (202/266) Branch coverage: 51.1% (86/168) Total lines: 5068/9/2026 - 12:16:58 AM Line coverage: 81.5% (217/266) Branch coverage: 54.1% (91/168) Total lines: 506

Coverage delta

Coverage delta 7 -7

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
Map(...)51.21%1098284.11%
Map(...)51.61%846282.14%
MapImageFromEntity(...)0%7280%
MapImageToEntity(...)50%22100%
GetType(...)100%11100%
TypeRequiresDeserialization(...)100%11100%
DeserializeBaseItem(...)83.33%161270.58%
GetPathToSave(...)50%2266.66%

File(s)

/srv/git/jellyfin/Jellyfin.Server.Implementations/Item/BaseItemMapper.cs

#LineLine coverage
 1#pragma warning disable RS0030 // Do not use banned APIs
 2
 3using System;
 4using System.Collections.Concurrent;
 5using System.Linq;
 6using System.Reflection;
 7using System.Text;
 8using System.Text.Json;
 9using Jellyfin.Database.Implementations.Entities;
 10using Jellyfin.Extensions;
 11using Jellyfin.Extensions.Json;
 12using MediaBrowser.Common;
 13using MediaBrowser.Controller;
 14using MediaBrowser.Controller.Entities;
 15using MediaBrowser.Controller.Entities.Audio;
 16using MediaBrowser.Controller.Entities.TV;
 17using MediaBrowser.Controller.LiveTv;
 18using MediaBrowser.Model.Entities;
 19using MediaBrowser.Model.LiveTv;
 20using Microsoft.Extensions.Logging;
 21using BaseItemDto = MediaBrowser.Controller.Entities.BaseItem;
 22using BaseItemEntity = Jellyfin.Database.Implementations.Entities.BaseItemEntity;
 23
 24namespace Jellyfin.Server.Implementations.Item;
 25
 26/// <summary>
 27/// Handles mapping between BaseItemEntity (database) and BaseItemDto (domain) objects.
 28/// </summary>
 29public static class BaseItemMapper
 30{
 31    /// <summary>
 32    /// This holds all the types in the running assemblies
 33    /// so that we can de-serialize properly when we don't have strong types.
 34    /// </summary>
 235    private static readonly ConcurrentDictionary<string, Type?> _typeMap = new ConcurrentDictionary<string, Type?>();
 36
 37    /// <summary>
 38    /// Maps a Entity to the DTO.
 39    /// </summary>
 40    /// <param name="entity">The entity.</param>
 41    /// <param name="dto">The dto base instance.</param>
 42    /// <param name="appHost">The Application server Host.</param>
 43    /// <returns>The dto to map.</returns>
 44    public static BaseItemDto Map(BaseItemEntity entity, BaseItemDto dto, IServerApplicationHost? appHost)
 45    {
 8846        dto.Id = entity.Id;
 8847        dto.ParentId = entity.ParentId.GetValueOrDefault();
 8848        dto.Path = appHost?.ExpandVirtualPath(entity.Path) ?? entity.Path;
 8849        dto.EndDate = entity.EndDate;
 8850        dto.CommunityRating = entity.CommunityRating;
 8851        dto.CustomRating = entity.CustomRating;
 8852        dto.IndexNumber = entity.IndexNumber;
 8853        dto.IsLocked = entity.IsLocked;
 8854        dto.Name = entity.Name;
 8855        dto.OfficialRating = entity.OfficialRating;
 8856        dto.Overview = entity.Overview;
 8857        dto.ParentIndexNumber = entity.ParentIndexNumber;
 8858        dto.PremiereDate = entity.PremiereDate;
 8859        dto.ProductionYear = entity.ProductionYear;
 8860        dto.SortName = entity.SortName;
 8861        dto.ForcedSortName = entity.ForcedSortName;
 8862        dto.RunTimeTicks = entity.RunTimeTicks;
 8863        dto.PreferredMetadataLanguage = entity.PreferredMetadataLanguage;
 8864        dto.PreferredMetadataCountryCode = entity.PreferredMetadataCountryCode;
 8865        dto.IsInMixedFolder = entity.IsInMixedFolder;
 8866        dto.InheritedParentalRatingValue = entity.InheritedParentalRatingValue;
 8867        dto.InheritedParentalRatingSubValue = entity.InheritedParentalRatingSubValue;
 8868        dto.CriticRating = entity.CriticRating;
 8869        dto.PresentationUniqueKey = entity.PresentationUniqueKey;
 8870        dto.OriginalTitle = entity.OriginalTitle;
 8871        dto.OriginalLanguage = entity.OriginalLanguage;
 8872        dto.Album = entity.Album;
 8873        dto.LUFS = entity.LUFS;
 8874        dto.NormalizationGain = entity.NormalizationGain;
 8875        dto.IsVirtualItem = entity.IsVirtualItem;
 8876        dto.ExternalSeriesId = entity.ExternalSeriesId;
 8877        dto.Tagline = entity.Tagline;
 8878        dto.TotalBitrate = entity.TotalBitrate;
 8879        dto.ExternalId = entity.ExternalId;
 8880        dto.Size = entity.Size;
 8881        dto.Genres = string.IsNullOrWhiteSpace(entity.Genres) ? [] : entity.Genres.Split('|');
 8882        dto.DateCreated = entity.DateCreated ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
 8883        dto.DateModified = entity.DateModified ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
 8884        dto.ChannelId = entity.ChannelId ?? Guid.Empty;
 8885        dto.DateLastRefreshed = entity.DateLastRefreshed ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
 8886        dto.DateLastSaved = entity.DateLastSaved ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
 8887        dto.OwnerId = entity.OwnerId ?? Guid.Empty;
 8888        dto.Width = entity.Width.GetValueOrDefault();
 8889        dto.Height = entity.Height.GetValueOrDefault();
 8890        dto.UserData = entity.UserData;
 91
 8892        if (entity.Provider is not null)
 93        {
 8794            dto.ProviderIds = entity.Provider.ToDictionary(e => e.ProviderId, e => e.ProviderValue);
 95        }
 96
 8897        if (entity.ExtraType is not null)
 98        {
 099            dto.ExtraType = (ExtraType)entity.ExtraType;
 100        }
 101
 88102        if (entity.LockedFields is not null)
 103        {
 87104            dto.LockedFields = entity.LockedFields?.Select(e => (MetadataField)e.Id).ToArray() ?? [];
 105        }
 106
 88107        if (entity.Audio is not null)
 108        {
 0109            dto.Audio = (ProgramAudio)entity.Audio;
 110        }
 111
 88112        dto.ProductionLocations = entity.ProductionLocations?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? [];
 88113        dto.Studios = entity.Studios?.Split('|') ?? [];
 88114        dto.Tags = string.IsNullOrWhiteSpace(entity.Tags) ? [] : entity.Tags.Split('|');
 115
 88116        if (dto is IHasProgramAttributes hasProgramAttributes)
 117        {
 0118            hasProgramAttributes.IsMovie = entity.IsMovie;
 0119            hasProgramAttributes.IsSeries = entity.IsSeries;
 0120            hasProgramAttributes.EpisodeTitle = entity.EpisodeTitle;
 0121            hasProgramAttributes.IsRepeat = entity.IsRepeat;
 122        }
 123
 88124        if (dto is LiveTvChannel liveTvChannel)
 125        {
 0126            liveTvChannel.ServiceName = entity.ExternalServiceId;
 127        }
 128
 88129        if (dto is Trailer trailer)
 130        {
 0131            trailer.TrailerTypes = entity.TrailerTypes?.Select(e => (TrailerType)e.Id).ToArray() ?? [];
 132        }
 133
 88134        if (dto is Video video)
 135        {
 3136            video.PrimaryVersionId = entity.PrimaryVersionId;
 137
 138            // The LinkedChildren table is the source of truth for version links
 3139            if (entity.LinkedChildEntities is not null)
 140            {
 2141                video.LinkedAlternateVersions = entity.LinkedChildEntities
 2142                    // LocalAlternateVersion links belong to Video.LocalAlternateVersions, not here
 2143                    .Where(e => e.ChildType == Database.Implementations.Entities.LinkedChildType.LinkedAlternateVersion)
 2144                    .OrderBy(e => e.SortOrder)
 2145                    .Select(e => new LinkedChild
 2146                    {
 2147                        ItemId = e.ChildId,
 2148                        Type = (MediaBrowser.Controller.Entities.LinkedChildType)e.ChildType
 2149                    })
 2150                    .ToArray();
 151            }
 152        }
 153
 88154        if (dto is IHasSeries hasSeriesName)
 155        {
 0156            hasSeriesName.SeriesName = entity.SeriesName;
 0157            hasSeriesName.SeriesId = entity.SeriesId.GetValueOrDefault();
 0158            hasSeriesName.SeriesPresentationUniqueKey = entity.SeriesPresentationUniqueKey;
 159        }
 160
 88161        if (dto is Episode episode)
 162        {
 0163            episode.SeasonName = entity.SeasonName;
 0164            episode.SeasonId = entity.SeasonId.GetValueOrDefault();
 165        }
 166
 88167        if (dto is IHasArtist hasArtists)
 168        {
 0169            hasArtists.Artists = entity.Artists?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? [];
 170        }
 171
 88172        if (dto is IHasAlbumArtist hasAlbumArtists)
 173        {
 0174            hasAlbumArtists.AlbumArtists = entity.AlbumArtists?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? [];
 175        }
 176
 88177        if (dto is LiveTvProgram program)
 178        {
 0179            program.ShowId = entity.ShowId;
 180        }
 181
 88182        if (entity.Images is not null)
 183        {
 87184            dto.ImageInfos = entity.Images.Select(e => MapImageFromEntity(e, appHost)).ToArray();
 185        }
 186
 88187        if (dto is IHasStartDate hasStartDate)
 188        {
 0189            hasStartDate.StartDate = entity.StartDate.GetValueOrDefault();
 190        }
 191
 192        // Fields that are present in the DB but are never actually used
 193        // dto.UnratedType = entity.UnratedType;
 194        // dto.TopParentId = entity.TopParentId;
 195        // dto.CleanName = entity.CleanName;
 196        // dto.UserDataKey = entity.UserDataKey;
 197
 88198        if (dto is Folder folder)
 199        {
 85200            folder.DateLastMediaAdded = entity.DateLastMediaAdded ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKin
 85201            if (entity.LinkedChildEntities is not null)
 202            {
 85203                folder.LinkedChildren = entity.LinkedChildEntities
 85204                    .OrderBy(e => e.SortOrder)
 85205                    .Select(e => new LinkedChild
 85206                    {
 85207                        ItemId = e.ChildId,
 85208                        Type = (MediaBrowser.Controller.Entities.LinkedChildType)e.ChildType
 85209                    })
 85210                    .ToArray();
 211            }
 212        }
 213
 88214        return dto;
 215    }
 216
 217    /// <summary>
 218    /// Maps a DTO to a database entity.
 219    /// </summary>
 220    /// <param name="dto">The DTO.</param>
 221    /// <param name="appHost">The application host for path resolution.</param>
 222    /// <returns>The database entity.</returns>
 223    public static BaseItemEntity Map(BaseItemDto dto, IServerApplicationHost appHost)
 224    {
 131225        var dtoType = dto.GetType();
 131226        var entity = new BaseItemEntity()
 131227        {
 131228            Type = dtoType.ToString(),
 131229            Id = dto.Id
 131230        };
 231
 131232        if (TypeRequiresDeserialization(dtoType))
 233        {
 104234            entity.Data = JsonSerializer.Serialize(dto, dtoType, JsonDefaults.Options);
 235        }
 236
 131237        entity.ParentId = !dto.ParentId.IsEmpty() ? dto.ParentId : null;
 131238        entity.Path = GetPathToSave(dto.Path, appHost);
 131239        entity.EndDate = dto.EndDate;
 131240        entity.CommunityRating = dto.CommunityRating;
 131241        entity.CustomRating = dto.CustomRating;
 131242        entity.IndexNumber = dto.IndexNumber;
 131243        entity.IsLocked = dto.IsLocked;
 131244        entity.Name = dto.Name;
 131245        entity.CleanName = dto.Name.GetCleanValue();
 131246        entity.OfficialRating = dto.OfficialRating;
 131247        entity.Overview = dto.Overview;
 131248        entity.ParentIndexNumber = dto.ParentIndexNumber;
 131249        entity.PremiereDate = dto.PremiereDate;
 131250        entity.ProductionYear = dto.ProductionYear;
 131251        entity.SortName = dto.SortName;
 131252        entity.ForcedSortName = dto.ForcedSortName;
 131253        entity.RunTimeTicks = dto.RunTimeTicks;
 131254        entity.PreferredMetadataLanguage = dto.PreferredMetadataLanguage;
 131255        entity.PreferredMetadataCountryCode = dto.PreferredMetadataCountryCode;
 131256        entity.IsInMixedFolder = dto.IsInMixedFolder;
 131257        entity.InheritedParentalRatingValue = dto.InheritedParentalRatingValue;
 131258        entity.InheritedParentalRatingSubValue = dto.InheritedParentalRatingSubValue;
 131259        entity.CriticRating = dto.CriticRating;
 131260        entity.PresentationUniqueKey = dto.PresentationUniqueKey;
 131261        entity.OriginalTitle = dto.OriginalTitle;
 131262        entity.OriginalLanguage = dto.OriginalLanguage;
 131263        entity.Album = dto.Album;
 131264        entity.LUFS = dto.LUFS;
 131265        entity.NormalizationGain = dto.NormalizationGain;
 131266        entity.IsVirtualItem = dto.IsVirtualItem;
 131267        entity.ExternalSeriesId = dto.ExternalSeriesId;
 131268        entity.Tagline = dto.Tagline;
 131269        entity.TotalBitrate = dto.TotalBitrate;
 131270        entity.ExternalId = dto.ExternalId;
 131271        entity.Size = dto.Size;
 131272        entity.Genres = string.Join('|', dto.Genres.Distinct(StringComparer.OrdinalIgnoreCase));
 131273        entity.DateCreated = dto.DateCreated == DateTime.MinValue ? null : dto.DateCreated;
 131274        entity.DateModified = dto.DateModified == DateTime.MinValue ? null : dto.DateModified;
 131275        entity.ChannelId = dto.ChannelId;
 131276        entity.DateLastRefreshed = dto.DateLastRefreshed == DateTime.MinValue ? null : dto.DateLastRefreshed;
 131277        entity.DateLastSaved = dto.DateLastSaved == DateTime.MinValue ? null : dto.DateLastSaved;
 131278        entity.OwnerId = dto.OwnerId == Guid.Empty ? null : dto.OwnerId;
 131279        entity.Width = dto.Width;
 131280        entity.Height = dto.Height;
 131281        entity.Provider = dto.ProviderIds.Select(e => new BaseItemProvider()
 131282        {
 131283            Item = entity,
 131284            ProviderId = e.Key,
 131285            ProviderValue = e.Value
 131286        }).ToList();
 287
 131288        if (dto.Audio.HasValue)
 289        {
 0290            entity.Audio = (ProgramAudioEntity)dto.Audio;
 291        }
 292
 131293        if (dto.ExtraType.HasValue)
 294        {
 0295            entity.ExtraType = (BaseItemExtraType)dto.ExtraType;
 296        }
 297
 131298        entity.ProductionLocations = dto.ProductionLocations is not null ? string.Join('|', dto.ProductionLocations.Wher
 131299        entity.Studios = dto.Studios is not null ? string.Join('|', dto.Studios.Distinct(StringComparer.OrdinalIgnoreCas
 131300        entity.Tags = dto.Tags is not null ? string.Join('|', dto.Tags.Distinct(StringComparer.OrdinalIgnoreCase)) : nul
 131301        entity.LockedFields = dto.LockedFields is not null ? dto.LockedFields
 131302            .Select(e => new BaseItemMetadataField()
 131303            {
 131304                Id = (int)e,
 131305                Item = entity,
 131306                ItemId = entity.Id
 131307            })
 131308            .ToArray() : null;
 309
 131310        if (dto is IHasProgramAttributes hasProgramAttributes)
 311        {
 0312            entity.IsMovie = hasProgramAttributes.IsMovie;
 0313            entity.IsSeries = hasProgramAttributes.IsSeries;
 0314            entity.EpisodeTitle = hasProgramAttributes.EpisodeTitle;
 0315            entity.IsRepeat = hasProgramAttributes.IsRepeat;
 316        }
 317
 131318        if (dto is LiveTvChannel liveTvChannel)
 319        {
 0320            entity.ExternalServiceId = liveTvChannel.ServiceName;
 321        }
 322
 131323        if (dto is Video video)
 324        {
 0325            entity.PrimaryVersionId = video.PrimaryVersionId;
 326        }
 327
 131328        if (dto is IHasSeries hasSeriesName)
 329        {
 5330            entity.SeriesName = hasSeriesName.SeriesName;
 5331            entity.SeriesId = hasSeriesName.SeriesId;
 5332            entity.SeriesPresentationUniqueKey = hasSeriesName.SeriesPresentationUniqueKey;
 333        }
 334
 131335        if (dto is Episode episode)
 336        {
 0337            entity.SeasonName = episode.SeasonName;
 0338            entity.SeasonId = episode.SeasonId;
 339        }
 340
 131341        if (dto is IHasArtist hasArtists)
 342        {
 0343            entity.Artists = hasArtists.Artists is not null ? string.Join('|', hasArtists.Artists.Distinct(StringCompare
 344        }
 345
 131346        if (dto is IHasAlbumArtist hasAlbumArtists)
 347        {
 0348            entity.AlbumArtists = hasAlbumArtists.AlbumArtists is not null ? string.Join('|', hasAlbumArtists.AlbumArtis
 349        }
 350
 131351        if (dto is LiveTvProgram program)
 352        {
 0353            entity.ShowId = program.ShowId;
 354        }
 355
 131356        if (dto.ImageInfos is not null)
 357        {
 131358            entity.Images = dto.ImageInfos.Select(f => MapImageToEntity(dto.Id, f)).ToArray();
 359        }
 360
 131361        if (dto is Trailer trailer)
 362        {
 0363            entity.TrailerTypes = trailer.TrailerTypes?.Select(e => new BaseItemTrailerType()
 0364            {
 0365                Id = (int)e,
 0366                Item = entity,
 0367                ItemId = entity.Id
 0368            }).ToArray() ?? [];
 369        }
 370
 131371        entity.MediaType = dto.MediaType.ToString();
 131372        if (dto is IHasStartDate hasStartDate)
 373        {
 0374            entity.StartDate = hasStartDate.StartDate;
 375        }
 376
 131377        entity.UnratedType = dto.GetBlockUnratedType().ToString();
 378
 379        // Fields that are present in the DB but are never actually used
 380        // dto.UserDataKey = entity.UserDataKey;
 381
 131382        if (dto is Folder folder)
 383        {
 126384            entity.DateLastMediaAdded = folder.DateLastMediaAdded == DateTime.MinValue ? null : folder.DateLastMediaAdde
 126385            entity.IsFolder = folder.IsFolder;
 386        }
 387
 131388        return entity;
 389    }
 390
 391    /// <summary>
 392    /// Maps a database image entity to a domain image info.
 393    /// </summary>
 394    /// <param name="e">The database image entity.</param>
 395    /// <param name="appHost">The application host.</param>
 396    /// <returns>The mapped image info.</returns>
 397    public static ItemImageInfo MapImageFromEntity(BaseItemImageInfo e, IServerApplicationHost? appHost)
 398    {
 0399        return new ItemImageInfo()
 0400        {
 0401            Path = appHost?.ExpandVirtualPath(e.Path) ?? e.Path,
 0402            BlurHash = e.Blurhash is null ? null : Encoding.UTF8.GetString(e.Blurhash),
 0403            DateModified = e.DateModified ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc),
 0404            Height = e.Height,
 0405            Width = e.Width,
 0406            Type = (ImageType)e.ImageType
 0407        };
 408    }
 409
 410    /// <summary>
 411    /// Maps a domain image info to a database image entity.
 412    /// </summary>
 413    /// <param name="baseItemId">The parent item ID.</param>
 414    /// <param name="e">The image info to map.</param>
 415    /// <returns>The mapped database entity.</returns>
 416    public static BaseItemImageInfo MapImageToEntity(Guid baseItemId, ItemImageInfo e)
 417    {
 5418        return new BaseItemImageInfo()
 5419        {
 5420            ItemId = baseItemId,
 5421            Id = Guid.NewGuid(),
 5422            Path = e.Path,
 5423            Blurhash = e.BlurHash is null ? null : Encoding.UTF8.GetBytes(e.BlurHash),
 5424            DateModified = e.DateModified,
 5425            Height = e.Height,
 5426            Width = e.Width,
 5427            ImageType = (ImageInfoImageType)e.Type,
 5428            Item = null!
 5429        };
 430    }
 431
 432    /// <summary>
 433    /// Gets the type from a type name string.
 434    /// </summary>
 435    /// <param name="typeName">The type name.</param>
 436    /// <returns>The resolved type, or null.</returns>
 437    public static Type? GetType(string typeName)
 438    {
 177439        ArgumentException.ThrowIfNullOrEmpty(typeName);
 440
 177441        return _typeMap.GetOrAdd(typeName, k => AppDomain.CurrentDomain.GetAssemblies()
 177442            .Select(a => a.GetType(k))
 177443            .FirstOrDefault(t => t is not null));
 444    }
 445
 446    /// <summary>
 447    /// Checks whether a type requires JSON deserialization.
 448    /// </summary>
 449    /// <param name="type">The type to check.</param>
 450    /// <returns>True if the type requires deserialization.</returns>
 451    public static bool TypeRequiresDeserialization(Type type)
 452    {
 219453        return type.GetCustomAttribute<RequiresSourceSerialisationAttribute>() == null;
 454    }
 455
 456    /// <summary>
 457    /// Deserializes a BaseItemEntity and sets all properties.
 458    /// </summary>
 459    /// <param name="baseItemEntity">The DB entity.</param>
 460    /// <param name="logger">Logger.</param>
 461    /// <param name="appHost">The application server Host.</param>
 462    /// <param name="skipDeserialization">If only mapping should be processed.</param>
 463    /// <returns>A mapped BaseItem, or null if the item type is unknown.</returns>
 464    public static BaseItemDto? DeserializeBaseItem(BaseItemEntity baseItemEntity, ILogger logger, IServerApplicationHost
 465    {
 90466        var type = GetType(baseItemEntity.Type);
 90467        if (type is null)
 468        {
 2469            logger.LogWarning(
 2470                "Skipping item {ItemId} with unknown type '{ItemType}'. This may indicate a removed plugin or database c
 2471                baseItemEntity.Id,
 2472                baseItemEntity.Type);
 2473            return null;
 474        }
 475
 88476        BaseItemDto? dto = null;
 88477        if (TypeRequiresDeserialization(type) && baseItemEntity.Data is not null && !skipDeserialization)
 478        {
 479            try
 480            {
 13481                dto = JsonSerializer.Deserialize(baseItemEntity.Data, type, JsonDefaults.Options) as BaseItemDto;
 13482            }
 0483            catch (JsonException ex)
 484            {
 0485                logger.LogError(ex, "Error deserializing item with JSON: {Data}", baseItemEntity.Data);
 0486            }
 487        }
 488
 88489        if (dto is null)
 490        {
 75491            dto = Activator.CreateInstance(type) as BaseItemDto ?? throw new InvalidOperationException("Cannot deseriali
 492        }
 493
 88494        return Map(baseItemEntity, dto, appHost);
 495    }
 496
 497    private static string? GetPathToSave(string path, IServerApplicationHost appHost)
 498    {
 131499        if (path is null)
 500        {
 15501            return null;
 502        }
 503
 116504        return appHost.ReverseVirtualPath(path);
 505    }
 506}