Deserializing dates with dd/MM/yyyy format using Json.Net

C#Jsonjson.net

C# Problem Overview


I'm trying to deserialize an object from JSON data to a C# class (I'm using Newtonsoft Json.NET). The data contains dates as string values like 09/12/2013 where the format is dd/MM/yyyy.

If I call JsonConvert.DeserializeObject<MyObject>(data), dates are loaded to the DateTime property of the C# class with the MM/dd/yyyy format, this causes the date value to be 12 September 2013 (instead of 9 December 2013).

Is it possible to configure JsonConvert to get the date in the correct format?

C# Solutions


Solution 1 - C#

You can use an IsoDateTimeConverter and specify the DateTimeFormat to get the result you want, e.g.:

MyObject obj = JsonConvert.DeserializeObject<MyObject>(jsonString, 
                   new IsoDateTimeConverter { DateTimeFormat = "dd/MM/yyyy" });

Demo:

class Program
{
    static void Main(string[] args)
    {
        string json = @"{ ""Date"" : ""09/12/2013"" }";

        MyObject obj = JsonConvert.DeserializeObject<MyObject>(json, 
            new IsoDateTimeConverter { DateTimeFormat = "dd/MM/yyyy" });

        DateTime date = obj.Date;
        Console.WriteLine("day = " + date.Day);
        Console.WriteLine("month = " + date.Month);
        Console.WriteLine("year = " + date.Year);
    }
}

class MyObject
{
    public DateTime Date { get; set; }
}

Output:

day = 9
month = 12
year = 2013

Solution 2 - C#

The starting sections are about NewtownSoft converter, and the reset is about .Net Core Json Serializer, since there was no Microsoft serializer when i first wrote this answer

Note: NewtownSoft and Microsoft have so many overlapping names, make sure you use the right namespaces

Newtownsoft Serializer:

Multi Format Support:

This is what i use:

public class CustomDateTimeConverter : IsoDateTimeConverter
{
    public CustomDateTimeConverter()
    {
        base.DateTimeFormat = "dd/MM/yyyy";
    }
}

then you will do this:

public class MyObject
{
    [JsonConverter(typeof(CustomDateTimeConverter))]
    public DateTime Date {get;set;}
}

and then deserialize in using any normal way you did before...

MyObject obj = JsonConvert.DeserializeObject<MyObject>(json);

Alternative

Other way would be same to what @pimbrouwers said:

public class MyObject
{
    [JsonProperty("Date")] //Naturally Case Sensetive
    private string dateJson {get;set;}
    // it would be good to look at @pimbrouwers answer and use nullable
    [JsonIgnore]
    public DateTime Date
    {
        get
        {
            return DateTime.ParseExact(dateJson,"dd/MM/yyyy",CultureInfo.InvariantCulture);
        }
        set
        {
            dateJson = value.ToString("dd/MM/yyyy");
        }
    }

}

Custom DateTimeConverter

Also i just write this, Custom DateTime Converter that would match your custom out of scope format or calendar

public class CustomDateTimeConverterJalali : DateTimeConverterBase
{
    //I had no use for WriteJson section, i just wrote it, so i do not guarantee it working
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        if (value == null)
        {
            writer.WriteNull();
            return;
        }

        var nullableType = Nullable.GetUnderlyingType(value.GetType());
        var isNullable = nullableType != null;

        DateTime date;
        if (isNullable)
            date = ((DateTime?) value).Value;
        else
            date = (DateTime) value;


        PersianCalendar pc = new PersianCalendar();

        writer.WriteValue(pc.GetYear(date) + "/" + pc.GetMonth(date) + "/" + pc.GetDayOfMonth(date));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        //this should likely be null, but since the provider json returned empty string, it was unavoidable... (i'm not sure what we will read using reader, if data is actually null on the json side, feel free to experiment 
        if (string.IsNullOrWhiteSpace((string) reader.Value))
        {
            return null;
        }

        var strDate = reader.Value.ToString();

        PersianCalendar pc = new PersianCalendar();
        var dateParts = strDate.Split('/');

        DateTime date = pc.ToDateTime(int.Parse(dateParts[0]), int.Parse(dateParts[1]), int.Parse(dateParts[2]),
            0, 0, 0, 0);

        return date;
    }

    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(DateTime);//DateTime=>true | DateTime?=>true
    }
}

Note:

The other ways you provide are one time configured and may, be simpler, and can be useful at most of times,... but here, the provider, provide me with a service, that provide dates in two format in each object, and one of these object provide two date each one in different calendar... so it's good to know this two ways i provide in here

Microsoft Serializer:

[ASP.NET CORE MVC] Custom DateTimeConverter (Via default JSON serializer):

Note that Microsoft implementation of JSON converter is different that NewtownSoft implementation. I hope NewtownSoft flag won't come down soon, as they put all their life on it, but people tend to use library of those in row of power, so here it is.

Note also that, the Microsoft implementation requires you to implement each type of data includes Nullable and non-Nullable separately.

