Не удается сериализовать Dictionary в C#

#c# #json #dictionary #asp.net-core #serialization

#c# #json #словарь #asp.net-core #сериализация

Вопрос:

Я передаю модель запроса в качестве параметра в свой API для запроса POST, и одно из свойств модели запроса имеет тип Dictionary<int, OrderFood>. Я использую Swagger для тестирования методов действий, и я получаю ответ об ошибке:

«Тип коллекции ‘System.Коллекции.Generic.Dictionary`2[System.int32, OrderFood] не поддерживается.'»

Я понимаю, что в настоящее время не поддерживается сериализация словаря ключей без строк и что мне нужно реализовать пользовательский конвертер JSON. Это класс модели запроса:

     [JsonConverter(typeof(JsonNonStringKeyDictionaryConverter<int, OrderFood>))]
public class FoodOrderRequestModel
{
    public string ModelName { get; set; }

    public string DisplayName { get; set; }

    public string Type { get; set; }

    public object ModelValue { get; set; }

    public object DisplayValue { get; set; }

    public Dictionary<int, OrderFood> Items { get; set; }
}
  

И вот конвертер:

 internal sealed class JsonNonStringKeyDictionaryConverter<TKey, TValue> : JsonConverter<IDictionary<TKey, TValue>>
{
    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[] { element.Key }), element.Value);
        }

        return instance;
    }

    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();
    }
}

internal sealed class JsonNonStringKeyDictionaryConverterFactory : JsonConverterFactory
{
    public override bool CanConvert(Type typeToConvert)
    {
        if (!typeToConvert.IsGenericType)
        {
            return false;
        }

        if (typeToConvert.GenericTypeArguments[0] == typeof(string))
        {
            return false;
        }

        return typeToConvert.GetInterface("IDictionary") != null;
    }

    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;
    }
}
  

Сериализация происходит до того, как в моем API будет достигнута какая-либо точка останова, поэтому я не уверен, как я могу протестировать свой конвертер, чтобы узнать, добавлен ли он вообще в конвейер. Я также не уверен в размещении jsononstringkeydictionaryconverter<,> потому что я видел определенные реализации, где он находится непосредственно над атрибутом. Мы высоко ценим любые советы или помощь.

Комментарии:

1. У меня это сработало после замены IDictionary на Dictionnary и перемещения атрибута конвертера в свойство, а не в класс

2. @Kalten вы изменили идентификатор на Dictionary для всех экземпляров в конвертере?

3. Везде в JsonNonStringKeyDictionaryConverter классе

4. @Kalten сработает ли это, если я использую Dictionary<int, OrderFood>, а OrderFood — это объект, содержащий другой Dictionary<строка, объект>?

5. Согласно этой проблеме с GitHub, она исправлена в .NET 5, но не перенесена обратно в Core 3.x; последний комментарий предполагает, что для исправления можно добавить / обновить System.Text.Json пакет NuGet: github.com/dotnet/runtime/issues/30524#issuecomment-524619972

Ответ №1:

Обновите / добавьте свой System.Text.Пакет Json NuGet для LTS версии 5.0.1 хорошо работал для приложения .NET Core 3.1.

Особая благодарность @CoolBots за обсуждение темы на githubhttps://github.com/dotnet/runtime/issues/30524#issuecomment-524619972