JSON.Net BSON 序列化不正确地处理 DateTimeOffset?

JSON.Net BSON serialization incorrectly handling DateTimeOffset?

我正在尝试使用 JSON.Net 序列化为 BSON,但原始偏移量似乎没有得到遵守。

你能看出我如何努力使这项工作有问题吗?

[Test]
public void SerializeDateTimeOffsetToBson()
{
    var serializer = new Newtonsoft.Json.JsonSerializer {
        TypeNameHandling = TypeNameHandling.Auto,
        DateParseHandling = DateParseHandling.DateTimeOffset,
        DateTimeZoneHandling = DateTimeZoneHandling.RoundtripKind
    };

    var negOffset = new DateTimeOffset(2014, 7, 10, 0, 0, 0, new TimeSpan(-5, 0, 0));
    var gmtOffset = new DateTimeOffset(2014, 7, 10, 0, 0, 0, new TimeSpan());
    var posOffset = new DateTimeOffset(2014, 7, 10, 0, 0, 0, new TimeSpan(5, 0, 0));

    var dt = new {
        negOffset = negOffset,
        gmtOffset = gmtOffset,
        posOffset = posOffset
    };

    byte[] serialized;

    using (var ms = new MemoryStream())
    using (var writer = new BsonWriter(ms)) {
        serializer.Serialize(writer, dt);
        writer.Close();
        serialized = ms.ToArray();
    }

    dynamic deserializedDt;

    using (var ms = new MemoryStream(serialized))
    using (var rdr = new BsonReader(ms)) {
        deserializedDt = (dynamic)serializer.Deserialize(rdr);
        rdr.Close();
    }

    Assert.IsTrue(deserializedDt.negOffset == dt.negOffset);
    Assert.IsTrue(deserializedDt.posOffset == dt.posOffset);
    Assert.IsTrue(deserializedDt.gmtOffset == dt.gmtOffset);
}

所有三个断言都将失败。

反序列化后,deserializedDt.negOffset 是 2014 年 7 月 9 日下午 10 点,偏移量为 -07:00(计算机当前时区),deserializedDt.posOffset 是 2014 年 7 月 9 日下午 12 点,偏移量为 - 07:00,deserializedDt.gmtOffset 是 2014 年 7 月 9 日下午 5 点,偏移量为 -07:00。

在 .Net 4.0 项目中使用 JSON.Net 8.0.3。

更新-----------------

经过进一步调查,我在 Github https://github.com/JamesNK/Newtonsoft.Json/issues/898

上提出了一个关于此的问题

BSON 规范不允许存储 DateTime 的偏移量; it stores UTC datetime as an Int64,自 Unix 纪元以来的毫秒数。

如果您不想丢失偏移量,您可以创建一个 JsonConverter,它将 DateTime 与偏移量分开,以分别序列化(和反序列化)两者。例如:

public class DateTimeOffsetConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(DateTimeOffset) == objectType;
    }

    public override object ReadJson(
        JsonReader reader, 
        Type objectType, 
        object existingValue, 
        Newtonsoft.Json.JsonSerializer serializer)
    {
        if (reader.TokenType != JsonToken.StartObject)
            return null;

        reader.Read(); // PropertyName "DateTimeInTicks"
        reader.Read(); // Property value
        var ticks = (Int64)reader.Value;

        reader.Read(); // PropertyName "Offset"
        reader.Read(); // Property value
        var offset = TimeSpan.Parse((String)reader.Value);

        // Move forward to JsonToken.EndObject
        reader.Read();

        return new DateTimeOffset(ticks, offset);
    }

    public override void WriteJson(
        JsonWriter writer, 
        object value, 
        Newtonsoft.Json.JsonSerializer serializer)
    {
        var dateTimeOffset = (DateTimeOffset)value;

        var toSerialize = new {
            DateTimeInTicks = dateTimeOffset.DateTime.Ticks,
            Offset = dateTimeOffset.Offset
        };

        serializer.Serialize(writer, toSerialize);
    }
}

然后您可以将其应用到您的 类,如下所示:

public class TestClass
{
    public Int32 TestInt { get; set; }

    [JsonConverter(typeof(DateTimeOffsetConverter))] 
    public DateTimeOffset TestDateTimeOffset { get; set; }

    public String TestString { get; set; }

    [JsonConverter(typeof(DateTimeOffsetConverter))] 
    public DateTimeOffset? TestNullableDateTimeOffset { get; set; }
}

这是我在上一个答案之后得出的第二个解决方案,我最终选择了这个解决方案。

这个的优点是您不必装饰 DateTimeOffset 或 DateTimeOffset 的每个实例?它也可以在您无法控制且无法控制的框架 类 上运行装饰(例如元组)。它可以正常工作,无需任何额外的努力。

转换器修改为保存为字符串而不是拆分为对象:

public class DateTimeOffsetConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(DateTimeOffset) == objectType
            || typeof(DateTimeOffset?) == objectType;
    }

    public override object ReadJson(
        JsonReader reader, 
        Type objectType, 
        object existingValue, 
        Newtonsoft.Json.JsonSerializer serializer)
    {
        if (reader.TokenType != JsonToken.String && reader.TokenType != JsonToken.Date)
            return null;

        DateTimeOffset dt;

        // If you need to deserialize already-serialized DateTimeOffsets,
        // it would come in as JsonToken.Date, uncomment to handle. Newly 
        // serialized values will come in as JsonToken.String.
        //if (reader.TokenType == JsonToken.Date)
        //  return (DateTimeOffset)reader.Value;

        var dateWithOffset = (String)reader.Value;

        if (String.IsNullOrEmpty(dateWithOffset))
            return null;

        if (DateTimeOffset.TryParseExact(dateWithOffset, "O", CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out dt))
            return dt;

        return null;
    }

    public override void WriteJson(
        JsonWriter writer, 
        object value, 
        Newtonsoft.Json.JsonSerializer serializer)
    {
        var dateTimeOffset = (DateTimeOffset)value;

        // Serialize DateTimeOffset as round-trip formatted string
        serializer.Serialize(writer, dateTimeOffset.ToString("O"));
    }
}

需要自定义 ContractResolver 以在需要时注入转换器:

public class DateTimeOffsetContractResolver: DefaultContractResolver
{
    protected override JsonContract CreateContract(Type objectType)
    {
        var contract = base.CreateContract(objectType);

        if (objectType == typeof(DateTimeOffset) || objectType == typeof(DateTimeOffset?) 
            contract.Converter = new DateTimeOffsetConverter();

        return contract;
    }
}

并配置 JSON.Net 以使用您的 ContractResolver:

var serializer = new JsonSerializer {
    ContractResolver = new DateTimeOffsetContractResolver()
};