kronstapper
kronstapper

Reputation: 1

Mapping Dictionary<string, string> to object instance of some type excluding property names not present in the dictionary using Mapster

Decided to use Mapster instead of writing my own conversion/mapping methods. Haven't found anything that would suffice my use case on their github wiki.

I have an instance of Dictionary<string, string> where keys are property names of target type and values are string representations of that property value on the target type.

Here's an example of target type:

public sealed class TriggerSettings
{
    public bool ShouldRun { get; set; }
    public SimpleTriggerRecurringType RecurringType { get; set; } // enum
    public SimpleTriggerDayOfWeek DayOfWeek { get; set; } // enum
    public int Hour { get; set; }
    public int Minute { get; set; }
    public int Second { get; set; }
}

Keys in the dictionary may be incorrect and the key set may not contain all the target property names. I want to take all the valid property names and map string representations on according properties of the target type, but with a condition that I already have the target type instance and do not want to change properties, that are not present in the dictionary.

Is there a simple way to create such configuration with TypeAdapterConfig once or it can be only resolved at runtime for specific dictionary instance?

Previously I used my own simple method, that used reflection and looked like this

public static void ConvertAndMapKeyValuePairsOnObject<T>(T obj, IDictionary<string, string> propertyNameValuePairs)
{
    var properties = typeof(T).GetProperties(
        BindingFlags.Public |
        BindingFlags.NonPublic |
        BindingFlags.Instance);

    foreach (var property in properties)
    {
        if (!propertyNameValuePairs.ContainsKey(property.Name))
        {
            continue;
        }

        var converter = TypeDescriptor.GetConverter(property.PropertyType);
        var result = converter.ConvertFrom(propertyNameValuePairs[property.Name]);
        if (result is null)
        {
            continue;
        }

        property.SetValue(obj, result);
    }
}

Upvotes: 0

Views: 1366

Answers (1)

kronstapper
kronstapper

Reputation: 1

A simple solution has been found. Not sure if it's the best, but UseDestinationValue method can be used on TypeAdapterSetter object with a predicate in such case. All invalid property names will be ignored, as well as the target object properties that are not in the dictionary key set.

Here's an extension method, that can be called on the object you intend to map onto with the contents of the dictionary.

public static class MapsterExtensions
{
    public static TDestination UpdateWithDictionary<TDestination>(
        this TDestination source,
        IDictionary<string, string> propertyNameValuePairs)
    {
        var mappingConfig = new TypeAdapterConfig()
            .NewConfig<IDictionary<string, string>, TDestination>()
            .UseDestinationValue(member => !propertyNameValuePairs.ContainsKey(member.Name));

        return propertyNameValuePairs.Adapt(source, mappingConfig.Config);
    }
}

Usage example:

var propertyDictionary = new Dictionary<string, string>
{
    {
        "Hour", "12"
    },
    {
        "Minute", "30"
    }
};

var settings = new TriggerSettings
{
    ShouldRun = true
};

Console.WriteLine($"Hour property before mapping: {settings.Hour}");
Console.WriteLine($"Minute property before mapping: {settings.Minute}");
Console.WriteLine($"ShouldRun property before mapping: {settings.ShouldRun}");

settings.UpdateWithDictionary(propertyDictionary);

Console.WriteLine($"Hour property after mapping: {settings.Hour}");
Console.WriteLine($"Minute property after mapping: {settings.Minute}");
Console.WriteLine($"ShouldRun property after mapping (should remain the same): {settings.ShouldRun}");

Output:

Hour property before mapping: 0
Minute property before mapping: 0
ShouldRun property before mapping: True
Hour property after mapping: 12
Minute property after mapping: 30
ShouldRun property after mapping (should remain the same): True

Upvotes: 0

Related Questions