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.
A common way to deserialize JSON is to first create a class with properties and fields that represent one or more of the JSON properties. Then, to deserialize from a string or a file, call the JsonSerializer. Deserialize method.
It returns JSON data in string format. In Deserialization, it does the opposite of Serialization which means it converts JSON string to custom . Net object. In the following code, it calls the static method DeserializeObject() of the JsonConvert class by passing JSON data.
Deserializes the JSON to the specified . NET type. Deserializes the JSON to the specified . NET type using a collection of JsonConverter.
Just declare the constructor as follows, that's all
[JsonConstructor]
public DateTimeWithZone(DateTime universalTime, TimeZoneInfo timeZone,
DateTimeKind kind = DateTimeKind.Utc)
{
universalTime = DateTime.SpecifyKind(universalTime, kind);
_utcDateTime = universalTime.Kind != DateTimeKind.Utc
? TimeZoneInfo.ConvertTimeToUtc(universalTime, timeZone)
: universalTime;
_timeZone = timeZone;
}
Note: I only added JsonConstructor
attribute and changed the parameter name as universalTime
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With