Приведение типов после десериализации

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

Задача

Имеется базовый класс MyClassBase.

public abstract class MyClassBase
{
    public string Value { get; set; }

    protected MyClassBase()
    {
    }

    protected MyClassBase(string value)
    {
        Value = value;
    }

    public abstract string Do();
}

Имеется список, содержащий значения типов, унаследованных от класса MyClassBase т.е. List<MyClassBase>.

public class MyFirstClass : MyClassBase
{
    public string FirstValue { get; set; }

    public MyFirstClass(string value, string firstValue) : base(value)
    {
        FirstValue = firstValue;
    }

    public override string Do()
    {
        return FirstValue;
    }
}


public class MySecondClass : MyClassBase
{
    public string SecondValue { get; set; }

    public MySecondClass(string value, string secondValue) : base(value)
    {
        SecondValue = secondValue;
    }

    public override string Do()
    {
        return SecondValue;
    }
}

Необходимо сериализовать этот список в строку и затем восстановить в исходном состоянии.

Решение

Для сериализации/десериализации будем использовать библиотеку Newtonsoft.Json.

С сериализацией проблем не возникнет, а вот десериализовать список в исходное состояние не удасться (потому что элементы списка разного типа и привести их можно будет только к базовому типу, при этом часть функциональности классов будет утеряна).

Для корректного восстановления списка в исходное состояние необходимо при сериализации сохранять тип элемента списка. Для этого необходимо немного изменить тип элементов списка и сделать их не MyClassBase, а KeyValuePair<Type, MyClassBase>. Тоесть элементом списка будет пара ключ-значение, где ключом будет тип значения. Таким образом vs сохраняем данные о типе элемента списка при сериализации.

При десериализации строка восстанавливается в список типа List<KeyValuePair<Type, JObject>>, т.е. вместо конечного типа указываем тип JObject. Почему не обычный object?.. Потому что сериализатор использует именно тип JObject для общих типов (тоесть если указать object, то по сути он все равно будет JObject), а главное, этот тип имеет методы приведения.

Конечным шагом будет перебор списка List<KeyValuePair<Type, JObject>> и приведение всех его элементов к нужному типу, для формирования списка типа KeyValuePair<Type, MyClassBase>.

class Program
{
    static void Main()
    {
        // Формируем список элементов ключ-значение, где в качестве ключа выступает тип класса значения.
        var items = new List<KeyValuePair<Type, MyClassBase>>
        {
            new KeyValuePair<Type, MyClassBase>(typeof(MyFirstClass), new MyFirstClass("Base", "First")),
            new KeyValuePair<Type, MyClassBase>(typeof(MySecondClass), new MySecondClass("Base", "Second"))
        };

        // Сериализуем список в строку.
        var str = JsonConvert.SerializeObject(items, Formatting.Indented);
        Console.WriteLine(str);

        // Восстанавливаем список из строки. Типы значений разные и заранее не известны,
        // поэтому в качестве типа значения используем тип JObject. Используется именно тип
        // JObject, а не Object, потому что у него есть средства приведения типов.
        var itemsTmp = JsonConvert.DeserializeObject<List<KeyValuePair<Type, JObject>>>(str);

        // Формируем исходный список с нужными типами.
        items = new List<KeyValuePair<Type, MyClassBase>>();
        foreach (var pair in itemsTmp)
        {
            var item = (MyClassBase)pair.Value.ToObject(pair.Key);
            items.Add(new KeyValuePair<Type, MyClassBase>(pair.Key, item));
        }

        // Проверяем, что все типы восстановлены правильно.
        foreach (var pair in items)
            Console.WriteLine($"{pair.Key.FullName} {pair.Value.Do()}");
    }
}

Исходный код

About the author

Добавить комментарий

Сказать спасибо

Способ платежа:

Подписаться на обновления

Укажите свой e-mail чтобы получать уведомления о новых статьях.

Присоединиться к еще 2 подписчикам