JSON.NET Serialize DateTime.MinValue as null - c#

I'd like DateTime fields that are set to DateTime.MinValue returned by my Web API to be serialized to NULL instead of "0001-01-01T00:00:00".
I understand there's a way to get JSON.NET to omit fields that are set to default values, but I would prefer JSON.NET to specifically serialize DateTime MinValue / "0001-01-01T00:00:00" as null.
Is there a way to do this?

Create a custom converter which serializes DateTime.MinValue into null, and (if required) deserializes null into DateTime.MinValue:
public class MinDateTimeConverter : DateTimeConverterBase
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.Value == null)
return DateTime.MinValue;
return (DateTime)reader.Value;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
DateTime dateTimeValue = (DateTime)value;
if (dateTimeValue == DateTime.MinValue)
{
writer.WriteNull();
return;
}
writer.WriteValue(value);
}
}
You can then use attributes to add the converter to your data class, as shown in this example:
public class Example
{
[JsonConverter(typeof(MinDateTimeConverter))]
public DateTime ValueOne { get; set; }
[JsonConverter(typeof(MinDateTimeConverter))]
public DateTime ValueTwo { get; set; }
}
public static void Main(string[] args)
{
Example data = new Example();
data.ValueOne = DateTime.MinValue;
data.ValueTwo = DateTime.Now;
JsonSerializer serializer = new JsonSerializer();
using (StringWriter writer = new StringWriter())
{
serializer.Serialize(writer, data);
Console.Write(writer.ToString());
}
Console.ReadKey();
}
Console output:
{"ValueOne":null,"ValueTwo":"2016-10-26T09:54:48.497463+01:00"}

Custom DateTime Json Converter
public class DateTimeConverter : JsonConverter
{
public override void WriteJson(JsonWriter jsonWriter, object inputObject,JsonSerializer jsonSerializer)
{
// Typecast the input object
var dateTimeObject = inputObject as DateTime?;
// Set the properties of the Json Writer
jsonWriter.Formatting = Newtonsoft.Json.Formatting.Indented;
if(dateTimeObject == DateTime.MinValue)
jsonWriter.WriteValue((DateTime?)null);
else
jsonWriter.WriteValue(dateTimeObject);
}
public override object ReadJson(JsonReader reader,
Type objectType,
object existingValue,
JsonSerializer serializer)
{
DateTime? readValue = reader.ReadAsDateTime();
return (readValue == null) ? DateTime.MinValue : readValue;
}
public override bool CanConvert(Type objectType)
{
return typeof(DateTime?).IsAssignableFrom(objectType);
}
}
Usage:
public class DateTest
{
[JsonConverterAttribute(typeof(DateTimeConverter))]
public DateTime? MyDateTime { get; set;}
[JsonConverterAttribute(typeof(DateTimeConverter))]
public DateTime? MyDateTime1 { get; set; }
}
void Main()
{
DateTest dateTest = new DateTest
{
MyDateTime = DateTime.MinValue,
MyDateTime1 = DateTime.MaxValue
};
Console.WriteLine(JsonConvert.SerializeObject(dateTest));
}
Result:
{
"MyDateTime": null,
"MyDateTime1": "9999-12-31T23:59:59.9999999"
}

Not 100% clear on why the original poster wanted this and it's 5 years down the line but I too wanted to achieve this.
However, in my case the issue was that when I serialised my object it was inserting the DateTime fields because they hadn't been specified on the incoming object but were being returned as MinValue not null.
Thus when I deserialised at the other end these fields were showing as 'dirty' and being parsed.
In this case you can just amend your JsonSerializerSettings block to include 'ignore' in the default value handling:
string serialisedMessage = JsonConvert.SerializeObject(message, new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
DefaultValueHandling = DefaultValueHandling.Ignore
});
So I'm leaving this in case of future users who are in my position.

Related

Custom converter for deserialization not firing or not hitting break point in Web API

