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.

Original source