0

I have an abstract class for a DomainEvent and several derived classes for the actual events. I want to serialise these events into JSON with an envelope format such as:

{
    type: "event.foo",
    payload: {
        ...
    }
}

Between the domain events and the serialisation I want to define some DTOs for the events to define the shape of the JSON. The last constraint is that the class doing the mapping will be storing the events in the DomainEvent base type. Somehow I need to get this base type into a mapping function for the correct derived type so that I can put it into the payload of the envelope.

My current approach is to use attributes to tag the mapper functions and use reflection to initialise a mapper singleton with a lookup of type to mapper function. These functions operate using object types so they can be stored together. There is a lot of weird stuff going on with this implementation and I am wondering if there is a better approach to solving this problem?

EDIT:

Here is my current code. It works fine but I am interested in alternative solutions:

public class EventSerializer
{
    private readonly IDictionary<Type, Func<object, object>> _sourceTypeToDtoMappings = new Dictionary<Type, Func<object, object>>();

    public EventSerializer()
    {
        var mapFunctions = Assembly.GetExecutingAssembly()
            .GetTypes()
            .SelectMany(t => t.GetMethods())
            .Where(m => m.GetCustomAttribute(typeof(MapsEventAttribute), false) != null);

        foreach (var mapFunction in mapFunctions)
        {
            var functionParameters = mapFunction.GetParameters();
            
            if (functionParameters.Length != 1)
                throw new InvalidOperationException($"Map function {mapFunction.Name} must have exactly one parameter");
            
            var inputType = mapFunction.GetParameters().FirstOrDefault()?.ParameterType 
                            ?? throw new InvalidOperationException("Map function must have an input parameter");
            
            if (mapFunction.ReturnType == typeof(void)) 
                throw new InvalidOperationException("Map function must have a return type");
            
            _sourceTypeToDtoMappings.Add(inputType, obj => mapFunction.Invoke(null, new[] { obj })!);
        }
    }
    
    public string Serialize(GameEvent gameEvent)
    {
        var payload = DynamicMap(gameEvent, gameEvent.GetType());

        var dto = new GameEventDto
        {
            Type = gameEvent.GetType().Name,
            Payload = payload
        };

        return JsonSerializer.Serialize(dto);
    }

    private object DynamicMap(object obj, Type sourceType)
    {
        var success = _sourceTypeToDtoMappings.TryGetValue(sourceType, out var mapper);
        if (!success)
            throw new InvalidOperationException($"No mapping found for type {sourceType}");

        return mapper!(obj);
    }
}

I moved the dictionary initialisation into the constructor to make it easier to see the whole picture here.

tvandinther
  • 371
  • 3
  • 13
  • With your vague description, I'm thinking you want a tool like AutoMapper or Mapster. Library recommendations are off-topic here though, so if you are looking to continue with a vanilla solution, then you'll ned to provide more details (examples of classes, how you're currently doing it, etc.) – ProgrammingLlama May 26 '22 at 03:41
  • 1
    Is https://stackoverflow.com/questions/28128923/serializing-an-interface-abstract-object-using-newtonsoft-json relevant? – Caius Jard May 26 '22 at 03:53
  • So this is an age old problem that has been solved for sometime now. If you take a step back and think [data agnostic](https://en.wikipedia.org/wiki/Agnostic_(data)) then the solution is simple and you won't be tied to one particular data format or depend on one particular tool (even if it has the word "Auto" in the product name). There are many data transformation tools in the EAI space (which is exactly where you are) that work either via code or via visual mappers. Most run auto-generated XSLT over converted XML. Honestly if you convert your JSON to XML it's job done. –  May 26 '22 at 04:14

0 Answers0