Сериализация словаря со значениями IConvertible с использованием Json.NET с флагом TypeNameHandling - PullRequest
0 голосов
/ 06 сентября 2018

У меня есть следующий словарь, который я бы очень хотел сериализовать с помощью 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 Ответ

0 голосов
/ 06 сентября 2018

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

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

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

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

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

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

Указанные выше проблемы можно решить с помощью следующих custom 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);

Примечания:

  • Поскольку 1068 * применяется к ConvertibleDictionaryDTO, нет необходимости включать TypeNameHandling.Objects глобально. Это снижает ваши риски безопасности.

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

  • Однако для обеспечения дополнительной безопасности вы все равно можете написать пользовательское связующее устройство сериализации, которое допускает только известные типы из белого списка.

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

...