首页 > 解决方案 > 将 LocalDate 从版本 1 反序列化到版本 2

问题描述

不幸的是,当我们开始这个项目时,我们没有将NodaTime.Serialization配置添加到NEventStore接线中。

这意味着我们在 NEventStore 中有这样的 JSON 文档。

笔记。简化视图。实际上不是事件表示。

dates.json

{
    "$type": "NodatimeIssueTest.Product, NodatimeIssueTest",
    "FirstDate": {
        "$type": "NodaTime.LocalDate, NodaTime",
        "ticks": 12304224000000000,
        "calendar": "ISO"
    },
    "SecondDate": {
        "$type": "System.Nullable`1[[NodaTime.LocalDate, NodaTime]], mscorlib",
        "ticks": 12304224000000000,
        "calendar": "ISO"
    },
    "OtherDates": [
        {
            "$type": "NodaTime.LocalDate, NodaTime",
            "ticks": 12304224000000000,
            "calendar": "ISO"
        }
    ],
    "FirstDateTime": {
        "$type": "NodaTime.LocalDateTime, NodaTime",
        "ticks": 12304734100000000,
        "calendar": "ISO"
    },
    "FirstInstant": {
        "$type": "NodaTime.Instant, NodaTime",
        "ticks": 12304734100000000
    }
}

而不是这个

{
    "$type": "NodatimeIssueTest.Product, NodatimeIssueTest",
    "FirstDate": "2008-12-28",
    "SecondDate": "2008-12-28",
    "OtherDates": [
        "2008-12-28"
    ],
    "FirstDateTime": "2008-12-28T14:10:10",
    "FirstInstant": "2008-12-28T14:10:10Z"
}

正因为如此,我们很难升级到最新的 NodaTime 包,因为我们不能再反序列化 json 文档。

一种解决方案是读取所有 NEventStore 提交并使用正确的 NodaTime 解析器进行序列化。但是,如果可以避免这种情况,我会很高兴。

另一种选择是进行自定义转换和数据绑定。

但这需要低级别的 Nodatime 逻辑。尽管我们不需要例如涵盖所有日历。

标签: json.netnodatime

解决方案


使用自定义转换器和活页夹的解决方案,dates.json例如有问题

using System;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using NodaTime;
using NodaTime.Serialization.JsonNet;
using NUnit.Framework;

namespace NodatimeIssueTest
{
    [TestFixture]
    public class TestClass
    {
        [Test]
        public void Deserialize()
        {
            var serializer = new JsonSerializer();
            serializer.NullValueHandling = NullValueHandling.Ignore;
            serializer.TypeNameHandling = TypeNameHandling.Objects;

            //Custom converters and binder
            serializer.Converters.Add(new NodaLocalDateConverter());
            serializer.Converters.Add(new NodaLocalDateTimeConverter());
            serializer.Converters.Add(new NodaInstantConverter());
            serializer.SerializationBinder = new CustomBinder();

            using (var sr = new StreamReader($@"{TestContext.CurrentContext.TestDirectory}\dates.json"))
            using (var reader = new JsonTextReader(sr))
            {
                var product = serializer.Deserialize<Product>(reader);
                Assert.AreEqual(new LocalDate(2008, 12, 28), product.FirstDate);
                Assert.AreEqual(new LocalDate(2008, 12, 28), product.SecondDate);
                Assert.AreEqual(new LocalDate(2008, 12, 28), product.OtherDates[0]);
                Assert.AreEqual(new LocalDateTime(2008, 12, 28, 14, 10, 10), product.FirstDateTime);
                Assert.AreEqual(Instant.FromUtc(2008, 12, 28, 14, 10, 10), product.FirstInstant);
            }
        }
    }

    public class Product
    {
        public LocalDate FirstDate { get; set; }
        public LocalDate? SecondDate { get; set; }
        public List<LocalDate> OtherDates { get; set; }

        public LocalDateTime FirstDateTime { get; set; }
        public Instant FirstInstant { get; set; }
    }

    public class NodaLocalDateConverter : JsonConverter
    {
        public override bool CanWrite => false;
        public override bool CanRead => true;

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.TokenType == JsonToken.Null) return null;
            if (reader.TokenType == JsonToken.StartObject)
            {
                var custom = (CustomLocalDate) serializer.Deserialize(reader, typeof(CustomLocalDate));
                var dateTime = new DateTime(custom.Ticks, DateTimeKind.Utc).AddTicks(new DateTime(1970, 1, 1).Ticks);
                var local = LocalDate.FromDateTime(dateTime, CalendarSystem.Iso);

                return local;
            }

            return null;
        }

        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(LocalDate) || objectType == typeof(LocalDate?);
        }
    }

    public class NodaLocalDateTimeConverter : JsonConverter
    {
        public override bool CanWrite => false;
        public override bool CanRead => true;

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.TokenType == JsonToken.Null) return null;
            if (reader.TokenType == JsonToken.StartObject)
            {
                var custom = (CustomLocalDateTime) serializer.Deserialize(reader, typeof(CustomLocalDateTime));
                var dateTime = new DateTime(custom.Ticks, DateTimeKind.Utc).AddTicks(new DateTime(1970, 1, 1).Ticks);
                var local = LocalDateTime.FromDateTime(dateTime, CalendarSystem.Iso);

                return local;
            }

            return null;
        }

        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(LocalDateTime) || objectType == typeof(LocalDateTime?);
        }
    }

    public class NodaInstantConverter : JsonConverter
    {
        public override bool CanWrite => false;
        public override bool CanRead => true;

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.TokenType == JsonToken.Null) return null;
            if (reader.TokenType == JsonToken.StartObject)
            {
                var custom = (CustomInstant) serializer.Deserialize(reader, typeof(CustomInstant));
                var dateTime = new DateTime(custom.Ticks, DateTimeKind.Utc).AddTicks(new DateTime(1970, 1, 1).Ticks);
                var local = Instant.FromDateTimeUtc(dateTime);

                return local;
            }

            return null;
        }

        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(Instant) || objectType == typeof(Instant?);
        }
    }

    public class CustomBinder : DefaultSerializationBinder
    {
        public override Type BindToType(string assemblyName, string typeName)
        {
            switch (typeName)
            {
                case "NodaTime.LocalDate": return typeof(CustomLocalDate);
                case "System.Nullable`1[[NodaTime.LocalDate, NodaTime]]": return typeof(CustomLocalDate);

                case "NodaTime.LocalDateTime": return typeof(CustomLocalDateTime);
                case "System.Nullable`1[[NodaTime.LocalDateTime, NodaTime]]": return typeof(CustomLocalDateTime);

                case "NodaTime.Instant": return typeof(CustomInstant);
                case "System.Nullable`1[[NodaTime.Instant, NodaTime]]": return typeof(CustomInstant);

                default: return base.BindToType(assemblyName, typeName);
            }
        }
    }

    public class CustomLocalDate
    {
        [JsonProperty("ticks")] public long Ticks { get; set; }

        [JsonProperty("calendar")] public string Calendar { get; set; }
    }

    public class CustomLocalDateTime
    {
        [JsonProperty("ticks")] public long Ticks { get; set; }

        [JsonProperty("calendar")] public string Calendar { get; set; }
    }

    public class CustomInstant
    {
        [JsonProperty("ticks")] public long Ticks { get; set; }
    }
}

推荐阅读