Сериализация словаря со значениями IConvertible с использованием Json.NET с флагом TypeNameHandling

У меня есть следующий словарь, который я бы очень хотел сериализовать с помощью Json.Net. Словарь содержит предметы из IConvertible интерфейс, позволяющий мне добавлять в словарь любой примитивный тип, который мне нужен.

    var dic = new Dictionary<string, IConvertible>();
    dic.Add("bool2", false);
    dic.Add("int2", 235);
    dic.Add("string2", "hellohello");

У меня есть следующая реализация для сериализации списка с использованием Json.net:

    var settings = new JsonSerializerSettings();
    settings.TypeNameHandling = TypeNameHandling.Objects;
    var dicString = JsonConvert.SerializeObject(dic,    Newtonsoft.Json.Formatting.Indented, settings);

Это дает мне следующий вывод:

    {
      "$type": "System.Collections.Generic.Dictionary`2[[System.String,         mscorlib],[System.IConvertible, mscorlib]], mscorlib",
      "bool2": false,
      "int2": 235,
      "string2": "hellohello"
    }

Тем не мение. При попытке десериализации как таковой:

    var dic2 = JsonConvert.DeserializeObject<Dictionary<string, IConvertible>>(dicString);

... я получаю следующую ошибку:

    Error converting value False to type 'System.IConvertible'. Path 'bool2', line 3, position 16.

Я посмотрел вокруг и нашел следующее; но установка typeNameHandling не решила это. Я не могу украсить IConvertible значение с атрибутом имени типа, так как это словарь.

Приведение интерфейсов для десериализации в JSON.NET

Я не нашел никакой другой информации по этой теме, так что некоторая помощь будет принята с благодарностью!

Я также нашел это решение, но оно включает в себя создание ExpandableObjectConverter, который не очень элегантное решение.

Проблемы с использованием JSON.NET с ExpandableObjectConverter

1 ответ

Решение

У вас действительно есть несколько проблем здесь:

  1. Похоже, вы столкнулись со странной проблемой с Json.NET при десериализации целевого типа IConvertible, При десериализации конвертируемого примитивного типа он вызывает системную подпрограмму Convert.ChangeType() преобразовать примитив в целевой тип (например, long в int). И по какой-то причине эта системная подпрограмма выдает исключение при запросе преобразования примитива в тип IConvertible хотя этот примитив уже такого типа.

  2. Ты используешь TypeNameHandling.Objects для сериализации вашего словаря конвертируемых значений, однако этот параметр задокументирован только для работы сериализации в объект JSON. Однако ваши значения будут сериализованы как примитивы JSON, поэтому настройка не применяется.

    Чтобы сохранить информацию о типе для словаря полиморфных примитивов, вам нужно вручную обернуть значения в объекте-контейнере, например, в том, который показан в этом ответе, для десериализации словаря <строка, объект> со значениями перечисления в C#. (Однако этот ответ здесь не работает из-за проблемы #1.)

  3. Если вы не пишете пользовательский связыватель сериализации, TypeNameHandling является небезопасным и уязвимым для атак с использованием инъекций гаджетов, таких как показанные в предостережениях TypeNameHandling в Newtonsoft Json и External json, уязвимы из-за автозапуска Json.Net TypeNameHandling?,

  4. Вы не используете те же настройки для десериализации, что и для сериализации.

Вышеуказанные проблемы могут быть решены с использованием следующих пользовательских JsonConverter:

public class ConvertibleDictionaryConverter : JsonConverter
{
    [JsonDictionary(ItemTypeNameHandling = TypeNameHandling.Auto)]
    class ConvertibleDictionaryDTO : Dictionary<string, ConvertibleWrapper>
    {
        public ConvertibleDictionaryDTO() : base() { }

        public ConvertibleDictionaryDTO(int count) : base(count) { }
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof(IDictionary<string, IConvertible>).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var dto = serializer.Deserialize<ConvertibleDictionaryDTO>(reader);
        if (dto == null)
            return null;
        var dictionary = (IDictionary<string, IConvertible>)(existingValue ?? serializer.ContractResolver.ResolveContract(objectType).DefaultCreator());
        foreach (var pair in dto)
            dictionary.Add(pair.Key, pair.Value.ObjectValue);
        return dictionary;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var dictionary = (IDictionary<string, IConvertible>)value;
        var dto = new ConvertibleDictionaryDTO(dictionary.Count);
        foreach (var pair in dictionary)
            dto.Add(pair.Key, ConvertibleWrapper.CreateWrapper(pair.Value));
        serializer.Serialize(writer, dto);
    }
}

abstract class ConvertibleWrapper
{
    protected ConvertibleWrapper() { }

    [JsonIgnore]
    public abstract IConvertible ObjectValue { get; }

    public static ConvertibleWrapper CreateWrapper<T>(T value) where T : IConvertible
    {
        if (value == null)
            return new ConvertibleWrapper<T>();
        var type = value.GetType();
        if (type == typeof(T))
            return new ConvertibleWrapper<T>(value);
        // Return actual type of subclass
        return (ConvertibleWrapper)Activator.CreateInstance(typeof(ConvertibleWrapper<>).MakeGenericType(type), value);
    }
}

sealed class ConvertibleWrapper<T> : ConvertibleWrapper where T : IConvertible
{
    public ConvertibleWrapper() : base() { }

    public ConvertibleWrapper(T value)
        : base()
    {
        this.Value = value;
    }

    public override IConvertible ObjectValue { get { return Value; } }

    public T Value { get; set; }
}

Затем сериализовать и десериализовать следующим образом:

var settings = new JsonSerializerSettings
{
    Converters = { new ConvertibleDictionaryConverter() },
};
var dicString = JsonConvert.SerializeObject(dic, Newtonsoft.Json.Formatting.Indented, settings);

var dic2 = JsonConvert.DeserializeObject<Dictionary<string, IConvertible>>(dicString, settings);

Заметки:

  • Так как [JsonDictionary(ItemTypeNameHandling = TypeNameHandling.Auto)] применяется к ConvertibleDictionaryDTO не нужно включать TypeNameHandling.Objects во всем мире. Это снижает ваши риски безопасности.

  • Ограничение типа объекта в ConvertibleWrapper<T> реализовать IConvertible также существенно снижает риски безопасности, поскольку маловероятно, что гаджеты будут реализованы IConvertible,

  • Тем не менее, для дополнительной безопасности вы все равно можете написать пользовательский механизм связывания сериализации, который разрешает только известные типы из белого списка.

Рабочий образец.Net скрипка здесь.

Другие вопросы по тегам