Here is how I did it, I create a base class include all shared stuff, and then create a simple derived one for each version.

The Base:

using System;
using System.Buffers;
using System.Buffers.Text;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Charter724.Helper.JsonConverter.Microsoft
{
    /// <summary>
    /// Base Custom Format DateTime Handler <br/>
    /// using System.Text.Json.Serialization;
    /// </summary>
    [SuppressMessage("ReSharper", "RedundantBaseQualifier")]
    public class MsBaseDateTimeConverter<T> : JsonConverter<T>
    {
        private const string DefaultDateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.FFFFFFFK";

        private readonly string _format;
        private readonly CultureInfo _culture;
        private readonly DateTimeStyles _dateTimeStyles;

        public MsBaseDateTimeConverter(string format, CultureInfo culture = null, DateTimeStyles dateTimeStyles= DateTimeStyles.RoundtripKind)
        {
            _format = format;

            if (culture == null)
            {
                _culture = CultureInfo.CurrentCulture;
            }

            _dateTimeStyles = dateTimeStyles;

        }

        public override bool CanConvert(Type typeToConvert)
        {
            if (typeToConvert == typeof(DateTime) || typeToConvert == typeof(DateTime?))
            {
                return true;
            }

            return false;
        }

        public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            bool nullable = ReflectionUtils.IsNullableType(typeToConvert);
            if (reader.TokenType == JsonTokenType.Null)
            {
                if (!nullable)
                {
                    throw new JsonException();
                }

                return default;
            }

            if (_format != null)
            {
                if (DateTime.TryParseExact(reader.GetString(), _format, _culture, _dateTimeStyles,
                    out var dtValue))
                {
                    return (T) (object) dtValue;
                }

                throw new JsonException();
            }
            else
            {
                // try to parse number directly from bytes
                ReadOnlySpan<byte> span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
                if (Utf8Parser.TryParse(span, out DateTime dtValue, out int bytesConsumed) &&
                    span.Length == bytesConsumed)
                    return (T) (object) dtValue;

                // try to parse from a string if the above failed, this covers cases with other escaped/UTF characters
                if (DateTime.TryParse(reader.GetString(), out dtValue))
                    return (T) (object) dtValue;

                return (T) (object) reader.GetDateTime();
            }
        }

        public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
        {
            if (value != null)
            {
                if (value is DateTime dateTime)
                {
                    if ((_dateTimeStyles & DateTimeStyles.AdjustToUniversal) == DateTimeStyles.AdjustToUniversal
                        || (_dateTimeStyles & DateTimeStyles.AssumeUniversal) == DateTimeStyles.AssumeUniversal)
                    {
                        dateTime = dateTime.ToUniversalTime();
                    }

                    var text = dateTime.ToString(_format ?? DefaultDateTimeFormat, _culture);
                    writer.WriteStringValue(text);
                }
                else
                {
                    throw new JsonException();
                }
                return;
            }

            writer.WriteNullValue();

        }
    }
}

The derived one for non-nullable:

using System;
using System.Diagnostics.CodeAnalysis;

namespace Charter724.Helper.JsonConverter.Microsoft
{
    /// <summary>
    /// Format: yyyy-MM-dd - NOT NULL <br/>
    /// Microsoft <br/>
    /// using System.Text.Json.Serialization;
    /// </summary>
    [SuppressMessage("ReSharper", "RedundantBaseQualifier")]
    public class MsCustomDateTimeConverter : MsBaseDateTimeConverter<DateTime>
    {
        public MsCustomDateTimeConverter():base("yyyy-MM-dd")
        {
            //base.DateTimeFormat = "yyyy-MM-dd";
        }
    }
}

The derived one for nullable:

using System;
using System.Diagnostics.CodeAnalysis;

namespace Charter724.Helper.JsonConverter.Microsoft
{
    /// <summary>
    /// Format: yyyy-MM-dd - NULLABLE <br/>
    /// Microsoft <br/>
    /// using System.Text.Json.Serialization;
    /// </summary>
    [SuppressMessage("ReSharper", "RedundantBaseQualifier")]
    public class MsCustomDateTimeConverterNullable : MsBaseDateTimeConverter<DateTime?>
    {
        public MsCustomDateTimeConverterNullable():base("yyyy-MM-dd")
        {
            //base.DateTimeFormat = "yyyy-MM-dd";
        }
    }
}

Usage Details:

public class MyObject
{
    [System.Text.Json.Serialization.JsonConverter(typeof(MsCustomDateTimeConverter))]
    public DateTime Date { set; get; }
}

Alternative:

I didn't test if microsoft version also support private members with JsonProperty or not, but since EF Core failed in this matter, I just wanted to note the matter, in case it didn't worked.

