Preserving Polymorphic Types in a WCF Service using JSON Preserving Polymorphic Types in a WCF Service using JSON json json

Preserving Polymorphic Types in a WCF Service using JSON


Whenever dealing with serialization, try to first serialize an object graph to see the serialized string format. Then use the format to produce correct serialized strings.

Your packet is incorrect. The correct one is:

POST http://localhost:47440/Service1.svc/SubmitDataEvents HTTP/1.1User-Agent: FiddlerHost: localhost:47440Content-Length: 211Content-Type: text/json[  {    "__type":"IntEvent:#WcfTest.Data",    "Id":12345,    "Timestamp":"\/Date(1324757832735+0700)\/",    "Value":5  },  {    "__type":"BoolEvent:#WcfTest.Data",    "Id":45678,    "Timestamp":"\/Date(1324757832736+0700)\/",    "Value":true  }]

Note the Content-Type header also.

I've tried it with your code and it works perfectly (well, I've removed the Console.WriteLine and tested in debugger). All the class hierarchy is fine, all objects can be cast to their types. It works.

UPDATE

The JSON you've posted works with the following code:

[DataContract]public class SomeClass{  [DataMember]  public List<DataEvent> dataEvents { get; set; }}...[ServiceContract]public interface IDataService{  ...  [OperationContract]  [WebInvoke(UriTemplate = "SubmitDataEvents")]  void SubmitDataEvents(SomeClass parameter);}

Note that another high-level node is added to the object tree.

And again, it works fine with inheritance.

If the problem still remains, please post the code that you use to invoke the service, as well as exception details you get.

UPDATE 2

How strange... It works on my machine.

I use .NET 4 and VS2010 with the latest updates on Win7 x64.

I take your service contract, implementation and data contracts. I host them in a web application under Cassini. I have the following web.config:

<configuration>  <connectionStrings>    <!-- excluded for brevity -->  </connectionStrings>  <system.web>    <!-- excluded for brevity -->  </system.web>  <system.webServer>    <modules runAllManagedModulesForAllRequests="true"/>  </system.webServer>  <system.serviceModel>    <behaviors>      <serviceBehaviors>        <behavior name="">          <serviceMetadata httpGetEnabled="true" />          <serviceDebug includeExceptionDetailInFaults="false" />        </behavior>      </serviceBehaviors>      <endpointBehaviors>        <behavior name="WebBehavior">          <webHttp />        </behavior>      </endpointBehaviors>    </behaviors>    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />    <services>      <service name="WebApplication1.DataService">        <endpoint address="ws" binding="wsHttpBinding" contract="WebApplication1.IDataService"/>        <endpoint address="" behaviorConfiguration="WebBehavior"           binding="webHttpBinding"           contract="WebApplication1.IDataService">        </endpoint>        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>      </service>    </services>  </system.serviceModel></configuration>

