Search code examples
asp.netjsonasp.net-web-apijson.netstripe.net

Json.NET IsoDateTimeConverter not working with classes from Stripe.Net library


I'm using ASP.NET Web API. I have the following code and the dates in the response IEnumerable<StripeInvoice> aren't being converted to ISO 8601 format when serializing to JSON. They are in the Microsoft .NET format like this /Date(1412657602)/. Using IsoDateTimeConverter() should convert to ISO 8601 format such as 2011-07-14T19:43:37+0100. Why isn't this working for IEnumerable<StripeInvoice>?

IEnumerable<StripeInvoice> response = service.List();            
string json = JsonConvert.SerializeObject(response, new IsoDateTimeConverter());

EDIT

Here is the full method from my Web API controller.

public IEnumerable<StripeInvoice>  GetUserInvoices(string id)
{
    var options = new StripeInvoiceListOptions()
    {
        CustomerId = id
    };

    // Using https://github.com/jaymedavis/stripe.net stripe library
    var invoiceService = new StripeInvoiceService();
    invoiceService.ApiKey = "key";            
    IEnumerable<StripeInvoice> response = invoiceService.List(options);       

    // test 
    IEnumerable<SomeObject> objs = new SomeObject[] { 
    new SomeObject() { SomeDate = DateTime.Now.AddDays(-1) },
        new SomeObject() { SomeDate = DateTime.Now }
    };

    // return objs;  // this serialized the dates properly into  ISO 8601
    return response;  // this doesn't serialize to ISO 8601.  It remains in /Date(142657602)/
}

Solution

  • The problem is that the StripeInvoice class in the Stripe.Net library is marked up with [JsonConverter] attributes such that all DateTime properties use a StripeDateTimeConverter. That converter always writes out dates in the form /Date(1412657602)/, as you can see in the source code. Presumably, this is to allow the class to work correctly with the Stripe REST API, which clearly does not use ISO 8601 dates.

    You have a few options to solve this:

    1. The most straighforward option is simply to copy the invoice data from the StripeInvoice to your own version of Invoice class and return that from your Web API instead of passing the StripeInvoice directly through to consumers. Honestly, this is probably your best option, because it separates the concerns of your data source (Stripe) from the concerns of your own API. You could potentially use something like AutoMapper to help copy the data.
    2. It should be possible to create a custom IContractResolver to dynamically replace the StripeDateTimeConverter in the Stripe.Net classes with an IsoDateTimeConverter when you serialize them in your Web API (see update below).
    3. The Stripe.Net library is open source; you could modify the code for your own needs such that you can turn the StripeDateTimeConverter on or off depending on context. This is arguably the worst option of the three, because every time the library gets updated you would have to reapply your custom changes.

    UPDATE

    If you are interested in the second approach, here is a possible implementation for the contract resolver:

    class ForceIsoDateTimeResolver : DefaultContractResolver
    {
        private JsonConverter dateConverter = new IsoDateTimeConverter();
    
        protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
        {
            JsonProperty prop = base.CreateProperty(member, memberSerialization);
            // For any DateTime property on any class, replace the
            // existing converter with an IsoDateTimeConverter.
            if (prop.PropertyType == typeof(DateTime) || 
                prop.PropertyType == typeof(DateTime?))
            {
                prop.Converter = dateConverter;
            }
            return prop;
        }
    }
    

    To install this resolver into your Web API project, add the following to the Register method of the WebApiConfig class (in the App_Start folder of your Web API project):

    JsonSerializerSettings settings = config.Formatters.JsonFormatter.SerializerSettings;
    settings.ContractResolver = new ForceIsoDateTimeResolver();