I have a converter like this
class MultiFormatDateConverter : JsonConverter
{
public List<string> DateTimeFormats { get; set; }
public override bool CanConvert(Type objectType)
{
return objectType == typeof(DateTime);
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
{
string dateString = (string)reader.Value;
DateTime date;
foreach (string format in DateTimeFormats)
{
// adjust this as necessary to fit your needs
if (DateTime.TryParseExact(dateString, format, CultureInfo.InvariantCulture, DateTimeStyles.None, out date))
return date;
}
throw new System.Text.Json.JsonException("Unable to parse \"" + dateString + "\" as a date.");
}
}
and here is the configuration
var settings = new JsonSerializerSettings();
settings.DateParseHandling = DateParseHandling.None;
settings.Converters.Add(new MultiFormatDateConverter
{
DateTimeFormats = new List<string> { "yyyyMMddTHHmmssZ", "yyyy-MM-ddTHH:mm","MMMM yyyy","dd/MM/yyyy","dd/MM/yy","MMM-yy","MMM yy"
}
});
and here is how I am calling it:
List<KipReport> rpt730 = JsonConvert.DeserializeObject<List<KipReport>>(responseBody, settings);
This is the JSON and class
[
{
"Name":"Alex",
"MonthWorked":"January 2021",
"LastEdtDate":"16/02/2021",
"LastEditBy":"san"
}
]
class KipReport
{
public string Name { get; set; }
public DateTime? MonthWorked { get; set; }
public DateTime? LastEditDate { get; set; }
}
Mine is a web API and here is the controller which calls the function. Please note it calls the function as Task.Run()
[HttpGet]
public async Task<IActionResult> Get()
{
await Task.Run(()=>_kReport.GetKReports());
return Accepted();
}
When executing it says
16/03/2021 is not a valid date format
Then I used this way for converting than a converter
var settings = new IsoDateTimeConverter { DateTimeFormat = "dd/MM/yyyy" };
Then error is with January 2021 is not a valid date
Does it means, it's not considering the converter??
Since I have a different format for dates I am using a converter.
So for Web API/Task.Run do we need to do anything specific for the Custom converter?
Your properties are of type DateTime? (i.e. nullable value types) so in CanConvert you must check for objectType == typeof(DateTime?) as well as objectType == typeof(DateTime). Then, in Read(), if the incoming objectType is typeof(DateTime?) you should return null in the event of a null JSON token.
The following fixed converter does this and also skips comments:
class MultiFormatDateConverter : JsonConverter
{
public List<string> DateTimeFormats { get; set; } = new ();
public override bool CanConvert(Type objectType) =>
objectType == typeof(DateTime) || objectType == typeof(DateTime?);
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) => throw new NotImplementedException();
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
{
if (reader.MoveToContent().TokenType == JsonToken.Null)
return objectType == typeof(DateTime?) ? null : throw new System.Text.Json.JsonException("Unable to parse null as a date.");
else if (reader.TokenType != JsonToken.String)
throw new System.Text.Json.JsonException("Unable to parse token \"" + reader.TokenType + "\" as a date.");
string dateString = (string)reader.Value;
foreach (string format in DateTimeFormats)
{
// adjust this as necessary to fit your needs
if (DateTime.TryParseExact(dateString, format, CultureInfo.InvariantCulture, DateTimeStyles.None, out var date))
return date;
}
throw new System.Text.Json.JsonException("Unable to parse \"" + dateString + "\" as a date.");
}
}
public static partial class JsonExtensions
{
public static JsonReader MoveToContent(this JsonReader reader)
{
while ((reader.TokenType == JsonToken.Comment || reader.TokenType == JsonToken.None) && reader.Read())
;
return reader;
}
}
Notes:
In your JSON you have a property named "LastEdtDate" while the corresponding c# property is LastEditDate. The JSON property name is missing the letter i in Edit and so will not get bound to the c# property. I assume this is a typo in the question, but if not, you will need to add [JsonProperty("LastEdtDate")] to LastEditDate.
Demo fiddle here.

Deserialize timestamp value in JSON to DateTime while Model Binding

