Получение ServiceStack для сохранения информации о типе

Я использую ServiceStack для сериализации и десериализации некоторых объектов в JSON. Рассмотрим этот пример:

public class Container
{
    public Animal Animal { get; set; }
}

public class Animal
{
}

public class Dog : Animal
{
    public void Speak() { Console.WriteLine("Woof!"); }
}

var container = new Container { Animal = new Dog() };
var json = JsonSerializer.SerializeToString(container);
var container2 = JsonSerializer.DeserializeFromString<Container>(json);

((Dog)container.Animal).Speak(); //Works
((Dog)container2.Animal).Speak(); //InvalidCastException

Последняя строка генерирует InvalidCastException, потому что поле Animal создается как тип Animal, а не тип Dog. Есть ли способ, с помощью которого ServiceStack может сохранить информацию о том, что этот конкретный экземпляр относится к типу Dog?

Ответ 1

Наследование в DTO является плохой идеей - DTO должен быть как можно более самоописательным, и, используя клиентов наследования, фактически не имеет понятия, что сервис в конечном итоге возвращает. Именно поэтому ваши классы DTO не смогут правильно сериализоваться в большинстве сериализаторов на основе стандартов.

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

Поскольку в спецификации JSON нет понятия "информация типа", для того чтобы наследование работало в сериализаторах JSON, им нужно испустить собственные расширения JSON wireformat, чтобы включить эту информацию типа, которая теперь соединяет вашу полезную нагрузку JSON с конкретной реализацией сериализатора JSON.

ServiceStack JsonSerializer сохраняет этот тип информации в свойстве __ type и, поскольку он может значительно раздуть полезную нагрузку, будет генерировать информацию этого типа только для типы, которые в ней нуждаются, т.е. типы Interfaces, поздние границы object или abstract.

С учетом сказанного, решение было бы изменить Animal как на Интерфейс или абстрактный класс, однако рекомендация не должна использовать наследование в DTO.

Ответ 2

Вы сериализуете только свойства объекта животного, независимо от того, является ли сериализованный объект собакой или нет. Даже если вы добавите публичное свойство в класс собаки, например "Имя", оно не будет сериализовано, поэтому при десериализации вы будете иметь только свойства класса "Животное".

Если вы измените его на следующее, оно будет работать;

public class Container<T> where T: Animal 
{        
    public T Animal { get; set; } 
}

public class Animal
{
}

public class Dog : Animal
{
    public void Speak() { Console.WriteLine("Woof!"); }
    public string Name { get; set; }
}

var c = new Container<Dog> { Animal = new Dog() { Name = "dog1" } };
var json = JsonSerializer.SerializeToString<Container<Dog>>(c);
var c2 = JsonSerializer.DeserializeFromString<Container<Dog>>(json);

c.Animal.Speak(); //Works
c2.Animal.Speak(); 

Ответ 3

Возможно, вне темы, но сериализатор Newtonsoft может сделать это, включая опцию:

            serializer = new JsonSerializer();
        serializer.TypeNameHandling = TypeNameHandling.All;

Он создаст свойство внутри json с именем $type с сильным типом объекта. Когда вы вызываете десериализатор, это значение будет использоваться для повторного создания объекта с теми же типами. Следующий тест работает с использованием newtonsoft с сильным типом, а не с ServiceStack

 [TestFixture]
public class ServiceStackTests
{
    [TestCase]
    public void Foo()
    {
        FakeB b = new FakeB();
        b.Property1 = "1";
        b.Property2 = "2";

        string raw = b.ToJson();
        FakeA a=raw.FromJson<FakeA>();
        Assert.IsNotNull(a);
        Assert.AreEqual(a.GetType(), typeof(FakeB));
    }
}

public abstract class FakeA
{
    public string Property1 { get; set; }
}

public class FakeB:FakeA
{
    public string Property2 { get; set; }
}