Why can I not deserialize this custom struct using Json.Net?
c#, datetime, deserialization, json.net, serialization
Solution
You need to write a custom `JsonConverter` to properly serialize and deserialize these values. Add this class to your project.
public class DateTimeWithZoneConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof (DateTimeWithZone) || objectType == typeof (DateTimeWithZone?);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var dtwz = (DateTimeWithZone) value;
writer.WriteStartObject();
writer.WritePropertyName("UniversalTime");
serializer.Serialize(writer, dtwz.UniversalTime);
writer.WritePropertyName("TimeZone");
serializer.Serialize(writer, dtwz.TimeZone.Id);
writer.WriteEndObject();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var ut = default(DateTime);
var tz = default(TimeZoneInfo);
var gotUniversalTime = false;
var gotTimeZone = false;
while (reader.Read())
{
if (reader.TokenType != JsonToken.PropertyName)
break;
var propertyName = (string)reader.Value;
if (!reader.Read())
continue;
if (propertyName == "UniversalTime")
{
ut = serializer.Deserialize<DateTime>(reader);
gotUniversalTime = true;
}
if (propertyName == "TimeZone")
{
var tzid = serializer.Deserialize<string>(reader);
tz = TimeZoneInfo.FindSystemTimeZoneById(tzid);
gotTimeZone = true;
}
}
if (!(gotUniversalTime && gotTimeZone))
{
throw new InvalidDataException("An DateTimeWithZone must contain UniversalTime and TimeZone properties.");
}
return new DateTimeWithZone(ut, tz);
}
}
Then register it with the json settings you're using. For example, the default settings can be changed like this:
JsonConvert.DefaultSettings = () =>
{
var settings = new JsonSerializerSettings();
settings.Converters.Add(new DateTimeWithZoneConverter());
return settings;
};
Then it will properly serialize to a usable format. Example:
{
"UniversalTime": "2014-07-13T20:24:40.4664448Z",
"TimeZone": "Pacific Standard Time"
}
And it will deserialize properly as well.
If you want to include the local time, You would just add that to the `WriteJson` method, but it should probably be ignored when deserializing. Otherwise you'd have two different sources of truth. Only one can be authoritative.
Also, you might instead try Noda Time, which includes a `ZonedDateTime` struct for this exact purpose. There's already support for serialization via the NodaTime.Serialization.JsonNet NuGet package.
Problem
I have a struct representing a DateTime which also has zone info as below: ``` public struct DateTimeWithZone { private readonly DateTime _utcDateTime; private readonly TimeZoneInfo _timeZone; public DateTimeWithZone(DateTime dateTime, TimeZoneInfo timeZone, DateTimeKind kind = DateTimeKind.Utc) { dateTime = DateTime.SpecifyKind(dateTime, kind); _utcDateTime = dateTime.Kind != DateTimeKind.Utc ? TimeZoneInfo.ConvertTimeToUtc(dateTime, timeZone) : dateTime; _timeZone = timeZone; } public DateTime UniversalTime { get { return _utcDateTime; } } public TimeZoneInfo TimeZone { get { return _timeZone; } } public DateTime LocalTime { get { return TimeZoneInfo.ConvertTime(_utcDateTime, _timeZone); } } } ``` I can serialize the object using: ``` var now = DateTime.Now; var dateTimeWithZone = new DateTimeWithZone(now, TimeZoneInfo.Local, DateTimeKind.Local); var serializedDateTimeWithZone = JsonConvert.SerializeObject(dateTimeWithZone); ``` But when I deserialize it using the below, I get an invalid DateTime value (DateTime.MinValue) ``` var deserializedDateTimeWithZone = JsonConvert.DeserializeObject<DateTimeWithZone>(serializedDateTimeWithZone); ``` Any help is much appreciated.