< 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/4/2026 - 12:15:16 AM Line coverage: 71.5% (181/253) Branch coverage: 54.7% (92/168) Total lines: 4895/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/4/2026 - 12:15:16 AM Line coverage: 71.5% (181/253) Branch coverage: 54.7% (92/168) Total lines: 4895/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    {
 8746        dto.Id = entity.Id;
 8747        dto.ParentId = entity.ParentId.GetValueOrDefault();
 8748        dto.Path = appHost?.ExpandVirtualPath(entity.Path) ?? entity.Path;
 8749        dto.EndDate = entity.EndDate;
 8750        dto.CommunityRating = entity.CommunityRating;
 8751        dto.CustomRating = entity.CustomRating;
 8752        dto.IndexNumber = entity.IndexNumber;
 8753        dto.IsLocked = entity.IsLocked;
 8754        dto.Name = entity.Name;
 8755        dto.OfficialRating = entity.OfficialRating;
 8756        dto.Overview = entity.Overview;
 8757        dto.ParentIndexNumber = entity.ParentIndexNumber;
 8758        dto.PremiereDate = entity.PremiereDate;
 8759        dto.ProductionYear = entity.ProductionYear;
 8760        dto.SortName = entity.SortName;
 8761        dto.ForcedSortName = entity.ForcedSortName;
 8762        dto.RunTimeTicks = entity.RunTimeTicks;
 8763        dto.PreferredMetadataLanguage = entity.PreferredMetadataLanguage;
 8764        dto.PreferredMetadataCountryCode = entity.PreferredMetadataCountryCode;
 8765        dto.IsInMixedFolder = entity.IsInMixedFolder;
 8766        dto.InheritedParentalRatingValue = entity.InheritedParentalRatingValue;
 8767        dto.InheritedParentalRatingSubValue = entity.InheritedParentalRatingSubValue;
 8768        dto.CriticRating = entity.CriticRating;
 8769        dto.PresentationUniqueKey = entity.PresentationUniqueKey;
 8770        dto.OriginalTitle = entity.OriginalTitle;
 8771        dto.OriginalLanguage = entity.OriginalLanguage;
 8772        dto.Album = entity.Album;
 8773        dto.LUFS = entity.LUFS;
 8774        dto.NormalizationGain = entity.NormalizationGain;
 8775        dto.IsVirtualItem = entity.IsVirtualItem;
 8776        dto.ExternalSeriesId = entity.ExternalSeriesId;
 8777        dto.Tagline = entity.Tagline;
 8778        dto.TotalBitrate = entity.TotalBitrate;
 8779        dto.ExternalId = entity.ExternalId;
 8780        dto.Size = entity.Size;
 8781        dto.Genres = string.IsNullOrWhiteSpace(entity.Genres) ? [] : entity.Genres.Split('|');
 8782        dto.DateCreated = entity.DateCreated ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
 8783        dto.DateModified = entity.DateModified ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
 8784        dto.ChannelId = entity.ChannelId ?? Guid.Empty;
 8785        dto.DateLastRefreshed = entity.DateLastRefreshed ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
 8786        dto.DateLastSaved = entity.DateLastSaved ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc);
 8787        dto.OwnerId = entity.OwnerId ?? Guid.Empty;
 8788        dto.Width = entity.Width.GetValueOrDefault();
 8789        dto.Height = entity.Height.GetValueOrDefault();
 8790        dto.UserData = entity.UserData;
 91
 8792        if (entity.Provider is not null)
 93        {
 8694            dto.ProviderIds = entity.Provider.ToDictionary(e => e.ProviderId, e => e.ProviderValue);
 95        }
 96
 8797        if (entity.ExtraType is not null)
 98        {
 099            dto.ExtraType = (ExtraType)entity.ExtraType;
 100        }
 101
 87102        if (entity.LockedFields is not null)
 103        {
 86104            dto.LockedFields = entity.LockedFields?.Select(e => (MetadataField)e.Id).ToArray() ?? [];
 105        }
 106
 87107        if (entity.Audio is not null)
 108        {
 0109            dto.Audio = (ProgramAudio)entity.Audio;
 110        }
 111
 87112        dto.ProductionLocations = entity.ProductionLocations?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? [];
 87113        dto.Studios = entity.Studios?.Split('|') ?? [];
 87114        dto.Tags = string.IsNullOrWhiteSpace(entity.Tags) ? [] : entity.Tags.Split('|');
 115
 87116        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
 87124        if (dto is LiveTvChannel liveTvChannel)
 125        {
 0126            liveTvChannel.ServiceName = entity.ExternalServiceId;
 127        }
 128
 87129        if (dto is Trailer trailer)
 130        {
 0131            trailer.TrailerTypes = entity.TrailerTypes?.Select(e => (TrailerType)e.Id).ToArray() ?? [];
 132        }
 133
 87134        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
 87154        if (dto is IHasSeries hasSeriesName)
 155        {
 0156            hasSeriesName.SeriesName = entity.SeriesName;
 0157            hasSeriesName.SeriesId = entity.SeriesId.GetValueOrDefault();
 0158            hasSeriesName.SeriesPresentationUniqueKey = entity.SeriesPresentationUniqueKey;
 159        }
 160
 87161        if (dto is Episode episode)
 162        {
 0163            episode.SeasonName = entity.SeasonName;
 0164            episode.SeasonId = entity.SeasonId.GetValueOrDefault();
 165        }
 166
 87167        if (dto is IHasArtist hasArtists)
 168        {
 0169            hasArtists.Artists = entity.Artists?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? [];
 170        }
 171
 87172        if (dto is IHasAlbumArtist hasAlbumArtists)
 173        {
 0174            hasAlbumArtists.AlbumArtists = entity.AlbumArtists?.Split('|', StringSplitOptions.RemoveEmptyEntries) ?? [];
 175        }
 176
 87177        if (dto is LiveTvProgram program)
 178        {
 0179            program.ShowId = entity.ShowId;
 180        }
 181
 87182        if (entity.Images is not null)
 183        {
 86184            dto.ImageInfos = entity.Images.Select(e => MapImageFromEntity(e, appHost)).ToArray();
 185        }
 186
 87187        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
 87198        if (dto is Folder folder)
 199        {
 84200            folder.DateLastMediaAdded = entity.DateLastMediaAdded ?? DateTime.SpecifyKind(DateTime.MinValue, DateTimeKin
 84201            if (entity.LinkedChildEntities is not null)
 202            {
 84203                folder.LinkedChildren = entity.LinkedChildEntities
 84204                    .OrderBy(e => e.SortOrder)
 84205                    .Select(e => new LinkedChild
 84206                    {
 84207                        ItemId = e.ChildId,
 84208                        Type = (MediaBrowser.Controller.Entities.LinkedChildType)e.ChildType
 84209                    })
 84210                    .ToArray();
 211            }
 212        }
 213
 87214        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    {
 130225        var dtoType = dto.GetType();
 130226        var entity = new BaseItemEntity()
 130227        {
 130228            Type = dtoType.ToString(),
 130229            Id = dto.Id
 130230        };
 231
 130232        if (TypeRequiresDeserialization(dtoType))
 233        {
 103234            entity.Data = JsonSerializer.Serialize(dto, dtoType, JsonDefaults.Options);
 235        }
 236
 130237        entity.ParentId = !dto.ParentId.IsEmpty() ? dto.ParentId : null;
 130238        entity.Path = GetPathToSave(dto.Path, appHost);
 130239        entity.EndDate = dto.EndDate;
 130240        entity.CommunityRating = dto.CommunityRating;
 130241        entity.CustomRating = dto.CustomRating;
 130242        entity.IndexNumber = dto.IndexNumber;
 130243        entity.IsLocked = dto.IsLocked;
 130244        entity.Name = dto.Name;
 130245        entity.CleanName = dto.Name.GetCleanValue();
 130246        entity.OfficialRating = dto.OfficialRating;
 130247        entity.Overview = dto.Overview;
 130248        entity.ParentIndexNumber = dto.ParentIndexNumber;
 130249        entity.PremiereDate = dto.PremiereDate;
 130250        entity.ProductionYear = dto.ProductionYear;
 130251        entity.SortName = dto.SortName;
 130252        entity.ForcedSortName = dto.ForcedSortName;
 130253        entity.RunTimeTicks = dto.RunTimeTicks;
 130254        entity.PreferredMetadataLanguage = dto.PreferredMetadataLanguage;
 130255        entity.PreferredMetadataCountryCode = dto.PreferredMetadataCountryCode;
 130256        entity.IsInMixedFolder = dto.IsInMixedFolder;
 130257        entity.InheritedParentalRatingValue = dto.InheritedParentalRatingValue;
 130258        entity.InheritedParentalRatingSubValue = dto.InheritedParentalRatingSubValue;
 130259        entity.CriticRating = dto.CriticRating;
 130260        entity.PresentationUniqueKey = dto.PresentationUniqueKey;
 130261        entity.OriginalTitle = dto.OriginalTitle;
 130262        entity.OriginalLanguage = dto.OriginalLanguage;
 130263        entity.Album = dto.Album;
 130264        entity.LUFS = dto.LUFS;
 130265        entity.NormalizationGain = dto.NormalizationGain;
 130266        entity.IsVirtualItem = dto.IsVirtualItem;
 130267        entity.ExternalSeriesId = dto.ExternalSeriesId;
 130268        entity.Tagline = dto.Tagline;
 130269        entity.TotalBitrate = dto.TotalBitrate;
 130270        entity.ExternalId = dto.ExternalId;
 130271        entity.Size = dto.Size;
 130272        entity.Genres = string.Join('|', dto.Genres.Distinct(StringComparer.OrdinalIgnoreCase));
 130273        entity.DateCreated = dto.DateCreated == DateTime.MinValue ? null : dto.DateCreated;
 130274        entity.DateModified = dto.DateModified == DateTime.MinValue ? null : dto.DateModified;
 130275        entity.ChannelId = dto.ChannelId;
 130276        entity.DateLastRefreshed = dto.DateLastRefreshed == DateTime.MinValue ? null : dto.DateLastRefreshed;
 130277        entity.DateLastSaved = dto.DateLastSaved == DateTime.MinValue ? null : dto.DateLastSaved;
 130278        entity.OwnerId = dto.OwnerId == Guid.Empty ? null : dto.OwnerId;
 130279        entity.Width = dto.Width;
 130280        entity.Height = dto.Height;
 130281        entity.Provider = dto.ProviderIds.Select(e => new BaseItemProvider()
 130282        {
 130283            Item = entity,
 130284            ProviderId = e.Key,
 130285            ProviderValue = e.Value
 130286        }).ToList();
 287
 130288        if (dto.Audio.HasValue)
 289        {
 0290            entity.Audio = (ProgramAudioEntity)dto.Audio;
 291        }
 292
 130293        if (dto.ExtraType.HasValue)
 294        {
 0295            entity.ExtraType = (BaseItemExtraType)dto.ExtraType;
 296        }
 297
 130298        entity.ProductionLocations = dto.ProductionLocations is not null ? string.Join('|', dto.ProductionLocations.Wher
 130299        entity.Studios = dto.Studios is not null ? string.Join('|', dto.Studios.Distinct(StringComparer.OrdinalIgnoreCas
 130300        entity.Tags = dto.Tags is not null ? string.Join('|', dto.Tags.Distinct(StringComparer.OrdinalIgnoreCase)) : nul
 130301        entity.LockedFields = dto.LockedFields is not null ? dto.LockedFields
 130302            .Select(e => new BaseItemMetadataField()
 130303            {
 130304                Id = (int)e,
 130305                Item = entity,
 130306                ItemId = entity.Id
 130307            })
 130308            .ToArray() : null;
 309
 130310        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
 130318        if (dto is LiveTvChannel liveTvChannel)
 319        {
 0320            entity.ExternalServiceId = liveTvChannel.ServiceName;
 321        }
 322
 130323        if (dto is Video video)
 324        {
 0325            entity.PrimaryVersionId = video.PrimaryVersionId;
 326        }
 327
 130328        if (dto is IHasSeries hasSeriesName)
 329        {
 5330            entity.SeriesName = hasSeriesName.SeriesName;
 5331            entity.SeriesId = hasSeriesName.SeriesId;
 5332            entity.SeriesPresentationUniqueKey = hasSeriesName.SeriesPresentationUniqueKey;
 333        }
 334
 130335        if (dto is Episode episode)
 336        {
 0337            entity.SeasonName = episode.SeasonName;
 0338            entity.SeasonId = episode.SeasonId;
 339        }
 340
 130341        if (dto is IHasArtist hasArtists)
 342        {
 0343            entity.Artists = hasArtists.Artists is not null ? string.Join('|', hasArtists.Artists.Distinct(StringCompare
 344        }
 345
 130346        if (dto is IHasAlbumArtist hasAlbumArtists)
 347        {
 0348            entity.AlbumArtists = hasAlbumArtists.AlbumArtists is not null ? string.Join('|', hasAlbumArtists.AlbumArtis
 349        }
 350
 130351        if (dto is LiveTvProgram program)
 352        {
 0353            entity.ShowId = program.ShowId;
 354        }
 355
 130356        if (dto.ImageInfos is not null)
 357        {
 130358            entity.Images = dto.ImageInfos.Select(f => MapImageToEntity(dto.Id, f)).ToArray();
 359        }
 360
 130361        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
 130371        entity.MediaType = dto.MediaType.ToString();
 130372        if (dto is IHasStartDate hasStartDate)
 373        {
 0374            entity.StartDate = hasStartDate.StartDate;
 375        }
 376
 130377        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
 130382        if (dto is Folder folder)
 383        {
 125384            entity.DateLastMediaAdded = folder.DateLastMediaAdded == DateTime.MinValue ? null : folder.DateLastMediaAdde
 125385            entity.IsFolder = folder.IsFolder;
 386        }
 387
 130388        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    {
 175439        ArgumentException.ThrowIfNullOrEmpty(typeName);
 440
 175441        return _typeMap.GetOrAdd(typeName, k => AppDomain.CurrentDomain.GetAssemblies()
 175442            .Select(a => a.GetType(k))
 175443            .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    {
 217453        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    {
 89466        var type = GetType(baseItemEntity.Type);
 89467        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
 87476        BaseItemDto? dto = null;
 87477        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
 87489        if (dto is null)
 490        {
 74491            dto = Activator.CreateInstance(type) as BaseItemDto ?? throw new InvalidOperationException("Cannot deseriali
 492        }
 493
 87494        return Map(baseItemEntity, dto, appHost);
 495    }
 496
 497    private static string? GetPathToSave(string path, IServerApplicationHost appHost)
 498    {
 130499        if (path is null)
 500        {
 15501            return null;
 502        }
 503
 115504        return appHost.ReverseVirtualPath(path);
 505    }
 506}