Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for enum name sources (EnumMemberAttribute and DescriptionAttribute) #1079

Closed
danielwinkler opened this issue Jan 22, 2024 · 5 comments · Fixed by #1507
Closed
Labels
enhancement New feature or request good first issue Good for newcomers

Comments

@danielwinkler
Copy link

EnumMember enables control of the names of the enumerations as they are serialized, e.g.

[DataContract]
public enum Position
{
    [EnumMember(Value = "Emp")]
    Employee,
    [EnumMember(Value = "Mgr")]
    Manager,
    [EnumMember(Value = "Ctr")]
    Contractor,
}

from https://learn.microsoft.com/en-us/dotnet/api/system.runtime.serialization.enummemberattribute?view=net-8.0

It would be great if those annotations could be used to map to and from strings, e.g. by changing the mapping strategy EnumMappingStrategy = EnumMappingStrategy.ByAnnotation.

@latonz
Copy link
Contributor

latonz commented Jan 23, 2024

The enum mapping strategies are currently used to map from one enum to another enum.
The EnumMemberAttribute.Value would only be considered for enum to string or for string to enum mappings, wouldn't it?

@latonz latonz added the enhancement New feature or request label Jan 23, 2024
@danielwinkler
Copy link
Author

Yes, you're correct, I didn't think the EnumMappingStrategy part through.
But it would still be nice to have this functionality configureable.

@latonz
Copy link
Contributor

latonz commented Jan 23, 2024

We thought about allowing the MapEnumValueAttribute for enum to/from string mappings which would address the same use-case with a different api surface (e.g. MapEnumValue(”Emp”, Position.Employee)]).
I think supporting both would be nice.
Opt-in could be done by a new property on the MapperAttribute named RespectEnumMemberAttribute or similar. For now I would not implement this configuration on an per enum level… Not sure about the default value though. I think for new users it would be nice to have it set to true, but this may break existing users… May be something to set to false by default until the next major release.
Another thing to discuss is how to handle duplicated string values. Do you know how the .NET APIs using this attribute handle this situation? We should probably emit an error if the duplicated enum members values are different. If the values are the same, the duplication could probably be ignored and the first value could be used.

Would be happy to accept a PR implementing this 😊

@latonz latonz changed the title Add support for EnumMember Add support for EnumMemberAttribute Feb 22, 2024
@latonz latonz added the good first issue Good for newcomers label Mar 19, 2024
@latonz
Copy link
Contributor

latonz commented Apr 13, 2024

#1235 is asking for support of System.ComponentModel.DescriptionAttribute. It could be implemented similarly. The API may need to be adjusted for that. Instead of RespectEnumMemberAttribute we could introduce a EnumNameSource which can be set to MemberName (default), EnumMemberAttribute or DescriptionAttribute.

@latonz latonz changed the title Add support for EnumMemberAttribute Add support for enum name sources (EnumMemberAttribute and DescriptionAttribute) May 28, 2024
@richard-collette-precisely

I have a need for this. The use case is like:

public enum IanaTimeZone
{
    [EnumMember(Value = "Africa/Abidjan")] AfricaAbidjan,
    [EnumMember(Value = "Africa/Algiers")] AfricaAlgiers,
    [EnumMember(Value = "Africa/Bissau")] AfricaBissau,
    [EnumMember(Value = "Africa/Cairo")] AfricaCairo,
    //......
}

In the meantime we have the following converter being used. EnumMemberValue use is unfortunately not AOT compatible just yet, but coming soon in .NET 9 via dotnet/runtime#74385. I believe this provides slightly more impetus for adding support of the EnumMember attribute to Mapperly.

    private static IanaTimeZone StringToIanaTimeZone(string s) => EnumMemberValueConverter.ToEnum<IanaTimeZone>(s);
    private static string IanaTimeZoneToString(IanaTimeZone t) => EnumMemberValueConverter.ToString<IanaTimeZone>(t);
public static class EnumMemberValueConverter
{
    public static TEnum? ToEnum<TEnum>(string value) where TEnum : Enum
    {
        foreach (var field in typeof(TEnum).GetFields())
        {
            var attribute = field.GetCustomAttribute<EnumMemberAttribute>();
            if (attribute != null && attribute.Value == value)
            {
                return (TEnum)field.GetValue(null)!;
            }
        }

        throw new ArgumentException($"No EnumMember with value '{value}' found in {typeof(TEnum).Name}", value);
    }

    public static string ToString<TEnum>(TEnum enumValue) where TEnum : Enum
    {
        var field = typeof(TEnum).GetField(enumValue.ToString());
        if (field == null)
        {
            throw new ArgumentException($"No field found for value '{enumValue}' in enum {typeof(TEnum).Name}", enumValue.ToString());
        }

        var attribute = field.GetCustomAttribute<EnumMemberAttribute>();

        return attribute is { Value: not null } ? attribute.Value : enumValue.ToString();
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request good first issue Good for newcomers
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants