Ehsan Hafeez Ehsan Hafeez - 3 months ago 171
JSON Question

Property mapping issue with Json.Net and WCF Data Members

I am using Json.Net Nuget package to deserialze the Json object to C# concrete object. My Json object has keys with snake_casing "property_name" but my C# object is following camelCasing "propertyName". It's working fine when i deserialze the object but when i try to deserialize the same Json object from WCF service it didn't map to properties of C# class.

var eventMessage = JsonConvert.DeserializeObject<TestClass>( JsonMessage );


ServiceContract :

[ServiceContract]
public interface IService
{
[OperationContract]
[FaultContract( typeof( MessageUnAuthenticatedFault ))]
[WebInvoke( Method = "POST",
RequestFormat = WebMessageFormat.Json,
ResponseFormat = WebMessageFormat.Json,
UriTemplate = "Process" )]
string Process( string message );
}


Here is my DataContract

[DataContract]
public class TestClass
{
[DataMember]
public long Id { get; set; }

[DataMember]
[JsonProperty( PropertyName = "request_name" )]
public string RequestedName { get; set; }

[DataMember]
[JsonProperty( PropertyName = "request_type" )]
public string RequestType { get; set; }

[DataMember]
[JsonProperty( PropertyName = "request_key" )]
public string RequestKey { get; set; }

[DataMember]
[JsonProperty( PropertyName = "descriptive_name" )]
public string DescriptiveName { get; set; } = null;

[DataMember]
[JsonProperty( PropertyName = "owner_internal_id" )]
public string OwnerInternalId { get; set; }

[DataMember]
public string URL { get; set; }

[DataMember]
public Status Status { get; set; }

[DataMember]
public DateTime? Created { get; set; }

[DataMember]
public DateTime? Modified { get; set; }

}


Json Object look like this:

{"generated": "2016-08-04T08:06+0000", "request_type": "test", "request_key": "1111", "descriptive_name": "Description", "owner_internal_id": "1213456", "Created":"2016-08-04T08:00"}


Why Json.Net is not properly mapping the property in case of WCF service ?

P.S: My service only support JSON request and response.

Regards

dbc dbc
Answer

Update

Now that you have included your service contract in the question, I see there is a more fundamental problem. WCF is for exchanging Data Contract objects, and is format-agnostic between XML and JSON. It's so agnostic that there's no straightforward way to access the underlying raw request or response streams. Instead WCF insists on doing the binding for you. Since your uploaded JSON is not a JSON primitive, trying to bind it to string message fails.

Instead, you need to define your service along the lines of

[ServiceContract]
public interface IService
{
    [OperationContract]
    [FaultContract( typeof( MessageUnAuthenticatedFault ))]
    [WebInvoke( Method = "POST",
        RequestFormat = WebMessageFormat.Json,
        ResponseFormat = WebMessageFormat.Json,
        UriTemplate = "Process" )]
    ResponseClass Process( TestClass message );
}

Where ResponseClass is whatever you want to return. Take a look at WCF rest service to get post JSON data and retrieve JSON data with DataContract for an example.

If you really need the underlying post stream to do manual parsing, you might try the techniques from WCF REST Service JSON Post data and WCF + REST: Where is the request data?.

Original Answer

WCF does not use Json.NET for JSON serialization. It uses DataContractJsonSerializer. Thus annotating your types with [JsonProperty] will have no effect. Instead, you need to set DataMemberAttribute.Name, i.e.:

[DataContract]
public class TestClass
{
    [DataMember]
    public long Id { get; set; }

    [DataMember(Name = "request_name")]
    public string RequestedName { get; set; }

    [DataMember(Name = "request_type")]
    public string RequestType { get; set; }

    [DataMember(Name = "request_key")]
    public string RequestKey { get; set; }

    [DataMember(Name = "descriptive_name")]
    public string DescriptiveName { get; set; }

    [DataMember(Name = "owner_internal_id")]
    public string OwnerInternalId { get; set; }

    [DataMember]
    public string URL { get; set; }

    [DataMember]
    public Status Status { get; set; }

    [DataMember]
    public DateTime? Created { get; set; }

    [DataMember]
    public DateTime? Modified { get; set; }
}

To test standalone JSON deserialization using this serializer, you may use the following helper methods:

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

    public static string SerializeJson<T>(T obj, DataContractJsonSerializer serializer = null)
    {
        serializer = serializer ?? new DataContractJsonSerializer(obj.GetType());
        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 T DeserializeJson<T>(string json, DataContractJsonSerializer serializer = null)
    {
        serializer = serializer ?? new DataContractJsonSerializer(typeof(T));
        using (var stream = GenerateStreamFromString(json))
        {
            var obj = serializer.ReadObject(stream);
            return (T)obj;
        }
    }
}

And then do:

var test = DataContractJsonSerializerHelper.DeserializeJson<TestClass>(jsonString);

You must also format DateTime properties in Microsoft's preferred "/Date(1329159196126-0500)/" format, or use a surrogate for deserialization to do manual parsing. See DataContractJsonSerializer - Deserializing DateTime within List and WCF Extensibility – Serialization Surrogates for details, and ASP.NET AJAX: Inside JSON date and time string for background. Or just use a surrogate property for each DateTime, e.g.:

    [IgnoreDataMember]
    public DateTime? Created { get; set; }

    [DataMember(Name = "Created")]
    string CreatedString
    {
        get
        {
            if (Created == null)
                return null;
            // From https://stackoverflow.com/questions/114983/given-a-datetime-object-how-do-i-get-a-iso-8601-date-in-string-format
            return Created.Value.ToString("s", System.Globalization.CultureInfo.InvariantCulture);
        }
        set
        {
            if (string.IsNullOrEmpty(value))
                Created = null;
            else
                Created = DateTime.Parse(value);
        }
    }

    [DataMember]
    public DateTime? Modified { get; set; }
}

If you really want to use Json.NET with WCF for deserialization, you will need to do some work. See C# WCF REST - How do you use JSON.Net serializer instead of the default DataContractSerializer? and, possibly, Using Custom WCF Body Deserialization without changing URI Template Deserialization.

For a rundown of JSON serializers used by various Microsoft frameworks see Json serializers in ASP.NET and other.

Comments