I'm developing a Web API using the ASP.NET Core 2 and I have a Model class as following.
public class Model
{
int id { get; set; }
DateTime date { get; set; }
}
I am using JSON in the Request Body. Request Json is like
{
"id" : 1,
"date" : 1525261719
}
Binding this JSON data to the Model class like this in Controller
[HttpPost]
public async Task<IActionResult> PostEvent([FromBody] Model model)
{
// Some code here
}
I was not able to parse Unix timestamp into the DateTime type. I saw some examples like JSON Converter, IModelBinder nothing helps. As I was new to .NET world I don't know how to proceed for this issue.
Any help is much appreciated.
I had the same issue. I wrote this JsonConverter. Keep in mind that this is tailor-made for my specific situation.
public class UnixEpochTimeToDateTimeConverter: JsonConverter
{
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null) return null;
if (reader.TokenType != JsonToken.Integer) return null;
if (!reader.Path.Contains("time")) return null;
return long.TryParse(reader.Value.ToString(), out var epoch)
? DateTimeOffset.FromUnixTimeMilliseconds(epoch).DateTime
: DateTime.Now;
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(DateTime);
}
}
I found solution for this. I used ITypeConverter
// Converts timestamp to DateTime
public class DateTimeConverter : ITypeConverter<long?, DateTime?>
{
private readonly DateTime _epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
public DateTime? Convert(long? source, DateTime? destination, ResolutionContext context)
{
if (!source.HasValue) return null;
return _epoch.AddSeconds(source.Value);
}
}
// Converts DateTime to Timestamp
public class TimeStampConverter : ITypeConverter<DateTime?, long?>
{
private readonly DateTime _epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
public long? Convert(DateTime? source, long? destination, ResolutionContext context)
{
if (source == null) return null;
var result = (long)(source - _epoch).Value.TotalSeconds;
return result;
}
}
And I created a Map like this in startup.cs
AutoMapper.Mapper.Initialize(x =>
{
x.CreateMap<long?, DateTime?>().ConvertUsing<DateTimeConverter>();
x.CreateMap<DateTime?, long?>().ConvertUsing<TimeStampConverter>();
});
I used this couple of classes in my project and it is working fine. This may help anyone who is trying to achieve the same thing.

Date cannot be serialized or deserialized

I want to serialize and deserialize Nullable DateTime to/from JSON but I do not want to annotate it with JsonConverterAttribute. However, I would like to keep it at once place in JsonSerializerSettings not bloating DTOs with those attributes keeping DTOs clean as usual.
Here is DTO:
public class Post
{
public DateTime? Created { get; set; }
}
Here is Custom JsonConverter:
internal class EpochDateTimeConverter : Newtonsoft.Json.JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(DateTime).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var t = (long)Convert.ToDouble(reader.Value.ToString());
return t.FromUnixTime();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
long ticks;
DateTime valueAsDate = (DateTime)value;
if (valueAsDate != DateTime.MinValue)
{
if (value is DateTime)
{
var epoc = new DateTime(1970, 1, 1);
var delta = (valueAsDate) - epoc;
if (delta.TotalSeconds < 0)
{
throw new ArgumentOutOfRangeException("Unix epoc starts January 1st, 1970");
}
ticks = (long)delta.TotalSeconds;
}
else
{
throw new Exception("Expected date object value.");
}
writer.WriteValue(ticks);
}
}
}
Here is the minimal repro:
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
namespace NameSpaceSample
{
public class Post
{
public DateTime? Created { get; set; }
}
public class Program
{
public static void Main(string[] args)
{
var settings = new JsonSerializerSettings()
{
NullValueHandling = NullValueHandling.Ignore,
Converters = new List<JsonConverter>
{
new EpochDateTimeConverter()
}
};
string postAsJson = JsonConvert.SerializeObject(new Post { Created = DateTime.UtcNow }, settings);
Console.WriteLine(postAsJson);// {"Created":"2015-09-17T17:15:06.6160689Z"}
var json = "{\"Created\":1442510191}";
Post post = JsonConvert.DeserializeObject<Post>(json, settings);//Exception here
Console.ReadKey();
}
}
}
The exception thrown at that line is:
JsonReaderException:
Error reading date. Unexpected token: Integer. Path 'Created', line 1, position 21.
NOTE:
I know this can be resolved by just annotating it with JsonConverterAttribute as below but I don't want to do that for aforementioned reason.
public class Post
{
[JsonConverter(typeof(EpochDateTimeConverter))]
public DateTime? Created { get; set; }
}
Figured it out on my own. I just had to change CanConvert function to following:
public override bool CanConvert(Type objectType)
{
return objectType == typeof(DateTime) || objectType == typeof(DateTime?);
}
It is not very easy to find out. Putting my answer here to help others if they ever face this.
I give you my way from any object to another object so that you don't worry transfer anything ,thanks
public static T ConvertTo<T>(this object value)
{
T returnValue = default(T);
if (value is T)
{
returnValue = (T)value;
}
else
{
try
{
returnValue = (T)Convert.ChangeType(value, typeof(T));
}
catch (InvalidCastException)
{
returnValue = default(T);
}
}
return returnValue;
}
The CanConvert(Type objectType) method of the JsonConverter determines if that converter will be used for the current property that is being serialized/deserialized.
As the type of your property is DateTime? and that is not assignable from DateTime it returns false and the converter is then not being used.
You just need to change the method to the following:
public override bool CanConvert(Type objectType)
{
return typeof(DateTime?).IsAssignableFrom(objectType);
}

