Skip to main content
Version: 4.0 (next)

Humanize and parse enums and flags

Orientation

Enum humanization resolves one display label for each member: supported description/display metadata when present, otherwise the humanized member name. On Humanizer 4, dehumanization also recognizes the raw name, its humanized form, and authored display aliases. Flags enums are decomposed and joined through the active collection formatter.

Example

The executable verifies a two-flag value, explicit enum-name and DisplayAttribute source selection, every dehumanization alias, case-insensitive generic dehumanization, no-match behavior, metadata fallback, and an unknown flag bit:

Program.cs
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using Humanizer;

CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("en-US");
CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo("en-US");

var access = Access.Read | Access.Write;

AssertEqual("Can view and Can edit", access.Humanize());
AssertEqual(
"Read and Write",
access.Humanize(LetterCasing.Title, EnumHumanizeSource.EnumName));
AssertEqual(Access.Read, "CAN VIEW".DehumanizeTo<Access>());
AssertEqual(null, "missing".DehumanizeTo<Access>(OnNoMatch.ReturnsNull));
AssertEqual("", ((Access)8).Humanize());
AssertEqual("Awaiting reviewer", DeliveryState.NeedsReview.Humanize());
AssertEqual(
"Awaiting reviewer",
DeliveryState.NeedsReview.Humanize(
LetterCasing.AllCaps,
EnumHumanizeSource.Default));
AssertEqual(
"Needs review",
DeliveryState.NeedsReview.Humanize(
LetterCasing.Sentence,
EnumHumanizeSource.EnumName));
AssertEqual(
"Review required",
DeliveryState.NeedsReview.Humanize(
LetterCasing.AllCaps,
EnumHumanizeSource.DisplayName));
AssertEqual(
"Awaiting reviewer",
DeliveryState.NeedsReview.Humanize(
LetterCasing.AllCaps,
EnumHumanizeSource.DisplayDescription));
AssertEqual(
"Review",
DeliveryState.NeedsReview.Humanize(
LetterCasing.AllCaps,
EnumHumanizeSource.DisplayShortName));
AssertEqual(
"Waiting in queue",
DeliveryState.QueuedItem.Humanize(
LetterCasing.AllCaps,
EnumHumanizeSource.DisplayShortName));
AssertEqual(
"Read",
Access.Read.Humanize(
LetterCasing.Sentence,
EnumHumanizeSource.DisplayName));
AssertEqual(DeliveryState.NeedsReview, "NeedsReview".DehumanizeTo<DeliveryState>());
AssertEqual(DeliveryState.NeedsReview, "Needs review".DehumanizeTo<DeliveryState>());
AssertEqual(DeliveryState.NeedsReview, "Review required".DehumanizeTo<DeliveryState>());
AssertEqual(DeliveryState.NeedsReview, "Awaiting reviewer".DehumanizeTo<DeliveryState>());
AssertEqual(DeliveryState.NeedsReview, "REVIEW".DehumanizeTo<DeliveryState>());

Console.WriteLine("Can view and Can edit; explicit sources; all aliases -> NeedsReview");

static void AssertEqual<T>(T expected, T actual)
{
if (!EqualityComparer<T>.Default.Equals(expected, actual))
throw new InvalidOperationException($"Expected '{expected}', got '{actual}'.");
}

[Flags]
enum Access
{
[Display(Description = "None")]
None = 0,
[Display(Description = "Can view")]
Read = 1,
[Display(Description = "Can edit")]
Write = 2
}

enum DeliveryState
{
[Display(
Name = "Review required",
Description = "Awaiting reviewer",
ShortName = "Review")]
NeedsReview,
[Display(Name = "Waiting in queue")]
QueuedItem
}

Choose metadata and casing

Humanizer prefers supported description/display metadata before deriving words from the member name. That chosen value is also the member's dehumanization label. On Humanizer 4, LetterCasing changes only labels derived from the member name; authored metadata preserves its exact casing. Configurator.UseEnumDescriptionPropertyLocator can select a custom description-like property globally and must run before enum metadata is cached.

Select the humanization source

On Humanizer 4, pass both LetterCasing and EnumHumanizeSource when the default metadata precedence is not the desired presentation:

  • Default preserves the existing precedence. A DisplayAttribute uses its Description, then its Name, then the member name. Without that attribute, a configured description-like property is checked before the member name.
  • EnumName derives text from the member name and applies the requested casing.
  • DisplayName uses DisplayAttribute.Name.
  • DisplayDescription uses DisplayAttribute.Description.
  • DisplayShortName uses DisplayAttribute.ShortName, then its Name.

If selected display metadata is missing, Humanizer falls back to the humanized enum member name and applies the requested casing. Authored display metadata preserves its casing, and localized DisplayAttribute getters are honored. For flags, the selected source and fallback apply independently to each constituent before the labels are joined.

Source selection intentionally uses the Humanize(LetterCasing, EnumHumanizeSource) overload; there is no source-only overload, so existing calls such as Humanize(default) and Humanize(0) keep their prior meaning. An undefined EnumHumanizeSource throws ArgumentOutOfRangeException. Explicit source selection reads only the chosen DisplayAttribute property, so an unrelated invalid localized property does not break the call. A custom description-property locator affects the default path, not the explicitly selected DisplayAttribute sources. It does not change dehumanization aliases or their collision rules.

Parse accepted aliases

On Humanizer 4, DehumanizeTo<TEnum> recognizes the raw member name, its humanized form, and DisplayAttribute name, description, and short-name values. It also recognizes a configured description attribute value when that value becomes the member's authored description. Matching is case-insensitive, including for flags, but does not trim whitespace.

Handle missing input

The default DehumanizeTo<TEnum> throws NoMatchFoundException. Pass OnNoMatch.ReturnsNull to the nullable generic overload when an unknown label is expected.

Format flags

Known nonzero flags are humanized and joined using the current UI culture. A defined zero member uses its label. A value containing only unknown bits humanizes to an empty string.

Pitfall

Aliases should be unambiguous even though matching is case-insensitive. On Humanizer 4, a current humanized representation takes precedence over a supplemental alias when labels collide; collisions among supplemental aliases retain the later enum value in unsigned numeric order. Prefer the generic enum overloads. Runtime enum humanization and the runtime Type dehumanization overload use reflection and are annotated with RequiresDynamicCode and RequiresUnreferencedCode; they are not the safe default for trimmed or Native AOT applications.

Version notes

Enum APIs span the documented corpus, but Humanizer 3 changes generic constraints and metadata configuration. The flags example runs against the selected package. Consult the selected API before migrating code that stores values as the base Enum type. Metadata casing preservation when a LetterCasing argument is supplied, and dehumanization through raw, humanized, and supplemental metadata aliases, are Humanizer 4 behavior. EnumHumanizeSource and its two casing-plus-source overloads are also Humanizer 4 APIs.