21

我正在使用 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 类型的信息吗?

4

3 回答 3

39

DTO 中的继承是一个坏主意 - DTO 应该尽可能地自我描述,并且通过使用继承,客户端实际上不知道服务最终返回什么。这就是为什么您的 DTO 类在大多数“基于标准的”序列化程序中无法正确反序列化的原因。

在 DTO 中使用接口没有充分的理由(在 POCO 模型上使用它们的理由也很少),使用接口来减少应用程序代码中的耦合是一种货物崇拜习惯,这些代码会被轻率地泄露到 DTO 中。但是跨进程边界,接口只会增加耦合(它只是在代码中减少),因为消费者不知道要反序列化的具体类型,所以它必须发出特定于序列化的实现提示,现在将 C# 关注点嵌入到线路上(所以现在甚至C# 命名空间将中断序列化),现在将您的响应限制为由特定的序列化程序使用。在线路上泄露 C# 问题违反了服务实现互操作性的核心目标之一。

由于 JSON 规范中没有“类型信息”的概念,为了让继承在 JSON 序列化程序中工作,他们需要向JSON 有线格式发出专有扩展以包含此类型信息 - 现在将您的 JSON 有效负载耦合到特定的 JSON序列化器实现。

ServiceStack 的 JsonSerializer将此类型信息存储在__type属性中,并且由于它会大大增加有效负载,因此只会为需要它的类型(即Interfaces后期绑定object类型或abstract类)发出此类型信息。

话虽如此,解决方案是更改Animal接口抽象类,但建议不要在 DTO 中使用继承。

于 2012-05-25T18:00:00.887 回答
1

您只序列化动物对象的属性,无论序列化对象是否为狗。即使您向狗类添加公共属性,例如“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(); 
于 2012-05-25T08:21:18.360 回答
1

也许是题外话,但 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; }
}
于 2016-09-28T15:01:05.560 回答