Получение 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; }
}