How can I control the converter used for dictionary entries?

I've successfully applied the JsonConverter attribute in my classes to govern the converter used for properties of objects in the past. Now, I have a dictionary with several DateTime entries which need to be converted in different formats. For example, have a look at the following dictionary:
var header = new Dictionary<string, object>
{
{"Id", reader.GetInt32(0)},
...
{"CreatedAt", reader.GetDateTime(4)},
...
{"StartDate", reader.GetDateTime(7)},
...
};
When serializing this dictionary, I wish a different format for CreatedAt and a different one for StartDate. CreatedAt should have the full ISO format, e.g. 2014-05-26T09:13:34.56 and StartDate should have only the date, e.g. 2014-05-26.
What is the preferred way of telling Json.NET how to format the entries of a dictionary?
Here is a way you can do it :
Create your own DateOnly class which encaspsulate a DateTime struct.
Implements your own type converter by extending Newtonsoft.Json.JsonConverter abstract class.
public class DateOnly
{
public DateTime Date { get; set; }
}
Conveter class
public class DateOnlyConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value is DateOnly)
{
writer.WriteValue(((DateOnly) value).Date.ToString("yyyy-MM-dd"));
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
string value = reader.ReadAsString();
try
{
return DateTime.ParseExact(value, "yyyy-MM-dd", CultureInfo.InvariantCulture);
}
catch (FormatException)
{
return null;
}
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof (DateOnly);
}
}
Add your date only class instead of DateTime struct
var header = new Dictionary<string, object>
{
{"Id", reader.GetInt32(0)},
...
{"CreatedAt", reader.GetDateTime(4)},
...
{"StartDate", new DateOnly { Date = reader.GetDateTime(7)}},
...
};
And finally
string isoJson = JsonConvert.SerializeObject(entry, new IsoDateTimeConverter(), new DateOnlyConverter ());

Deserialization issue for datetime field

