Search code examples
serializationnodatimeakavache

Akavache not storing/returning a NodaTime LocalDateTime


I need to store a NodaTime LocalDateTime in an Akavache cache.

I've created a simple app which takes the following class and stores/retrieves it in/from an Akavache cache:

public class TestModel
{
        public string Name { get; set; }
        public LocalDateTime StartDateTimeLocal {get; set;}
        public DateTime StartDateTimeUtc {get;set;}
}

When this is stored in and retrieved from the cache, the StartDateTimeLocal property hasn't been populated.

It seems that Akavache isn't aware of how to serialise/deserialize a LocalDateTime.

Is it possible to register types with Akavache or supply a custom serialisation for unknown types?

Console application to demonstrate it:

using Akavache;
using NodaTime;
using System;
using System.Reactive.Linq;

namespace AkavacheNodaTimeCore
{
    class Program
    {
        static TestModel BeforeModel;
        static TestModel AfterModel;
        static void Main(string[] args)
        {
            // Note that we're using Akavache 6.0.27, to match the version we're using in our live system.
            BlobCache.ApplicationName = "AkavacheNodaTimeCore";
            BlobCache.EnsureInitialized();

            BeforeModel = new TestModel()
            {
                StartLocalDateTime = LocalDateTime.FromDateTime(DateTime.Now),
                StartDateTime = DateTime.UtcNow,
            };

            Console.WriteLine($"Before:LocalDateTime='{BeforeModel.StartLocalDateTime}' DateTime='{BeforeModel.StartDateTime}'");

            CycleTheModels();

            Console.WriteLine($"After: LocalDateTime='{AfterModel.StartLocalDateTime}' DateTime='{AfterModel.StartDateTime}'");
            Console.WriteLine("Note that Akavache retrieves DateTimes as DateTimeKind.Local, so DateTime before and after above will differ.");

            Console.WriteLine("Press any key to continue.");
            var y = Console.ReadKey();

        }
        /// <summary>
        /// Puts a model into Akavache and retrieves a new one so we can compare.
        /// </summary>
        static async void CycleTheModels()
        {
            await BlobCache.InMemory.Invalidate("model");
            await BlobCache.InMemory.InsertObject("model", BeforeModel);

            AfterModel = await BlobCache.InMemory.GetObject<TestModel>("model");
        }
    }
}

TestModel class:

using NodaTime;
using System;

namespace AkavacheNodaTimeCore
{
    public class TestModel
    {
        public string Name { get; set; }
        public LocalDateTime StartLocalDateTime { get; set; }
        public DateTime StartDateTime {get;set;}

    }
}

I have added a Git repo with the above in a console application which demonstrates the problem.


Solution

  • You need to configure the JsonSerializerSettings that Akavache uses with Json.NET. You'll need a reference to NodaTime.Serialization.JsonNet, at which point you can create a serializer settings instance, configure it for Noda Time, then add that as a dependency in Splat (which Akavache uses). I haven't used Splat before, so it's possible that this isn't the right way of doing it, but it works with your example:

    using Newtonsoft.Json;
    using NodaTime.Serialization.JsonNet;
    using Splat;
    
    ...
    
    // This should be before any of your other code.
    var settings = new JsonSerializerSettings();
    settings.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
    Locator.CurrentMutable.RegisterConstant(settings, typeof(JsonSerializerSettings));
    

    It may be worth filing in issue in the Akavache repo to request more documentation for customization of serialization settings - the above works, but was guesswork and a little bit of source code investigation.