WCF Client how can I deserialize an incompatible date format from the JSON response?
Asked Answered
M

2

7

I have scoured the Net for info on this, but most of the results are about creating WCF services or situations where the service is under your control.

I am building a WCF client proxy for a RESTful JSON service which is out of my control. I am using the basic ServiceContract/DataContract pattern, and trying to let the framework do as much of the work as possible.

Mostly this is working fine, but all the datetime fields coming from this external service are in a particular format, e.g.

{"SomeObject": 
    {"details":"blue and round", "lastmodified":"2013/01/02 23:14:55 +0000"}
}

So I get an error:

There was an error deserializing the object of type MyNamespace.SomeObject. DateTime content '2013/01/02 23:14:55 +0000' does not start with '/Date(' and end with ')/' as required for JSON.'.

My datacontract is:

namespace Marshmallow.WebServices.ServiceModels
{
    [DataContract]
    public class SomeObject
    {
        [DataMember(Name = "details")]
        public string Details { get; set; }

        [DataMember(Name = "lastmodified")]
        public DateTime LastModified { get; set; }
    }
}

My servicecontract is:

[ServiceContract]
public interface ICoolExternalApi
{
    [OperationContract]
    [WebGet(UriTemplate = "/something.json",
        ResponseFormat = WebMessageFormat.Json,
        BodyStyle = WebMessageBodyStyle.Wrapped)]
    [return: MessageParameter(Name = "SomeObject")]
    SomeObject GetAccount();
}

What I want to know is, where can I stick some code to define how WCF should deserialize the lastmodified field (make a DateTime object out of the string)?

Or better yet, define how to deserialize all DateTime DataMembers for all my DataContracts. I do not want a lot of repeated code.

I also do not want to resort to some third-party deserializer nor do I want to start putting everything else through a custom deserialization method, if it is avoidable.

Mapes answered 4/1, 2013 at 14:35 Comment(2)
Deserializing to a string does have the limitations you've noted. It will work but is far from elegant. Setting up a IDispatchMessageInspector is a little more effort but should be cleaner.Trenchant
I feel like using an IDispatchMessageInspector is not really that elegant. A) Regex is slow and not very scaleable. B) It is an extra parse of the whole json body. C) I would have to convert the datetime strings into 'that' format "\/Date(1297293089984-0800)\/" which would then be parsed again to popluate the DataMember (double processing). D) It's a hack. Surely there must be another WCF feature which I don't understand (OnDeserializing or something?) which is designed to deal with these situations?Mapes
M
1

So far this is the best I have come up with:

I have an internal string Extension method:

internal static class DeserializationHelper
{
    internal static DateTime GetDeserializedDateTime(this string @string)
    {
        if (string.IsNullOrEmpty(@string)) return default(DateTime);
        //insert complex custom deserialization logic here
        return DateTime.Parse(@string);
    }

}

This is the DataMember setup:

[DataMember(Name = "lastmodified")]
internal string _LastModified 
{
    set { LastModified = value.GetDeserializedDateTime(); }
    //getter is not needed for receiving data but WCF requires one
    get { return default(string); }
}

public DateTime LastModified { get; private set; }

If you wanted to use this DataContract for sending data (make this a writeable Property), you would have to write a DateTime Extension method (GetSerializedDateString), expand the setters/getters and introduce private members as go-betweens.

It smells of cut and paste, and it does not take advantage of any WCF framework features. What would Bill Gates do?

Mapes answered 30/3, 2013 at 9:37 Comment(0)
T
2

Two things I can think of:

  1. Change LastModified to be a string and then convert it to a Datetime object yourself. It would mean exposing two properties for the same data on your object though.
  2. Write a IDispatchMessageInspector to intercept the message before the deserialization occurs and massage the raw message using regex. It would allow a one stop solution for all dates in your service.
Trenchant answered 5/1, 2013 at 2:34 Comment(1)
Thanks for your input. 1) So far this is what I am doing, while keeping the string property internal. It requires extra coding for every DateTime DataMember, however, so it smells pretty bad to me. 2) While this would work, it feels like a hack, and introduces overhead which would compromise scaleability.Mapes
M
1

So far this is the best I have come up with:

I have an internal string Extension method:

internal static class DeserializationHelper
{
    internal static DateTime GetDeserializedDateTime(this string @string)
    {
        if (string.IsNullOrEmpty(@string)) return default(DateTime);
        //insert complex custom deserialization logic here
        return DateTime.Parse(@string);
    }

}

This is the DataMember setup:

[DataMember(Name = "lastmodified")]
internal string _LastModified 
{
    set { LastModified = value.GetDeserializedDateTime(); }
    //getter is not needed for receiving data but WCF requires one
    get { return default(string); }
}

public DateTime LastModified { get; private set; }

If you wanted to use this DataContract for sending data (make this a writeable Property), you would have to write a DateTime Extension method (GetSerializedDateString), expand the setters/getters and introduce private members as go-betweens.

It smells of cut and paste, and it does not take advantage of any WCF framework features. What would Bill Gates do?

Mapes answered 30/3, 2013 at 9:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.