Anapher/Strive

View on GitHub
src/Services/ConferenceManagement/Strive.Infrastructure/Serialization/JsonConfig.cs

Summary

Maintainability
A
0 mins
Test Coverage
using System;
using System.Collections.Generic;
using System.Linq;
using Autofac;
using JsonSubTypes;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using Strive.Core.Services.Poll;
using Strive.Core.Services.Scenes;
using Strive.Core.Services.Scenes.Scenes;
using Strive.Core.Services.WhiteboardService;
using Strive.Core.Services.WhiteboardService.CanvasData;
using Strive.Core.Services.WhiteboardService.PushActions;
using Strive.Infrastructure.Extensions;

namespace Strive.Infrastructure.Serialization
{
    public static class JsonConfig
    {
        public static JsonSerializerSettings Default
        {
            get
            {
                var settings = new JsonSerializerSettings();
                Apply(settings);
                return settings;
            }
        }

        public static void Apply(JsonSerializerSettings settings)
        {
            settings.ContractResolver = new CamelCasePropertyNamesContractResolver
                {NamingStrategy = new CamelCaseNamingStrategy(true, true)};
            settings.Converters.Add(new StringEnumConverter(new CamelCaseNamingStrategy(true, true)));
            settings.Converters.Add(GetSceneConverter());
            settings.Converters.Add(new ErrorConverter());
            ConfigurePollConverters(settings);
            ConfigureWhiteboardConverters(settings);

            // This is a hack to accomplish the following:
            // we want to preserve the casing of dictionary keys (so dont camel case keys as they might be case sensitive ids etc.)
            // but we also want to convert enums to camel case
            // A potential fix would've been to set CamelCaseNamingStrategy.ProcessDictionaryKeys to false, but the problem is that 
            // enum values are then also not converted to camel case. This converter fixes it by serializing all dictionaries Dictionary<string,any>
            // without changing the keys
            settings.Converters.Add(new DictionaryStringKeyPreserveCasingConverter());
        }

        private static JsonConverter GetSceneConverter()
        {
            var baseType = typeof(AutonomousScene);

            var sceneTypes = baseType.Assembly.GetTypes()
                .Where(x => x.IsInNamespace(baseType.Namespace!) && x.IsAssignableTo<IScene>());

            var builder = JsonSubtypesConverterBuilder.Of<IScene>("type");
            foreach (var sceneType in sceneTypes)
            {
                var key = sceneType.Name.Replace("Scene", null).ToCamelCase();
                builder.RegisterSubtype(sceneType, key);
            }

            return builder.SerializeDiscriminatorProperty().Build();
        }

        private static void ConfigurePollConverters(JsonSerializerSettings settings)
        {
            settings.Converters.Add(CreateJsonConverter<PollInstruction>(typeof(PollInstruction), "type",
                x => x.TrimEnd("Instruction").ToCamelCase()));

            settings.Converters.Add(CreateJsonConverter<PollAnswer>(typeof(PollAnswer), "type",
                x => x.TrimEnd("Answer").ToCamelCase()));

            settings.Converters.Add(CreateJsonConverter<PollResults>(typeof(PollResults), "type",
                x => x.TrimEnd("PollResults").ToCamelCase()));
        }

        private static void ConfigureWhiteboardConverters(JsonSerializerSettings settings)
        {
            settings.Converters.Add(CreateJsonConverter<CanvasAction>(typeof(CanvasAction), "type",
                x => x.TrimEnd("CanvasAction").ToCamelCase()));

            settings.Converters.Add(CreateJsonConverter<CanvasPushAction>(typeof(AddCanvasPushAction), "type",
                x => x.TrimEnd("CanvasPushAction").ToCamelCase()));

            settings.Converters.Add(CreateJsonConverter<CanvasLiveAction>(typeof(CanvasLiveAction), "type",
                x => x.TrimEnd("CanvasLiveAction").ToCamelCase()));

            var objectNameMatch = new Dictionary<string, string>
                {{nameof(CanvasText), "i-text"}, {nameof(CanvasLine), "line"}, {nameof(CanvasPath), "path"}};

            settings.Converters.Add(
                CreateJsonConverter<CanvasObject>(typeof(CanvasObject), "type", x => objectNameMatch[x]));
        }

        private static JsonConverter CreateJsonConverter<T>(Type baseType, string discriminatorProperty,
            Func<string, string> classNameFactory)
        {
            var subTypes = baseType.Assembly.GetTypes().Where(x =>
                x.IsInNamespace(baseType.Namespace!) && !x.IsAbstract && x.IsAssignableTo<T>());

            var builder = JsonSubtypesConverterBuilder.Of<T>(discriminatorProperty);
            foreach (var type in subTypes)
            {
                var key = classNameFactory(type.Name);
                builder.RegisterSubtype(type, key);
            }

            return builder.SerializeDiscriminatorProperty().Build();
        }
    }
}