public class MyObject
{
    [JsonProperty("Date")] //Naturally Case Sensetive
    private string dateJson {get;set;}
    // it would be good to look at @pimbrouwers answer and use nullable
    [JsonIgnore]
    public DateTime Date
    {
        get
        {
            return DateTime.ParseExact(dateJson,"dd/MM/yyyy",CultureInfo.InvariantCulture);
        }
        set
        {
            dateJson = value.ToString("dd/MM/yyyy");
        }
    }

}

Parallel Use of Both Serialization methods:

Converter in Two Environment (MVC Core Default Serializer & NewtownSoft)

To Use Converter In two environment using same model, all you have to do, is to apply both attribute from NewtownSoft and Default serializer, these two won't interfere and work fine. Just Make sure your interfaces are correct.

public class MyObject
{
    [System.Text.Json.Serialization.JsonConverter(typeof(MsCustomDateTimeConverter))]
    [Newtonsoft.Json.JsonConverter(typeof(NsCustomDateTimeConverter))]
    public DateTime Date { set; get; }
}

Solution 3 - C#

using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

var dateTimeConverter = new IsoDateTimeConverter { DateTimeFormat = "dd/MM/yyyy" };
myObject obj = JsonConvert.DeserializeObject<myObject>(myJSONString, dateTimeConverter);

Solution 4 - C#

You can also configure it in JsonSerializer:

var serializer = new JsonSerializer
{
    DateFormatString = "dd/MM/yyyy"
};

Solution 5 - C#

Add culture in WebConfig:

<system.web>
   <globalization culture="pt-BR" uiCulture="pt-BR" enableClientBasedCulture="true"/>
</system.web>

Then add the code snippet below in the WebApiConfig.cs file

var jsonFormatter =  GlobalConfiguration.Configuration.Formatters.JsonFormatter;
JsonSerializerSettings jSettings = new JsonSerializerSettings()
{
   Culture = System.Globalization.CultureInfo.CurrentCulture
};

jsonFormatter.SerializerSettings = jSettings;

The culture pt-BR works with the default dd-MM-yyyy, if you do not want to put the culture in WebConfig, you can create an instance only for that object.

Solution 6 - C#

According to newtonsoft you can use JsonSerializerSettings Class with

> DateFormatString

field . I do also at my projects at same way belowed.

string json = @"[
 '7 December, 2009',
 '1 January, 2010',
 10 February, 2010'
]";

IList<DateTime> dateList = JsonConvert.DeserializeObject<IList<DateTime>>(json, new 
JsonSerializerSettings
{
    DateFormatString = "d MMMM, yyyy"
});

foreach (DateTime dateTime in dateList)
{
   Console.WriteLine(dateTime.ToLongDateString());
}
// Monday, 07 December 2009
// Friday, 01 January 2010
// Wednesday, 10 February 2010

Solution 7 - C#

In my experience the simplest and easiest solution is always to add a string property on your CLR object (MyObject) in your case for JSON.Net to use. Also on your object you place a DateTime property, as a getter only, which will parse the datetime string you've deserialized using JSON.Net in the format you expect.

This lets you use the deserializer out of the box, and rely on good ol' fashioned c# to get the job done. No muss, no fuss.

public class MyObject
{
    public string dtStr { get; set; }
    public DateTime? dt
    {
        get
        {
            DateTime? d = null;

            if (!string.IsNullOrWhiteSpace(dtStr) && DateTime.TryParseExact(dtStr, "dd/mm/yyyy", CultureInfo.InvariantCultureDateTimeStyles.None, out d)
            {
                return d;
            }

            return d;
        }
    }
}

Solution 8 - C#

Actually none of them mentioned above solved my problem entirely. I find out that if any of my column data is null then NewtonSoft.JSON will suffer. My case was I had a DataSet serialized from service and deserialized in UI without any settings provided. What I did basically add below settings both in service(JsonConvert.SerializeObject) and in UI(JsonConvert.DeserializeObject)

 var settings = new JsonSerializerSettings
 {
       Culture = new System.Globalization.CultureInfo("tr-TR"),
       DateFormatString = "dd.MM.yyyy",
       NullValueHandling = NullValueHandling.Ignore
 };

Edit: When i did the above, Newtonsoft will change my columns orders while deserializing. So instead of these settings I just change serialize settings in service layer with below code then everything just begins to work as i expected.

string serializedDataSet= JsonConvert.SerializeObject(dataset, Formatting.Indented, new IsoDateTimeConverter { DateTimeFormat = "dd.MM.yyyy" });

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiongirtriView Question on Stackoverflow
Solution 1 - C#Brian RogersView Answer on Stackoverflow
Solution 2 - C#Hassan FaghihiView Answer on Stackoverflow
Solution 3 - C#FoyView Answer on Stackoverflow
Solution 4 - C#Ufuk HacıoğullarıView Answer on Stackoverflow
Solution 5 - C#Fco JuniorView Answer on Stackoverflow
Solution 6 - C#nzrytmnView Answer on Stackoverflow
Solution 7 - C#pimView Answer on Stackoverflow
Solution 8 - C#chetyView Answer on Stackoverflow