Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

json

c#

json.net

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?

like image 758
girtri Avatar asked Jan 21 '14 10:01

girtri


2 Answers

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 
like image 92
Brian Rogers Avatar answered Sep 19 '22 21:09

Brian Rogers


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; } } 
like image 29
deadManN Avatar answered Sep 21 '22 21:09

deadManN