Now I make the following POST by Fiddler2 (important: I've renamed the namespace of the derived types to match my case):

POST http://localhost:47440/Service1.svc/SubmitDataEvents HTTP/1.1User-Agent: FiddlerContent-Type: text/jsonHost: localhost:47440Content-Length: 336{    "DataEvents": [{        "__type": "IntEvent:#WebApplication1",        "Id": 12345,        "Timestamp": "\/Date(1324905383689+0000)\/",        "Value": 5    }, {        "__type": "BoolEvent:#WebApplication1",        "Id": 45678,        "Timestamp": "\/Date(1324905383689+0000)\/",        "Value": true    }]}

Then I have the following code in the service implementation:

public void SubmitDataEvents(DataPacket parameter){  foreach (DataEvent dataEvent in parameter.DataEvents)  {    var message = dataEvent.ToString();    Debug.WriteLine(message);  }}

Note that debugger shows the items details as DataEvents, but string representations and the first item in the details clearly show that all sub-types have been deserialized well:Debugger screenshot

And debug output contains the following after I hit the method:

IntEvent: 12345, 26.12.2011 20:16:23, 5BoolEvent: 45678, 26.12.2011 20:16:23, True

I've also tried running it under the IIS (on Win7) and everything works fine too.

I've had only the base type deserialized after I corrupted the packet by deleting one underscore from the __type field name. If I modify the value of __type, the call will crash during deserialization, it won't hit the service.

Here is what you could try:

  1. Make sure you don't have any debug messages, exceptions, etc (check Debug Output).
  2. Create a new clean web application solution, paste the required code and test if it works there. If it does, then your original project must have some weird configuration settings.
  3. In debugger, analyze System.ServiceModel.OperationContext.Current.RequestContext.RequestMessage.ToString() in the Watch window. It will contain the XML message translated from your JSON. Check if it is correct.
  4. Check if you have any pending updates for .NET.
  5. Try tracing WCF. Although it doesn't seem to emit any warnings for messages with wrong __type field name, it may happen that it will show you some hints for your issues reasons.

My RequestMessage

Seems like here is the track of the issue: while you have __type as element, I have it as attribute. Supposedly, your WCF assemblies have a bug in JSON to XML translation

<root type="object">  <DataEvents type="array">    <item type="object" __type="IntEvent:#WebApplication1">      <Id type="number">12345</Id>      <Timestamp type="string">/Date(1324905383689+0000)/</Timestamp>      <Value type="number">5</Value>    </item>    <item type="object" __type="BoolEvent:#WebApplication1">      <Id type="number">45678</Id>      <Timestamp type="string">/Date(1324905383689+0000)/</Timestamp>      <Value type="boolean">true</Value>    </item>  </DataEvents></root>

I've found the place where __type is processed. Here it is:

// from System.Runtime.Serialization.Json.XmlJsonReader, System.Runtime.Serialization, Version=4.0.0.0void ReadServerTypeAttribute(bool consumedObjectChar){  int offset;  int offsetMax;   int correction = consumedObjectChar ? -1 : 0;  byte[] buffer = BufferReader.GetBuffer(9 + correction, out offset, out offsetMax);   if (offset + 9 + correction <= offsetMax)   {    if (buffer[offset + correction + 1] == (byte) '\"' &&         buffer[offset + correction + 2] == (byte) '_' &&        buffer[offset + correction + 3] == (byte) '_' &&        buffer[offset + correction + 4] == (byte) 't' &&        buffer[offset + correction + 5] == (byte) 'y' &&         buffer[offset + correction + 6] == (byte) 'p' &&        buffer[offset + correction + 7] == (byte) 'e' &&         buffer[offset + correction + 8] == (byte) '\"')     {      // It's attribute!      XmlAttributeNode attribute = AddAttribute();       // the rest is omitted for brevity    }   } }

I've tried to find the place where the attribute is used to determine the deserialized type, but to no luck.

Hope this helps.


Thanks to Pavel Gatilov, I've now found the solution to this problem. I'll add it as separate answer here for anyone who may be caught out by this in future.

The problem is that the JSON deserializer doesn't seem to be very accepting of whitespace. The data in the packet that I was sending was "pretty printed" with line breaks and spaces to make it more readable. However, when this packet was deserialized, this meant that when looking for the "__type" hint, the JSON deserializer was looking at the wrong part of the packet. This meant that the type hint was missed and the packet was deserialized as the wrong type.

The following packet works correctly:

POST http://localhost:6463/DataService.svc/SubmitDataEvents HTTP/1.1User-Agent: FiddlerContent-Type: text/jsonHost: localhost:6463Content-Length: 233{"DataEvents":[{"__type":"IntEvent:#WebApplication1","Id":12345,"Timestamp":"\/Date(1324905383689+0000)\/","IntValue":5},{"__type":"BoolEvent:#WebApplication1","Id":45678,"Timestamp":"\/Date(1324905383689+0000)\/","BoolValue":true}]}

However, this packet doesn't work:

POST http://localhost:6463/DataService.svc/SubmitDataEvents HTTP/1.1User-Agent: FiddlerContent-Type: text/jsonHost: localhost:6463Content-Length: 343{    "DataEvents": [{        "__type": "IntEvent:#WebApplication1",        "Id": 12345,        "Timestamp": "\/Date(1324905383689+0000)\/",        "IntValue": 5    }, {        "__type": "BoolEvent:#WebApplication1",        "Id": 45678,        "Timestamp": "\/Date(1324905383689+0000)\/",        "BoolValue": true    }]}

These packets are exactly the same apart from the line breaks and spaces.