【问题标题】:Why can I not deserialize this custom struct using Json.Net?为什么我不能使用 Json.Net 反序列化这个自定义结构?
【发布时间】:2014-09-03 18:20:00
【问题描述】:

我有一个表示 DateTime 的结构,它也有如下区域信息:

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); 
        } 
    }
}

我可以使用以下方法序列化对象:

var now = DateTime.Now;
var dateTimeWithZone = new DateTimeWithZone(now, TimeZoneInfo.Local, DateTimeKind.Local);
var serializedDateTimeWithZone = JsonConvert.SerializeObject(dateTimeWithZone);

但是当我使用下面的反序列化它时,我得到一个无效的 DateTime 值 (DateTime.MinValue)

var deserializedDateTimeWithZone = JsonConvert.DeserializeObject<DateTimeWithZone>(serializedDateTimeWithZone);

非常感谢任何帮助。

【问题讨论】:

  • 你的类中有太多的逻辑需要序列化程序。它们最适合使用简单的“普通数据”类型(即带有 getter 和 setter 的一堆属性)
  • @Maxwell,我确信我的结构没有内置 DateTime 那么多的逻辑。

标签: c# datetime serialization json.net deserialization


【解决方案1】:

您需要编写custom JsonConverter 来正确序列化和反序列化这些值。将此类添加到您的项目中。

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);
    }
}

然后使用您正在使用的 json 设置注册它。例如,可以像这样更改默认设置:

JsonConvert.DefaultSettings = () =>
{
    var settings = new JsonSerializerSettings();
    settings.Converters.Add(new DateTimeWithZoneConverter());
    return settings;
};

然后它将正确地序列化为可用的格式。示例:

{
  "UniversalTime": "2014-07-13T20:24:40.4664448Z",
  "TimeZone": "Pacific Standard Time"
}

它也会正确反序列化。

如果您想包含当地时间,您只需将其添加到 WriteJson 方法中,但在反序列化时可能应该忽略它。否则你会有两个不同的真相来源。只有一个是权威的。

另外,您也可以尝试使用Noda Time,其中包含一个用于此目的的ZonedDateTime 结构。已经通过NodaTime.Serialization.JsonNet NuGet 包支持serialization

【讨论】:

  • 谢谢马特,我很清楚 Jon 在 Noda Time 上的工作,但我仍然想知道如何解决这个特殊问题。
  • 不是答案,应该是评论。
  • @EZI - 扩展为包含正确执行此操作所需的 JsonConverter
【解决方案2】:

只要声明构造函数如下,就可以了

[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;
}

注意:我只添加了JsonConstructor属性并将参数名称改为universalTime

【讨论】:

  • 这过于简单化了,它不起作用。看看 JSON 本身。时区的序列化完全太多。 (它应该只序列化时区 id,而不是类中的每个属性。)此外,反序列化时,尽管使用了 JsonConstructor,日期值仍然有错误的值。真的没有捷径可走。这是一个自定义值对象,因此需要 JsonConverter
  • 我的自定义结构有问题。我所需要的只是 [JsonConstructor] 来解决我的问题。谢谢你。
猜你喜欢
  • 1970-01-01
  • 2017-03-19
  • 1970-01-01
  • 1970-01-01
  • 2017-06-30
  • 1970-01-01
  • 1970-01-01
  • 2022-11-15
  • 1970-01-01
相关资源
最近更新 更多