Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JSON.Net BSON serialization incorrectly handling DateTimeOffset?

Tags:

json.net

bson

I'm trying to use JSON.Net to serialize to BSON, but the original offset does not appear to be respected.

Can you see a problem how I'm trying to make this work?

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

All three of the assertions will fail.

After deserialization, deserializedDt.negOffset is July 9th 2014 10 PM with an offset of -07:00 (computer's current time zone), deserializedDt.posOffset is July 9th 2014 12 PM with an offset of -07:00, and deserializedDt.gmtOffset is July 9th 2014 5 PM with an offset of -07:00.

Using JSON.Net 8.0.3 in a .Net 4.0 project.

UPDATE------------------

After further investigation, I've opened an issue about this on Github at https://github.com/JamesNK/Newtonsoft.Json/issues/898

like image 630
Remi Despres-Smyth Avatar asked Apr 25 '26 02:04

Remi Despres-Smyth


1 Answers

The BSON spec doesn't allow for storing a DateTime's offset; it stores UTC datetime as an Int64, milliseconds since the Unix epoch.

If you don't want to lose the offset, you can create a JsonConverter that will separate the DateTime from the Offset to serialize (and deserialize) both separately. For example:

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

You can then apply it to your classes as follows:

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; }
}
like image 113
Remi Despres-Smyth Avatar answered May 01 '26 01:05

Remi Despres-Smyth



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!