What is the best way to deserialize an JSON
I have the following JSON
"_created" : {
"$dt": "2013-03-26T16:45:20Z"
}
and i want get field of object, like this - DataTime Created {get; set;}
Question for json.net http://james.newtonking.com/projects/json-net.aspx experts
Easiest way is creating JsonConvertor
public class MongoDbDateTimeConverter : DateTimeConverterBase
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jObject = JObject.Load(reader);
return jObject["$dt"].Value<DateTime>();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
and used it with property
[JsonConverter(typeof(MongoDbDateTimeConverter))]
public DateTime Created { get; set; }
You could use the JSON serializer/deserializer from NewtonSoft, worked ok for me.
Nuget package
MediaTypeFormatter example
Add following MediaTypeFormatter to the GlobalConfiguration like so:
GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);
GlobalConfiguration.Configuration.Formatters.Insert(0, new JsonFormatter());
MediaTypeFormatter:
public class JsonFormatter : MediaTypeFormatter
{
private const string WesternEuropeStandardTime = "W. Europe Standard Time";
private TimeZoneInfo timeZoneInfo;
public JsonFormatter()
{
SupportedMediaTypes.Add(new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
this.timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(WesternEuropeStandardTime);
}
public override bool CanReadType(Type type)
{
return true;
}
public override bool CanWriteType(Type type)
{
return true;
}
public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, System.Net.Http.HttpContent content, IFormatterLogger formatterLogger)
{
Task<object> task = Task<object>.Factory.StartNew(() =>
{
JsonSerializerSettings settings = new JsonSerializerSettings()
{
NullValueHandling = NullValueHandling.Ignore,
};
StreamReader sr = new StreamReader(readStream);
JsonTextReader jreader = new JsonTextReader(sr);
JsonSerializer ser = new JsonSerializer();
ser.Converters.Add(new DateTimeConverter(this.timeZoneInfo) { DateTimeFormat = "o" });
return ser.Deserialize(jreader, type);
});
return task;
}
public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, System.Net.Http.HttpContent content, System.Net.TransportContext transportContext)
{
Task task = Task.Factory.StartNew(() =>
{
JsonSerializerSettings settings = new JsonSerializerSettings()
{
NullValueHandling = NullValueHandling.Ignore,
};
string json = JsonConvert.SerializeObject(
value,
Formatting.Indented,
new JsonConverter[1] { new DateTimeConverter(this.timeZoneInfo) { DateTimeFormat = "o" } });
byte[] buf = System.Text.Encoding.Default.GetBytes(json);
writeStream.Write(buf, 0, buf.Length);
writeStream.Flush();
});
return task;
}
private class DateTimeConverter : IsoDateTimeConverter
{
private TimeZoneInfo timeZoneInfo;
public DateTimeConverter(TimeZoneInfo timeZoneInfo)
{
this.timeZoneInfo = timeZoneInfo;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
DateTime? date = value as DateTime?;
if (date.HasValue && DateTime.MinValue != date.Value && DateTime.MaxValue != date.Value)
{
TimeSpan timeZoneOffset = this.timeZoneInfo.GetUtcOffset(date.Value);
value = DateTime.SpecifyKind(date.Value - timeZoneOffset, DateTimeKind.Utc);
}
base.WriteJson(writer, value, serializer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
object result = base.ReadJson(reader, objectType, existingValue, serializer);
DateTime? date = result as DateTime?;
if (date.HasValue && DateTime.MinValue != date.Value && DateTime.MaxValue != date.Value)
{
TimeSpan timeZoneOffset = this.timeZoneInfo.GetUtcOffset(date.Value);
result = DateTime.SpecifyKind(date.Value + timeZoneOffset, DateTimeKind.Utc);
}
return result;
}
}
}
if you are using json.NET
try to serialize a datetime field or an object consisting of datetime field like this:
JsonSerializerSettings microsoftDateFormatSettings = new JsonSerializerSettings
{
DateFormatHandling = DateFormatHandling.MicrosoftDateFormat
};
string serializedObject= Newtonsoft.Json
.JsonConvert
.SerializeObject(data, microsoftDateFormatSettings);
it works without a glitch, if serialization has been done using JSON.NET :)
and then you can successfully deserialize it back
var myobject = Newtonsoft.Json.JsonConvert.DeserializeObject(serializedObject);
If you're asking about a JSON parser in C#, then that is more of a Google thing. Go search there. There are loads of them available.
If you're asking about how to parse this string into DateTime, then DateTime.TryParse() will help you. You can help it by supplied string format, something like yyyy-MM-ddTHH:mm:ss.

Categories

Resources