Browse Source

Merge pull request #4312 from crobibero/json-array-converter

Add comma delimited string to array json converter
Bond-009 4 years ago
parent
commit
be2f27a069

+ 53 - 0
MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs

@@ -0,0 +1,53 @@
+using System;
+using System.ComponentModel;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace MediaBrowser.Common.Json.Converters
+{
+    /// <summary>
+    /// Convert comma delimited string to array of type.
+    /// </summary>
+    /// <typeparam name="T">Type to convert to.</typeparam>
+    public class JsonCommaDelimitedArrayConverter<T> : JsonConverter<T[]>
+    {
+        private readonly TypeConverter _typeConverter;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="JsonCommaDelimitedArrayConverter{T}"/> class.
+        /// </summary>
+        public JsonCommaDelimitedArrayConverter()
+        {
+            _typeConverter = TypeDescriptor.GetConverter(typeof(T));
+        }
+
+        /// <inheritdoc />
+        public override T[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+        {
+            if (reader.TokenType == JsonTokenType.String)
+            {
+                var stringEntries = reader.GetString()?.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
+                if (stringEntries == null || stringEntries.Length == 0)
+                {
+                    return Array.Empty<T>();
+                }
+
+                var entries = new T[stringEntries.Length];
+                for (var i = 0; i < stringEntries.Length; i++)
+                {
+                    entries[i] = (T)_typeConverter.ConvertFrom(stringEntries[i].Trim());
+                }
+
+                return entries;
+            }
+
+            return JsonSerializer.Deserialize<T[]>(ref reader, options);
+        }
+
+        /// <inheritdoc />
+        public override void Write(Utf8JsonWriter writer, T[] value, JsonSerializerOptions options)
+        {
+            JsonSerializer.Serialize(writer, value, options);
+        }
+    }
+}

+ 28 - 0
MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverterFactory.cs

@@ -0,0 +1,28 @@
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace MediaBrowser.Common.Json.Converters
+{
+    /// <summary>
+    /// Json comma delimited array converter factory.
+    /// </summary>
+    /// <remarks>
+    /// This must be applied as an attribute, adding to the JsonConverter list causes stack overflow.
+    /// </remarks>
+    public class JsonCommaDelimitedArrayConverterFactory : JsonConverterFactory
+    {
+        /// <inheritdoc />
+        public override bool CanConvert(Type typeToConvert)
+        {
+            return true;
+        }
+
+        /// <inheritdoc />
+        public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
+        {
+            var structType = typeToConvert.GetElementType();
+            return (JsonConverter)Activator.CreateInstance(typeof(JsonCommaDelimitedArrayConverter<>).MakeGenericType(structType));
+        }
+    }
+}

+ 92 - 0
tests/Jellyfin.Common.Tests/Json/JsonCommaDelimitedArrayTests.cs

@@ -0,0 +1,92 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Jellyfin.Common.Tests.Models;
+using MediaBrowser.Model.Session;
+using Xunit;
+
+namespace Jellyfin.Common.Tests.Json
+{
+    public static class JsonCommaDelimitedArrayTests
+    {
+        [Fact]
+        public static void Deserialize_String_Valid_Success()
+        {
+            var desiredValue = new GenericBodyModel<string>
+            {
+                Value = new[] { "a", "b", "c" }
+            };
+
+            var options = new JsonSerializerOptions();
+            var value = JsonSerializer.Deserialize<GenericBodyModel<string>>(@"{ ""Value"": ""a,b,c"" }", options);
+            Assert.Equal(desiredValue.Value, value?.Value);
+        }
+
+        [Fact]
+        public static void Deserialize_String_Space_Valid_Success()
+        {
+            var desiredValue = new GenericBodyModel<string>
+            {
+                Value = new[] { "a", "b", "c" }
+            };
+
+            var options = new JsonSerializerOptions();
+            var value = JsonSerializer.Deserialize<GenericBodyModel<string>>(@"{ ""Value"": ""a, b, c"" }", options);
+            Assert.Equal(desiredValue.Value, value?.Value);
+        }
+
+        [Fact]
+        public static void Deserialize_GenericCommandType_Valid_Success()
+        {
+            var desiredValue = new GenericBodyModel<GeneralCommandType>
+            {
+                Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown }
+            };
+
+            var options = new JsonSerializerOptions();
+            options.Converters.Add(new JsonStringEnumConverter());
+            var value = JsonSerializer.Deserialize<GenericBodyModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp,MoveDown"" }", options);
+            Assert.Equal(desiredValue.Value, value?.Value);
+        }
+
+        [Fact]
+        public static void Deserialize_GenericCommandType_Space_Valid_Success()
+        {
+            var desiredValue = new GenericBodyModel<GeneralCommandType>
+            {
+                Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown }
+            };
+
+            var options = new JsonSerializerOptions();
+            options.Converters.Add(new JsonStringEnumConverter());
+            var value = JsonSerializer.Deserialize<GenericBodyModel<GeneralCommandType>>(@"{ ""Value"": ""MoveUp, MoveDown"" }", options);
+            Assert.Equal(desiredValue.Value, value?.Value);
+        }
+
+        [Fact]
+        public static void Deserialize_String_Array_Valid_Success()
+        {
+            var desiredValue = new GenericBodyModel<string>
+            {
+                Value = new[] { "a", "b", "c" }
+            };
+
+            var options = new JsonSerializerOptions();
+            var value = JsonSerializer.Deserialize<GenericBodyModel<string>>(@"{ ""Value"": [""a"",""b"",""c""] }", options);
+            Assert.Equal(desiredValue.Value, value?.Value);
+        }
+
+        [Fact]
+        public static void Deserialize_GenericCommandType_Array_Valid_Success()
+        {
+            var desiredValue = new GenericBodyModel<GeneralCommandType>
+            {
+                Value = new[] { GeneralCommandType.MoveUp, GeneralCommandType.MoveDown }
+            };
+
+            var options = new JsonSerializerOptions();
+            options.Converters.Add(new JsonStringEnumConverter());
+            var value = JsonSerializer.Deserialize<GenericBodyModel<GeneralCommandType>>(@"{ ""Value"": [""MoveUp"", ""MoveDown""] }", options);
+            Assert.Equal(desiredValue.Value, value?.Value);
+        }
+    }
+}

+ 20 - 0
tests/Jellyfin.Common.Tests/Models/GenericBodyModel.cs

@@ -0,0 +1,20 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Text.Json.Serialization;
+using MediaBrowser.Common.Json.Converters;
+
+namespace Jellyfin.Common.Tests.Models
+{
+    /// <summary>
+    /// The generic body model.
+    /// </summary>
+    /// <typeparam name="T">The value type.</typeparam>
+    public class GenericBodyModel<T>
+    {
+        /// <summary>
+        /// Gets or sets the value.
+        /// </summary>
+        [SuppressMessage("Microsoft.Performance", "CA1819:Properties should not return arrays", MessageId = "Value", Justification = "Imported from ServiceStack")]
+        [JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))]
+        public T[] Value { get; set; } = default!;
+    }
+}