WCF JSON Deserialization Issues with Whitespace

2019-02-26 03:50发布

问题:

I have a WCF REST service that accepts a custom DataContract argument as JSON which may either by the super-type or a sub-type. When I pass JSON containing extra whitespace, the object always deserializes as the super-type. When I strip all whitespace from the JSON, the object deserializaes as the sub-type.

Here is an example:

[DataContract]
[KnownType(typeof(SubClass))]
public class SuperClass
{
    [DataMember]
    public string Message { get; set; }
}

[DataContract]
public class SubClass : SuperClass
{
    [DataMember]
    public string Extra { get; set; }
}

[ServiceContract]
public interface IService1
{
    [OperationContract]
    [WebInvoke]
    void LogMessage(SuperClass arg);
}

public class Service1 : IService1
{
    public void LogMessage(SuperClass arg)
    {
        if (arg is SubClass)
        {
            Debug.WriteLine("SubClass");
        }
        else if (arg is SuperClass)
        {
            Debug.WriteLine("SuperClass");
        }
    }
}

If I send the following message, the service will print SuperClass:

POST http://localhost:5763/Service1.svc/LogMessage HTTP/1.1
User-Agent: Fiddler
Content-Type: text/json
Host: localhost:5763
Content-Length: 86

{    "__type":"SubClass:#WhitespaceTest",    "Message":"Message",    "Extra":"Extra" }

I get the same result if I "pretty print" the packet as well so that the JSOn is split over multiple lines. However, the service will print SubClass if I strip the whitespace as follows:

POST http://localhost:5763/Service1.svc/LogMessage HTTP/1.1
User-Agent: Fiddler
Content-Type: text/json
Host: localhost:5763
Content-Length: 73

{"__type":"SubClass:#WhitespaceTest","Message":"Message","Extra":"Extra"}

I've debugged the output of System.ServiceModel.OperationContext.Current.RequestContext.RequestMessage.ToString() and noticed that the XML generated from the JSON is different between the 2 packets:

<!-- First packet, deserializes to SuperClass -->
<root type="object">
    <__type type="string">SubClass:#WhitespaceTest</__type>
    <Message type="string">Message</Message>
    <Extra type="string">Extra</Extra>
</root>

<!-- Second packet, deserializes to SubClass -->
<root type="object" __type="SubClass:#WhitespaceTest">
    <Message type="string">Message</Message> 
    <Extra type="string">Extra</Extra> 
</root>

So, it seems as though the whitespace confuses the JSON deserializer. Does anyone know why this happens and what I can do about it?

回答1:

This is a known issue which was fixed in the 4.5 framework. Unfortunately you essentially need to strip the white spaces from the front of the object if you want to use polymorphism in the current framework version. One way to do that is to read / write the JSON using a reader / writer created by JsonReaderWriterFactory, as it will strip the white spaces (or any pretty printing) around the elements.

public class StackOverflow_8661714
{
    [DataContract(Name = "SuperClass", Namespace = "WhitespaceTest")]
    [KnownType(typeof(SubClass))]
    public class SuperClass
    {
        [DataMember]
        public string Message { get; set; }
    }

    [DataContract(Name = "SubClass", Namespace = "WhitespaceTest")]
    public class SubClass : SuperClass
    {
        [DataMember]
        public string Extra { get; set; }
    }
    public static void Test()
    {
        string originalJson = "{    \"__type\":\"SubClass:WhitespaceTest\",    \"Message\":\"Message\",    \"Extra\":\"Extra\" }";
        byte[] originalJsonBytes = Encoding.UTF8.GetBytes(originalJson);
        MemoryStream writeStream = new MemoryStream();
        XmlDictionaryWriter jsonWriter = JsonReaderWriterFactory.CreateJsonWriter(writeStream, Encoding.UTF8, false);
        XmlDictionaryReader jsonReader = JsonReaderWriterFactory.CreateJsonReader(originalJsonBytes, 0, originalJsonBytes.Length, XmlDictionaryReaderQuotas.Max);
        jsonWriter.WriteNode(jsonReader, true);
        jsonWriter.Flush();
        Console.WriteLine(Encoding.UTF8.GetString(writeStream.ToArray()));
        writeStream.Position = 0;

        DataContractJsonSerializer dcjs = new DataContractJsonSerializer(typeof(SuperClass), new Type[] { typeof(SubClass) });
        object o = dcjs.ReadObject(writeStream);
        Console.WriteLine(o);
    }
}


回答2:

Unfortunately, I have no good answer to this. We ran into the same issue and contacted Microsoft about it. They insisted that what they were doing was okay because of performance concerns, which is laughable considering how the entire pipeline works.

So our experience has been that whitespace in many places causes issues. Also, if you put the __type field anywhere but the first, you'll get the supertype.

My advice would be to look at other solutions to JSON. Unfortunately I'm not up on the alternatives or I'd suggest something.

Edit: As carlosfigueira points out, this has apparently been fixed in 4.5. I have not tried it there as of yet.