diff --git a/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableConverterFactory.cs b/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableConverterFactory.cs
new file mode 100644
index 0000000000..5994ce9226
--- /dev/null
+++ b/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableConverterFactory.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace MediaBrowser.Common.Json.Converters
+{
+ ///
+ /// Json Omdb converter factory.
+ ///
+ ///
+ /// Remove when Omdb is moved to plugin.
+ ///
+ public class JsonOmdbNotAvailableConverterFactory : JsonConverterFactory
+ {
+ ///
+ public override bool CanConvert(Type typeToConvert)
+ {
+ return (typeToConvert.IsGenericType
+ && typeToConvert.GetGenericTypeDefinition() == typeof(Nullable<>)
+ && typeToConvert.GenericTypeArguments[0].IsValueType)
+ || typeToConvert == typeof(string);
+ }
+
+ ///
+ public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
+ {
+ if (typeToConvert == typeof(string))
+ {
+ return (JsonConverter)Activator.CreateInstance(typeof(JsonOmdbNotAvailableStringConverter));
+ }
+
+ var structType = typeToConvert.GenericTypeArguments[0];
+ return (JsonConverter)Activator.CreateInstance(typeof(JsonOmdbNotAvailableStructConverter<>).MakeGenericType(structType));
+ }
+ }
+}
diff --git a/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableStringConverter.cs b/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableStringConverter.cs
new file mode 100644
index 0000000000..2b343a5059
--- /dev/null
+++ b/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableStringConverter.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace MediaBrowser.Common.Json.Converters
+{
+ ///
+ /// Converts a string N/A to string.Empty.
+ ///
+ public class JsonOmdbNotAvailableStringConverter : JsonConverter
+ {
+ ///
+ public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ if (reader.TokenType == JsonTokenType.String)
+ {
+ var str = reader.GetString();
+ if (str != null && str.Equals("N/A", StringComparison.OrdinalIgnoreCase))
+ {
+ return null;
+ }
+ }
+
+ return JsonSerializer.Deserialize(ref reader, options);
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
+ {
+ JsonSerializer.Serialize(value, options);
+ }
+ }
+}
diff --git a/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableStructConverter.cs b/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableStructConverter.cs
new file mode 100644
index 0000000000..b9e67ce2de
--- /dev/null
+++ b/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableStructConverter.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace MediaBrowser.Common.Json.Converters
+{
+ ///
+ /// Converts a string N/A to string.Empty.
+ ///
+ /// The resulting type.
+ public class JsonOmdbNotAvailableStructConverter : JsonConverter
+ where T : struct
+ {
+ ///
+ public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ if (reader.TokenType == JsonTokenType.String)
+ {
+ var str = reader.GetString();
+ if (str != null && str.Equals("N/A", StringComparison.OrdinalIgnoreCase))
+ {
+ return null;
+ }
+ }
+
+ return JsonSerializer.Deserialize(ref reader, options);
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options)
+ {
+ JsonSerializer.Serialize(value, options);
+ }
+ }
+}
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
index 3c21629990..1ebd1b13a3 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
@@ -8,10 +8,12 @@ using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.Json;
+using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common;
using MediaBrowser.Common.Json;
+using MediaBrowser.Common.Json.Converters;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
@@ -467,74 +469,104 @@ namespace MediaBrowser.Providers.Plugins.Omdb
return string.Equals(lang, "en", StringComparison.OrdinalIgnoreCase);
}
- internal class SeasonRootObject
+ public class SeasonRootObject
{
+ [JsonConverter(typeof(JsonOmdbNotAvailableConverterFactory))]
public string Title { get; set; }
+ [JsonConverter(typeof(JsonOmdbNotAvailableConverterFactory))]
public string seriesID { get; set; }
- public int Season { get; set; }
+ [JsonConverter(typeof(JsonOmdbNotAvailableConverterFactory))]
+ public int? Season { get; set; }
+ [JsonConverter(typeof(JsonOmdbNotAvailableConverterFactory))]
public int? totalSeasons { get; set; }
public RootObject[] Episodes { get; set; }
+ [JsonConverter(typeof(JsonOmdbNotAvailableConverterFactory))]
public string Response { get; set; }
}
- internal class RootObject
+ public class RootObject
{
+ [JsonConverter(typeof(JsonOmdbNotAvailableConverterFactory))]
public string Title { get; set; }
+ [JsonConverter(typeof(JsonOmdbNotAvailableConverterFactory))]
public string Year { get; set; }
+ [JsonConverter(typeof(JsonOmdbNotAvailableConverterFactory))]
public string Rated { get; set; }
+ [JsonConverter(typeof(JsonOmdbNotAvailableConverterFactory))]
public string Released { get; set; }
+ [JsonConverter(typeof(JsonOmdbNotAvailableConverterFactory))]
public string Runtime { get; set; }
+ [JsonConverter(typeof(JsonOmdbNotAvailableConverterFactory))]
public string Genre { get; set; }
+ [JsonConverter(typeof(JsonOmdbNotAvailableConverterFactory))]
public string Director { get; set; }
+ [JsonConverter(typeof(JsonOmdbNotAvailableConverterFactory))]
public string Writer { get; set; }
+ [JsonConverter(typeof(JsonOmdbNotAvailableConverterFactory))]
public string Actors { get; set; }
+ [JsonConverter(typeof(JsonOmdbNotAvailableConverterFactory))]
public string Plot { get; set; }
+ [JsonConverter(typeof(JsonOmdbNotAvailableConverterFactory))]
public string Language { get; set; }
+ [JsonConverter(typeof(JsonOmdbNotAvailableConverterFactory))]
public string Country { get; set; }
+ [JsonConverter(typeof(JsonOmdbNotAvailableConverterFactory))]
public string Awards { get; set; }
+ [JsonConverter(typeof(JsonOmdbNotAvailableConverterFactory))]
public string Poster { get; set; }
public List Ratings { get; set; }
+ [JsonConverter(typeof(JsonOmdbNotAvailableConverterFactory))]
public string Metascore { get; set; }
+ [JsonConverter(typeof(JsonOmdbNotAvailableConverterFactory))]
public string imdbRating { get; set; }
+ [JsonConverter(typeof(JsonOmdbNotAvailableConverterFactory))]
public string imdbVotes { get; set; }
+ [JsonConverter(typeof(JsonOmdbNotAvailableConverterFactory))]
public string imdbID { get; set; }
+ [JsonConverter(typeof(JsonOmdbNotAvailableConverterFactory))]
public string Type { get; set; }
+ [JsonConverter(typeof(JsonOmdbNotAvailableConverterFactory))]
public string DVD { get; set; }
+ [JsonConverter(typeof(JsonOmdbNotAvailableConverterFactory))]
public string BoxOffice { get; set; }
+ [JsonConverter(typeof(JsonOmdbNotAvailableConverterFactory))]
public string Production { get; set; }
+ [JsonConverter(typeof(JsonOmdbNotAvailableConverterFactory))]
public string Website { get; set; }
+ [JsonConverter(typeof(JsonOmdbNotAvailableConverterFactory))]
public string Response { get; set; }
- public int Episode { get; set; }
+ [JsonConverter(typeof(JsonOmdbNotAvailableConverterFactory))]
+ public int? Episode { get; set; }
public float? GetRottenTomatoScore()
{
diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
index af4684f562..91b34546f3 100644
--- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
+++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
@@ -29,6 +29,7 @@
+
diff --git a/tests/Jellyfin.Common.Tests/Json/JsonOmdbConverterTests.cs b/tests/Jellyfin.Common.Tests/Json/JsonOmdbConverterTests.cs
new file mode 100644
index 0000000000..90537dc1ff
--- /dev/null
+++ b/tests/Jellyfin.Common.Tests/Json/JsonOmdbConverterTests.cs
@@ -0,0 +1,21 @@
+using System.Text.Json;
+using MediaBrowser.Common.Json;
+using MediaBrowser.Providers.Plugins.Omdb;
+using Xunit;
+
+namespace Jellyfin.Common.Tests.Json
+{
+ public static class JsonOmdbConverterTests
+ {
+ [Fact]
+ public static void Deserialize_Omdb_Response_Not_Available_Success()
+ {
+ const string Input = "{\"Title\":\"Chapter 1\",\"Year\":\"2013\",\"Rated\":\"TV-MA\",\"Released\":\"01 Feb 2013\",\"Season\":\"N/A\",\"Episode\":\"N/A\",\"Runtime\":\"55 min\",\"Genre\":\"Drama\",\"Director\":\"David Fincher\",\"Writer\":\"Michael Dobbs (based on the novels by), Andrew Davies (based on the mini-series by), Beau Willimon (created for television by), Beau Willimon, Sam Forman (staff writer)\",\"Actors\":\"Kevin Spacey, Robin Wright, Kate Mara, Corey Stoll\",\"Plot\":\"Congressman Francis Underwood has been declined the chair for Secretary of State. He's now gathering his own team to plot his revenge. Zoe Barnes, a reporter for the Washington Herald, will do anything to get her big break.\",\"Language\":\"English\",\"Country\":\"USA\",\"Awards\":\"N/A\",\"Poster\":\"https://m.media-amazon.com/images/M/MV5BMTY5MTU4NDQzNV5BMl5BanBnXkFtZTgwMzk2ODcxMzE@._V1_SX300.jpg\",\"Ratings\":[{\"Source\":\"Internet Movie Database\",\"Value\":\"8.7/10\"}],\"Metascore\":\"N/A\",\"imdbRating\":\"8.7\",\"imdbVotes\":\"6736\",\"imdbID\":\"tt2161930\",\"seriesID\":\"N/A\",\"Type\":\"episode\",\"Response\":\"True\"}";
+ var seasonRootObject = JsonSerializer.Deserialize(Input, JsonDefaults.GetOptions());
+ Assert.NotNull(seasonRootObject);
+ Assert.Null(seasonRootObject?.Awards);
+ Assert.Null(seasonRootObject?.Episode);
+ Assert.Null(seasonRootObject?.Metascore);
+ }
+ }
+}