Add Dictionary with non-string keys to System.Text.Json
This commit is contained in:
parent
93bca7ab50
commit
01a5103fef
|
@ -1,5 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Jellyfin.Api;
|
using Jellyfin.Api;
|
||||||
using Jellyfin.Api.Auth;
|
using Jellyfin.Api.Auth;
|
||||||
|
@ -9,6 +11,7 @@ using Jellyfin.Api.Constants;
|
||||||
using Jellyfin.Api.Controllers;
|
using Jellyfin.Api.Controllers;
|
||||||
using Jellyfin.Server.Formatters;
|
using Jellyfin.Server.Formatters;
|
||||||
using MediaBrowser.Common.Json;
|
using MediaBrowser.Common.Json;
|
||||||
|
using MediaBrowser.Model.Entities;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
@ -128,7 +131,30 @@ namespace Jellyfin.Server.Extensions
|
||||||
// Use method name as operationId
|
// Use method name as operationId
|
||||||
c.CustomOperationIds(description =>
|
c.CustomOperationIds(description =>
|
||||||
description.TryGetMethodInfo(out MethodInfo methodInfo) ? methodInfo.Name : null);
|
description.TryGetMethodInfo(out MethodInfo methodInfo) ? methodInfo.Name : null);
|
||||||
|
|
||||||
|
// TODO - remove when all types are supported in System.Text.Json
|
||||||
|
c.AddSwaggerTypeMappings();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void AddSwaggerTypeMappings(this SwaggerGenOptions options)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* TODO remove when System.Text.Json supports non-string keys.
|
||||||
|
* Used in Jellyfin.Api.Controller.GetChannels.
|
||||||
|
*/
|
||||||
|
options.MapType<Dictionary<ImageType, string>>(() =>
|
||||||
|
new OpenApiSchema
|
||||||
|
{
|
||||||
|
Type = "object",
|
||||||
|
Properties = typeof(ImageType).GetEnumNames().ToDictionary(
|
||||||
|
name => name,
|
||||||
|
name => new OpenApiSchema
|
||||||
|
{
|
||||||
|
Type = "string",
|
||||||
|
Format = "string"
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Common.Json.Converters
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Converter for Dictionaries without string key.
|
||||||
|
/// TODO This can be removed when System.Text.Json supports Dictionaries with non-string keys.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TKey">Type of key.</typeparam>
|
||||||
|
/// <typeparam name="TValue">Type of value.</typeparam>
|
||||||
|
internal sealed class JsonNonStringKeyDictionaryConverter<TKey, TValue> : JsonConverter<IDictionary<TKey, TValue>>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Read JSON.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="reader">The Utf8JsonReader.</param>
|
||||||
|
/// <param name="typeToConvert">The type to convert.</param>
|
||||||
|
/// <param name="options">The json serializer options.</param>
|
||||||
|
/// <returns>Typed dictionary.</returns>
|
||||||
|
/// <exception cref="NotSupportedException"></exception>
|
||||||
|
public override IDictionary<TKey, TValue> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
var convertedType = typeof(Dictionary<,>).MakeGenericType(typeof(string), typeToConvert.GenericTypeArguments[1]);
|
||||||
|
var value = JsonSerializer.Deserialize(ref reader, convertedType, options);
|
||||||
|
var instance = (Dictionary<TKey, TValue>)Activator.CreateInstance(
|
||||||
|
typeToConvert,
|
||||||
|
BindingFlags.Instance | BindingFlags.Public,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
CultureInfo.CurrentCulture);
|
||||||
|
var enumerator = (IEnumerator)convertedType.GetMethod("GetEnumerator")!.Invoke(value, null);
|
||||||
|
var parse = typeof(TKey).GetMethod(
|
||||||
|
"Parse",
|
||||||
|
0,
|
||||||
|
BindingFlags.Public | BindingFlags.Static,
|
||||||
|
null,
|
||||||
|
CallingConventions.Any,
|
||||||
|
new[] { typeof(string) },
|
||||||
|
null);
|
||||||
|
if (parse == null)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException($"{typeof(TKey)} as TKey in IDictionary<TKey, TValue> is not supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
while (enumerator.MoveNext())
|
||||||
|
{
|
||||||
|
var element = (KeyValuePair<string?, TValue>)enumerator.Current;
|
||||||
|
instance.Add((TKey)parse.Invoke(null, new[] { (object?) element.Key }), element.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write dictionary as Json.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="writer">The Utf8JsonWriter.</param>
|
||||||
|
/// <param name="value">The dictionary value.</param>
|
||||||
|
/// <param name="options">The Json serializer options.</param>
|
||||||
|
public override void Write(Utf8JsonWriter writer, IDictionary<TKey, TValue> value, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
var convertedDictionary = new Dictionary<string?, TValue>(value.Count);
|
||||||
|
foreach (var (k, v) in value)
|
||||||
|
{
|
||||||
|
convertedDictionary[k?.ToString()] = v;
|
||||||
|
}
|
||||||
|
JsonSerializer.Serialize(writer, convertedDictionary, options);
|
||||||
|
convertedDictionary.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Common.Json.Converters
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// https://github.com/dotnet/runtime/issues/30524#issuecomment-524619972.
|
||||||
|
/// TODO This can be removed when System.Text.Json supports Dictionaries with non-string keys.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class JsonNonStringKeyDictionaryConverterFactory : JsonConverterFactory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Only convert objects that implement IDictionary and do not have string keys.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="typeToConvert">Type convert.</param>
|
||||||
|
/// <returns>Conversion ability.</returns>
|
||||||
|
public override bool CanConvert(Type typeToConvert)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!typeToConvert.IsGenericType)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let built in converter handle string keys
|
||||||
|
if (typeToConvert.GenericTypeArguments[0] == typeof(string))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only support objects that implement IDictionary
|
||||||
|
return typeToConvert.GetInterface(nameof(IDictionary)) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create converter for generic dictionary type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="typeToConvert">Type to convert.</param>
|
||||||
|
/// <param name="options">Json serializer options.</param>
|
||||||
|
/// <returns>JsonConverter for given type.</returns>
|
||||||
|
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
|
||||||
|
{
|
||||||
|
var converterType = typeof(JsonNonStringKeyDictionaryConverter<,>)
|
||||||
|
.MakeGenericType(typeToConvert.GenericTypeArguments[0], typeToConvert.GenericTypeArguments[1]);
|
||||||
|
var converter = (JsonConverter)Activator.CreateInstance(
|
||||||
|
converterType,
|
||||||
|
BindingFlags.Instance | BindingFlags.Public,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
CultureInfo.CurrentCulture);
|
||||||
|
return converter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,6 +29,7 @@ namespace MediaBrowser.Common.Json
|
||||||
|
|
||||||
options.Converters.Add(new JsonGuidConverter());
|
options.Converters.Add(new JsonGuidConverter());
|
||||||
options.Converters.Add(new JsonStringEnumConverter());
|
options.Converters.Add(new JsonStringEnumConverter());
|
||||||
|
options.Converters.Add(new JsonNonStringKeyDictionaryConverterFactory());
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user