Polymorphism WCF deserialization doesn't work

373 views Asked by At

I have created the following classes. one base class IObject and 2 derived classes A and B.

[KnownType(typeof(B))]
[KnownType(typeof(A))]
[DataContract(Name = "IObject")]
public class IObject
{
}

[DataContract(Name="A")]
public class A : IObject
{
    [DataMember]
    public string s1 { get; set; }     // Tag Name as it will be presented to the user

}

[DataContract(Name="B")]
public class B : IObject
{
    [DataMember]
    public string s2 { get; set; }         

}

I have also created the following Service:

    [ServiceKnownType(typeof(B))]
    [ServiceKnownType(typeof(A))]
    public void GetR(IObject obj)
    {

        B other = (B)obj;
    }

What i want to do is get an instance of A or B but i don't know which one i will get so i expect to get an IObject and cast it later to either A or B as shown in the example i put.

What happens when i send a json string containing s2 string is that i get IObject instance and not B instance. What is wrong with the code?

Example of the client i'm using :

    var url = serviceURL;
    var data = {s2: "Data"};
    var json = JSON.stringify(data);

    $.ajax({
      type: "POST",
      url: url,
      data: data,
      contentType: "application/json",
      dataType: 'json'
    });

Edited: I have uploaded an example code to gitHub at the following link: https://github.com/AlgoEitan/27588144

The client there is a c# client (I've tried it with both c# and javascript - web browser client)

1

There are 1 answers

1
dbc On BEST ANSWER

DataContractJsonSerializer has a specific format in which it stores hints about known type information for polymorphic types, which one can discover with a bit of testing. I copied and pasted your classes as-is, and created the following test code:

public static class DataContractJsonSerializerPolymorphismTest
{
    public static void Test()
    {
        var a1 = new A() { s1 = "A" };
        var b1 = new B() { s2 = "B" };

        DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(IObject));

        var jsona1 = DataContractJsonSerializerHelper.GetJson(a1, serializer);
        var jsonb1 = DataContractJsonSerializerHelper.GetJson(b1, serializer);

        Debug.WriteLine(jsona1);
        Debug.WriteLine(jsonb1);

        var newa1 = DataContractJsonSerializerHelper.GetObject<IObject>(jsona1, serializer);

        Debug.Assert(newa1.GetType() == a1.GetType()); // No assert
    }
}

With that, the following JSON was created:

{"__type":"A:#Tile.DataContractJsonSerializerPolymorphism","s1":"A"}
{"__type":"B:#Tile.DataContractJsonSerializerPolymorphism","s2":"B"}

Where Tile.DataContractJsonSerializerPolymorphism happens to be the name of the CLR namespace in my test application. So, as you can see, the known type information got tucked away in this extra JSON property __type. Now, if you were using DataContractJsonSerializerHelper in your client also, you would never know this, because communication would just work. But you are using JSON.stringify() which does not have this logic. So, you may have to manually add the "__type":"DataContractName:DataContractNamespace" property on the client side.

More about the format for polymorphic types can be found here. (I only tracked this documentation down after finding the hidden __type parameter, which gave me an extra Google search term.)

FYI, here is the helper class I used with the test code:

public static class DataContractJsonSerializerHelper
{
    private static MemoryStream GenerateStreamFromString(string value)
    {
        return new MemoryStream(Encoding.Unicode.GetBytes(value ?? ""));
    }

    public static string GetJson<T>(T obj, DataContractJsonSerializer serializer) where T : class
    {
        using (var memory = new MemoryStream())
        {
            serializer.WriteObject(memory, obj);
            memory.Seek(0, SeekOrigin.Begin);
            using (var reader = new StreamReader(memory))
            {
                return reader.ReadToEnd();
            }
        }
    }

    public static string GetJson<T>(T obj) where T : class
    {
        DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T));
        return GetJson(obj, serializer);
    }

    public static T GetObject<T>(string json) where T : class
    {
        DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T));
        return GetObject<T>(json, serializer);
    }

    public static T GetObject<T>(string json, DataContractJsonSerializer serializer) where T : class
    {
        T obj = null;
        using (var stream = GenerateStreamFromString(json))
        {
            obj = (T)serializer.ReadObject(stream);
        }
        return obj;
    }
}