Search code examples
c#parsingenumshl7-fhir

Parse string to enum with custom attribute


I have an enum as follows. I also have a string in-progress I am trying to parse this string to the appropriate enum. As seen by the following test we want to parse to take a string and return the enum

    [Fact]
    public void TestParseOfEnum()
    {
        var data = "not-started";

        var parsed = EnumExtensions.GetValueFromEnumMember<CarePlan.CarePlanActivityStatus>(data);

        parsed.ShouldBe(CarePlan.CarePlanActivityStatus.NotStarted);
    }

The issue being that enum try parse is checking on the name which means its failing. I need to parse it on this custom attribute.

 CarePlan.CarePlanActivityStatus status
 Enum.TryParse("in-progress", out status)

A try parse will not find this as try parse checks on the name field and not on the custom attribute within this enum. So this would return false and not find my enum

I have been trying to see how I could get a list back of all of the fields within the enum and then test on the literal.

This would work but i have to specify each of the values within the enum in getfield

  var res = typeof(CarePlan.CarePlanActivityStatus)
                 .GetField(nameof(CarePlan.CarePlanActivityStatus.Cancelled))
                 .GetCustomAttribute<EnumLiteralAttribute>(false)
                 .Literal;
            

I tried something like this but Literal doesn't exist at this point apparently so this fails as well.

  var hold = typeof(CarePlan.CarePlanActivityStatus).GetFields().Where(a =>
            a.GetCustomAttributes<EnumLiteralAttribute>(false).Literal.Equals(data));

The enum

The nuget package i am using for the fhir library is Hl7.Fhir.R4

[FhirEnumeration("CarePlanActivityStatus")]
public enum CarePlanActivityStatus
{
  /// <summary>
  /// Care plan activity is planned but no action has yet been taken.
  /// (system: http://hl7.org/fhir/care-plan-activity-status)
  /// </summary>
  [EnumLiteral("not-started", "http://hl7.org/fhir/care-plan-activity-status"), Hl7.Fhir.Utility.Description("Not Started")] NotStarted,
  /// <summary>
  /// Appointment or other booking has occurred but activity has not yet begun.
  /// (system: http://hl7.org/fhir/care-plan-activity-status)
  /// </summary>
  [EnumLiteral("scheduled", "http://hl7.org/fhir/care-plan-activity-status"), Hl7.Fhir.Utility.Description("Scheduled")] Scheduled,
  /// <summary>
  /// Care plan activity has been started but is not yet complete.
  /// (system: http://hl7.org/fhir/care-plan-activity-status)
  /// </summary>
  [EnumLiteral("in-progress", "http://hl7.org/fhir/care-plan-activity-status"), Hl7.Fhir.Utility.Description("In Progress")] InProgress,
  /// <summary>
  /// Care plan activity was started but has temporarily ceased with an expectation of resumption at a future time.
  /// (system: http://hl7.org/fhir/care-plan-activity-status)
  /// </summary>
  [EnumLiteral("on-hold", "http://hl7.org/fhir/care-plan-activity-status"), Hl7.Fhir.Utility.Description("On Hold")] OnHold,
  /// <summary>
  /// Care plan activity has been completed (more or less) as planned.
  /// (system: http://hl7.org/fhir/care-plan-activity-status)
  /// </summary>
  [EnumLiteral("completed", "http://hl7.org/fhir/care-plan-activity-status"), Hl7.Fhir.Utility.Description("Completed")] Completed,
  /// <summary>
  /// The planned care plan activity has been withdrawn.
  /// (system: http://hl7.org/fhir/care-plan-activity-status)
  /// </summary>
  [EnumLiteral("cancelled", "http://hl7.org/fhir/care-plan-activity-status"), Hl7.Fhir.Utility.Description("Cancelled")] Cancelled,
  /// <summary>
  /// The planned care plan activity has been ended prior to completion after the activity was started.
  /// (system: http://hl7.org/fhir/care-plan-activity-status)
  /// </summary>
  [EnumLiteral("stopped", "http://hl7.org/fhir/care-plan-activity-status"), Hl7.Fhir.Utility.Description("Stopped")] Stopped,
  /// <summary>
  /// The current state of the care plan activity is not known.  Note: This concept is not to be used for "other" - one of the listed statuses is presumed to apply, but the authoring/source system does not know which one.
  /// (system: http://hl7.org/fhir/care-plan-activity-status)
  /// </summary>
  [EnumLiteral("unknown", "http://hl7.org/fhir/care-plan-activity-status"), Hl7.Fhir.Utility.Description("Unknown")] Unknown,
  /// <summary>
  /// Care plan activity was entered in error and voided.
  /// (system: http://hl7.org/fhir/care-plan-activity-status)
  /// </summary>
  [EnumLiteral("entered-in-error", "http://hl7.org/fhir/care-plan-activity-status"), Hl7.Fhir.Utility.Description("Entered in Error")] EnteredInError,
}

// example of everything I have tested with

The nuget package i am using for the fhir library is Hl7.Fhir.R4

public static class EnumExtensions
{
    public static T GetValueFromEnumMember<T>(string value) where T : Enum
    {
            {
                
                
                // Doesnt work as .Literal shows as red not valid.
                var hold = typeof(CarePlan.CarePlanActivityStatus).GetFields().Where(a =>
                      a.GetCustomAttributes<EnumLiteralAttribute>(false).Literal.Equals(data));
                
                
                // doesnt work as its returning the string i need it to return the enum
                var res = typeof(CarePlan.CarePlanActivityStatus)
                    .GetField(nameof(CarePlan.CarePlanActivityStatus.Cancelled))
                    .GetCustomAttribute<EnumLiteralAttribute>(false)
                    .Literal;
                
                
                

// neither of the following two work as they are just looping though and i cant find the enum they find.
                foreach (CarePlan.CarePlanActivityStatus status in (CarePlan.CarePlanActivityStatus[])Enum.GetValues(
                             typeof(CarePlan.CarePlanActivityStatus)))
                {
                }

                foreach (string i in Enum.GetNames(typeof(CarePlan.CarePlanActivityStatus)))
                {
                    // var res = typeof(CarePlan.CarePlanActivityStatus)
                    //     .GetField(nameof(CarePlan.CarePlanActivityStatus[i]))
                    //     .GetCustomAttribute<EnumLiteralAttribute>(false)
                    //     .Literal;
                    //
                    // Console.WriteLine($" {res}" );  
                    //
                    // Console.WriteLine($" {i}" );  
                }
                
            }

Isnt there a way to parse a string to an enum without making a large mess of a if statements to test it. Ideally i need to create a generic method as i have about 10 of these enums i need test.


Solution

  • You can try with a extension method to read the Custom Atribute from Enums:

    public static class EnumExtensions
    {
        public static T GetValueFromEnumMember<T>(string value) where T: Enum
        {
            var type = typeof(T);
            foreach (var field in type.GetFields())
            {
                var attribute = Attribute.GetCustomAttribute(field,
                    typeof(EnumMemberAttribute)) as EnumMemberAttribute;
                if (attribute != null)
                {
                    if (attribute.Value == value)
                        return (T)field.GetValue(null);
                }
                else
                {
                    if (field.Name == value)
                        return (T)field.GetValue(null);
                }
            }
            throw new ArgumentException($"unknow value: {value}");
        }
    }
    

    and use it like this:

    EnumExtensions.GetValueFromEnumMember<CarePlanActivityStatus>(stringValueToTest)
    

    it returns the Enum Value