Why can I not deserialize this custom struct using Json.Net?

佐手、 提交于 2019-12-29 08:36:28

问题


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.


回答1:


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.




回答2:


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



来源:https://stackoverflow.com/questions/24726273/why-can-i-not-deserialize-this-custom-struct-using-json-net

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!