21

I'm using ServiceStack to serialize and deserialize some objects to JSON. Consider this example:

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

The last line throws a InvalidCastException, because the Animal field is instantiated as an Animal type, not a Dog type. Is there any way I can tell ServiceStack to retain the information that this particular instance was of the Dog type?

larspars
  • 1,640
  • 1
  • 15
  • 30

3 Answers3

39

Inheritance in DTOs is a bad idea - DTO's should be as self-describing as possible and by using inheritance clients effectively have no idea what the service ultimately returns. Which is why your DTO classes will fail to de/serialize properly in most 'standards-based' serializers.

There's no good reason for having interfaces in DTO's (and very few reasons to have them on POCO models), it's a cargo cult habit of using interfaces to reduce coupling in application code that's being thoughtlessly leaked into DTOs. But across process boundaries, interfaces only adds coupling (it's only reduced in code) since the consumer has no idea what concrete type to deserialize into so it has to emit serialization-specific implementation hints that now embeds C# concerns on the wire (so now even C# namespaces will break serialization) and now constrains your response to be used by a particular serializer. Leaking C# concerns on the wire violates one of the core goal of services for enabling interoperability.

As there is no concept of 'type info' in the JSON spec, in order for inheritance to work in JSON Serializers they need to emit proprietary extensions to the JSON wireformat to include this type info - which now couples your JSON payload to a specific JSON serializer implementation.

ServiceStack's JsonSerializer stores this type info in the __type property and since it can considerably bloat the payload, will only emit this type information for types that need it, i.e. Interfaces, late-bound object types or abstract classes.

With that said the solution would be to change Animal to either be an Interface or an abstract class, the recommendation however is not to use inheritance in DTOs.

mythz
  • 141,670
  • 29
  • 246
  • 390
  • What to do in the case where I have multiple services that take essentially the same DTO? For example a user registration service and a user maintenance service? Both expect a UserAccount DTO with some difference in which fields should be filled in. The repository is expecting a UserAccount DTO because it's going to be inserting or updating the UserAccount table. So I created a UserAccount DTO. The UserRegistration DTO and UserMaintenance DTO inherit from it. [Also want to add here a big thanks for creating ServiceStack.] – GeorgeBarker Aug 27 '13 at 15:18
  • 4
    Ugh...to answer my own now seemingly obvious comment/question: A message should not "be" a business domain object, it should "contain" one (or more). A simple matter of "has" vs "is". So for my example the UpdateUserMsg and RegisterUserMsg would both contain a UserAccount DTO. Neither would BE a UserAccount DTO. No need for inheritance. – GeorgeBarker Sep 09 '13 at 20:08
  • How would you handle lists without inheritance? For example, let's say I have a DTO with a `List` property, and then I have 20 different classes that implement `IAnimal`. – Cocowalla Mar 20 '14 at 08:24
  • @Cocowalla `IAnimal` with 20 concrete implementations is as useful as having an empty `IModel{}` interface that all your DTO's implement. That way you can get by with 1 Service for your whole app that returns `IModel`. But what does that say to your clients? nothing. It gives no information about what it returns which is why your service contracts should be self-describing so consumers know what your services return and what contracts they realize. Either structure it with `has vs is`, have different services or flatten them so 1 model contains all properties of types you want it to represent. – mythz Mar 20 '14 at 08:57
  • In this answer: https://github.com/ServiceStack/Issues/issues/174 you give an example, like: _JsConfig.IncludeTypeInfo = true;_ IS there a global way to do it for all types, so I dont have to write a list of 3000+ classes? – Ted Apr 28 '18 at 17:10
  • Right, asked and answered: _JsConfig.IncludeTypeInfo = true;_ did the trick =) – Ted Apr 28 '18 at 17:26
  • I am using the following config: JsConfig.IncludeTypeInfo = true but it is not properly returning the value after serialization of the derived class when using the base class as the property type. It keeps coming back with a null value. Any ideas? – mlangwell Sep 18 '20 at 17:34
1

You are serializing only the properties of the animal object, whether the serialized object is dog or not. Even if you add a public property to the dog class, like "Name", it will not be serialized so when you deserialize you will only have properties of an "Animal" class.

If you change it to the following it will work;

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(); 
daryal
  • 14,643
  • 4
  • 38
  • 54
  • Thank you. I can't use generics to get around this unfortunately - I essentially have a ´List´, where each container have different types of ´Animal´. I was hoping there was some setting, or trick to get around this, but I guess I have to live without this functionality. – larspars May 25 '12 at 10:04
1

Maybe is off-topic but Newtonsoft serializer can do that including the option:

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

It will create a property inside the json called $type with the strong type of the object. When you call the deserializer, that value will be use to build the object again with the same types. The next test works using newtonsoft with strong type, not with 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; }
}
cpsaez
  • 314
  • 1
  • 11