System.Text. Json Сериализация словаря в виде массива - PullRequest
0 голосов
/ 08 января 2020

Можно ли сериализовать (и десериализовать) словарь в виде массива с помощью System.Text. Json?

Вместо { "hello": "world" } мне нужно, чтобы мой словарь был сериализован как { "key": "hello", "value": "world" } предпочтительно без установить атрибуты для свойства словаря моего класса.

Используя newtonsoft. json это было возможно следующим образом:

class DictionaryAsArrayResolver : DefaultContractResolver
{
    protected override JsonContract CreateContract(Type objectType)
    {
        if (objectType.GetInterfaces().Any(i => i == typeof(IDictionary) || 
           (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDictionary<,>))))
        {
            return base.CreateArrayContract(objectType);
        }

        return base.CreateContract(objectType);
    }
}

Ответы [ 2 ]

1 голос
/ 08 января 2020

Вы можете сделать это, используя JsonConverterFactory, который создает спецификацию c JsonConverter<T> для каждого типа словаря, который вы хотите сериализовать как массив. Вот один такой конвертер, который работает для каждого класса, который реализует IDictionary<TKey, TValue>:

public class DictionaryConverterFactory : JsonConverterFactory
{
    public override bool CanConvert(Type typeToConvert)
    {
        return typeToConvert.IsClass && typeToConvert.GetDictionaryKeyValueType() != null && typeToConvert.GetConstructor(Type.EmptyTypes) != null;
    }

    public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
    {
        var keyValueTypes = typeToConvert.GetDictionaryKeyValueType();
        var converterType = typeof(DictionaryAsArrayConverter<,,>).MakeGenericType(typeToConvert, keyValueTypes.Value.Key, keyValueTypes.Value.Value);
        return (JsonConverter)Activator.CreateInstance(converterType);
    }
}

public class DictionaryAsArrayConverter<TKey, TValue> : DictionaryAsArrayConverter<Dictionary<TKey, TValue>, TKey, TValue>
{
}

public class DictionaryAsArrayConverter<TDictionary, TKey, TValue> : JsonConverter<TDictionary> where TDictionary : class, IDictionary<TKey, TValue>, new()
{
    struct KeyValueDTO
    {
        public TKey Key { get; set; }
        public TValue Value { get; set; }
    }

    public override TDictionary Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        var list = JsonSerializer.Deserialize<List<KeyValueDTO>>(ref reader, options);
        if (list == null)
            return null;
        var dictionary = typeToConvert == typeof(Dictionary<TKey, TValue>) ? (TDictionary)(object)new Dictionary<TKey, TValue>(list.Count) : new TDictionary();
        foreach (var pair in list)
            dictionary.Add(pair.Key, pair.Value);
        return dictionary;
    }

    public override void Write(Utf8JsonWriter writer, TDictionary value, JsonSerializerOptions options)
    {
        JsonSerializer.Serialize(writer, value.Select(p => new KeyValueDTO { Key = p.Key, Value = p.Value }), options);
    }
}

public static class TypeExtensions
{
    public static IEnumerable<Type> GetInterfacesAndSelf(this Type type)
    {
        if (type == null)
            throw new ArgumentNullException();
        if (type.IsInterface)
            return new[] { type }.Concat(type.GetInterfaces());
        else
            return type.GetInterfaces();
    }

    public static KeyValuePair<Type, Type>? GetDictionaryKeyValueType(this Type type)
    {
        KeyValuePair<Type, Type>? types = null;
        foreach (var pair in type.GetDictionaryKeyValueTypes())
        {
            if (types == null)
                types = pair;
            else
                return null;
        }
        return types;
    }

    public static IEnumerable<KeyValuePair<Type, Type>> GetDictionaryKeyValueTypes(this Type type)
    {
        foreach (Type intType in type.GetInterfacesAndSelf())
        {
            if (intType.IsGenericType
                && intType.GetGenericTypeDefinition() == typeof(IDictionary<,>))
            {
                var args = intType.GetGenericArguments();
                if (args.Length == 2)
                    yield return new KeyValuePair<Type, Type>(args[0], args[1]);
            }
        }
    }
}

Затем добавьте фабрику к JsonSerializerOptions.Converters локально следующим образом:

var options = new JsonSerializerOptions
{
    Converters = { new DictionaryConverterFactory() },
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};

var json = JsonSerializer.Serialize(dictionary, options);

var dictionary2 = JsonSerializer.Deserialize<TDictionary>(json, options);

Или глобально в ASP. NET Core, как показано в Как установить json настройки сериализатора в asp. net core 3? :

services.AddControllers().AddJsonOptions(options =>
{
    options.JsonSerializerOptions.Converters.Add(new DictionaryConverterFactory());
    options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
});

Базовый индивидуальный конвертер DictionaryAsArrayConverter<TKey, TValue> также можно использовать напрямую, если вы хотите только сериализовать определенные типы словарей в виде массивов.

Примечания:

Демо-скрипка здесь .

0 голосов
/ 08 января 2020

Если вы хотите, чтобы это было коротким и простым, вы можете рассмотреть проекцию через анонимный тип:

var dictionary = new Dictionary<string, string>();
dictionary.Add("hello", "world");
dictionary.Add("how", "are you?");

var o = JsonSerializer.Serialize(dictionary.Select(x => new { key = x.Key, value = x.Value }));
// [{"key":"hello","value":"world"},{"key":"how","value":"are you?"}]

ed: конечно, это только если ваше чувство мазохисти c. Если все, что вам нужно, это просто выполнить работу, просто позвоните .ToList()

JsonSerializer.Serialize(dictionary.ToList());
// [{"Key":"hello","Value":"world"},{"Key":"how","Value":"are you?"}]
...