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()
};
我正在尝试使用 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()
};