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.
Related
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.
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.
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);
}
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 ());
I have created a test console application that has a simple class named Other. As an exercise, I want to set the OtherString property to null when it is serialized. I know how I could do this with a custom ContractResolver. I need to be able to do this with a custom Converter too.
The first converter I wrote was simple and the way I thought it should be. However, it would throw a "Self referencing loop detected with type 'JsonContractandConvert.Models.Other'. Path ''." exception. After doing some reading, I made some changes and now I have a working converter. These changes are a lot more verbose, but work.
My question is why are these changes required and is there a better way to do this with a Converter?
Other Class:
[JsonConverter(typeof(OtherConverter))]
public class Other
{
public int Id { get; set; }
public string OtherString { get; set; }
public int OtherInt { get; set; }
public string OtherName
{
get
{
return "Other Name = " + this.OtherString;
}
}
}
First Attempt: (This one throws an exception)
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (!this.CanConvert(value.GetType())) return;
var entity = value as Other;
if (entity == null) return;
entity.OtherString = null;
serializer.Serialize(writer, entity);
}
Second Attempt: (This works as expected)
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (!this.CanConvert(value.GetType())) return;
var entity = value as Other;
if (entity == null) return;
entity.OtherString = null;
writer.WriteStartObject();
var props = entity.GetType().GetProperties();
foreach (var propertyInfo in props)
{
var ignorAttribute =
propertyInfo.CustomAttributes.FirstOrDefault(i => i.AttributeType == typeof(JsonIgnoreAttribute));
if (ignorAttribute != null) continue;
var tempVal = propertyInfo.GetValue(entity);
if (tempVal == null) continue;
writer.WritePropertyName(propertyInfo.Name);
serializer.Serialize(writer, tempVal);
}
}
writer.WriteEndObject();
}
Edit:
Here's the code from the console app that I'm using to test.
class Program
{
static void Main(string[] args)
{
var otherObj = new Other { Id = 123, OtherInt = 456, OtherString = "This is the other string"};
var json = JsonConvert.SerializeObject(otherObj, Formatting.Indented);
Console.WriteLine(json);
}
}
Answering my own questions... (I think I've finally got my head around Json Converters)
To answer my basic question as to why one way works and the other doesn't. I think the answer is just that the way that doesn't work is just wrong. From what I can tell, you need to use the writer object. If you don't it won't work. (There may be scenarios where this is not the case, but I never found one.)
If you merely want to remove a property from the Json, use [JsonIgnore]. (Simple right?)
If you want to change the value some how, you'll need to use a Converter. By using a Converter, you're taking responsibility for serializing what ever object the Converter handles. So if the object is a simple type like a String, or an Int it's really easy. If it's something more complex like an array or complex object, then it takes a little more planning.
Here's an example of a simple String Converter:
Class to serialize:
public class Account
{
public int Id { get; set; }
public string AccountName { get; set; }
[JsonIgnore]
public virtual Account DefaultAssignTo { get; set; }
public int? DefaultAssignToId { get; set; }
[JsonIgnore]
public virtual ICollection<Role> Roles { get; set; }
[JsonIgnore]
public virtual Other Other { get; set; }
public int? OtherId { get; set; }
[JsonConverter(typeof(StringConverter))]
public string OtherName
{
get
{
return "Name = " + this.AccountName;
}
}
}
Converter:
public class StringConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(string).IsAssignableFrom(objectType);
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (!this.CanConvert(value.GetType())) return;
writer.WriteValue("blah blah blah");
}
}
Run: (As you can see I'm using Entity Framework for my data)
using (var db = new Context())
{
var json = JsonConvert.SerializeObject(db.Accounts.FirstOrDefault(), Formatting.Indented,
new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
Console.WriteLine(json);
}
Returns:
{
"Id": 43,
"AccountName": "John",
"DefaultAssignToId": 43,
"OtherId": 19,
"OtherName": "blah blah blah"
}
One of the things that messed me up for a bit was when to use writer.WriteStartObject(). Basically if you are Converting a complex object, you need to use it. If you do, then you need to create all of the property names and values. In the OP you can see an example of how I did this. The biggest down side is any Json attributes that properties are decorated with don't automatically happen. So if you can, decorate the class and let the serializer deal with it. Here's an interesting problem you can run into if you use it wrong.
In the String converter replace:
writer.WriteValue("blah blah blah");
with:
writer.WriteStartObject();
writer.WritePropertyName("BlahProp");
serializer.Serialize(writer, "blah blah blah");
writer.WriteEndObject();
run it again and here's the output: (Notice how OtherName is an object now instead of a string)
{
"Id": 43,
"AccountName": "John",
"DefaultAssignToId": 43,
"OtherId": 19,
"OtherName": {
"BlahProp": "blah blah blah"
}
}
The Roles collection also needed a Converter. Two things to note are first, you don't need to specify the property name and second, any Json attributes that the Role class is decorated with will work as expected.
using BaseCollection = System.Collections.Generic.ICollection<JsonContractandConvert.Models.Role>;
public class RemoveAccountsFromRolesConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(BaseCollection).IsAssignableFrom(objectType);
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (!this.CanConvert(value.GetType())) return;
var entities = value as BaseCollection;
if (entities == null) return;
writer.WriteStartArray();
foreach (var entity in entities)
{
entity.Accounts = null;
serializer.Serialize(writer, entity);
}
writer.WriteEndArray();
}
}
Lastly, here's a couple of converters I created for my real project:
This one converts an object to a shallow copy. This is useful if you have a collection where what that collection contains could cause a self referencing loop.
public class ShallowCopyCollectionConverter<TCollectionType, TCopyType> : JsonConverter
where TCollectionType : IEnumerable<TbdEntity>
where TCopyType : TbdEntity, new()
{
public override bool CanConvert(Type objectType)
{
return typeof(TCollectionType).IsAssignableFrom(objectType);
}
public override bool CanRead
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (this.CanConvert(value.GetType()) == false) return;
var entities = (TCollectionType)value;
writer.WriteStartArray();
foreach (var entity in entities)
{
serializer.Serialize(writer, entity.ShallowCopy<TCopyType>()); //ShallowCopy<> is a method in the base class that all of my classes extend.
}
writer.WriteEndArray();
}
}
This one will convert an abstract using the concrete type:
public class DataSnapInConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(DataSnapIn) == (objectType);
}
public override bool CanWrite
{
get { return false; }
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (this.CanConvert(objectType) == false) return null;
var jo = JObject.Load(reader);
var typeName = jo["snapInType"] ?? jo["SnapInType"]; //the abstract classes have this property to identify what concrete class they are.
var typeNameString = typeName.ToString();
var deserializeType = Type.GetType(typeNameString);
if(deserializeType == null)
throw new Exception("SnapInType is null or does not reference a valid class.");
var result = Activator.CreateInstance(deserializeType);
serializer.Populate(jo.CreateReader(), result);
return result;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}