让 ServiceStack 保留类型信息
Posted
技术标签:
【中文标题】让 ServiceStack 保留类型信息【英文标题】:Getting ServiceStack to retain type information 【发布时间】:2012-05-31 19:25:20 【问题描述】:我正在使用 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 中。但是跨进程边界,接口只会增加耦合(它只是在代码中减少),因为消费者不知道要反序列化的具体类型,所以它必须发出特定于序列化的实现提示,现在将 C# 关注点嵌入到线路上(所以现在甚至C# 命名空间将破坏序列化),现在将您的响应限制为由特定的序列化程序使用。在线路上泄露 C# 问题违反了服务实现互操作性的核心目标之一。
由于 JSON 规范中没有“类型信息”的概念,为了让继承在 JSON 序列化程序中工作,它们需要向JSON wireformat发出专有扩展以包含此类型信息- 现在将您的 JSON 有效负载耦合到特定的 JSON 序列化程序实现。
ServiceStack's JsonSerializer 将此类型信息存储在 __type 属性中,并且由于它会大大膨胀有效负载,因此只会为需要它的类型发出此类型信息,即 Interfaces
,后期绑定object
类型或 abstract
类。
话虽如此,解决方案是将Animal
更改为Interface 或abstract 类,但建议不要在DTO 中使用继承。
【讨论】:
如果我有多个服务采用基本相同的 DTO,该怎么办?例如用户注册服务和用户维护服务?两者都期望 UserAccount DTO 与应填写的字段有所不同。存储库期望 UserAccount DTO,因为它将插入或更新 UserAccount 表。所以我创建了一个 UserAccount DTO。 UserRegistration DTO 和 UserMaintenance DTO 继承自它。 [还要在这里非常感谢您创建 ServiceStack。] 呃...回答我自己现在看似显而易见的评论/问题:消息不应该“成为”业务领域对象,它应该“包含”一个(或更多)。 “有”与“是”的简单问题。因此,对于我的示例,UpdateUserMsg 和 RegisterUserMsg 都将包含 UserAccount DTO。两者都不是 UserAccount DTO。无需继承。 你将如何处理没有继承的列表?例如,假设我有一个带有List<IAnimal>
属性的 DTO,然后我有 20 个不同的类实现了IAnimal
。
@Cocowalla IAnimal
具有 20 个具体实现与拥有一个所有 DTO 实现的空 IModel
接口一样有用。这样,您就可以为您的整个应用程序使用 1 个服务,返回 IModel
。但这对您的客户有什么影响?没有。它没有提供有关它返回什么的信息,这就是为什么您的服务合同应该是自我描述的,以便消费者知道您的服务返回什么以及他们实现了什么合同。要么使用has vs is
构建它,要么使用不同的服务,要么将它们展平,以便 1 个模型包含您希望它表示的类型的所有属性。
在这个答案中:github.com/ServiceStack/Issues/issues/174 你举了一个例子,比如: JsConfig您只序列化动物对象的属性,无论序列化对象是否为狗。即使您向狗类添加公共属性,例如“Name”,它也不会被序列化,因此当您反序列化时,您只会拥有“Animal”类的属性。
如果你将其更改为以下内容,它将起作用;
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();
【讨论】:
谢谢。不幸的是,我不能使用泛型来解决这个问题——我基本上有一个“List也许是题外话,但 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;
【讨论】:
以上是关于让 ServiceStack 保留类型信息的主要内容,如果未能解决你的问题,请参考以下文章
让 servicestack 在 monodroid 上工作
使用 JSON 时如何让 ServiceStack 用破折号格式化 Guid?
ServiceStack.Text JsonSerializer 无法反序列化其自己的序列化模式(类型定义应以 '' SerializationException 开头)