Humanizr/Humanizer · error · InvalidOperationException
Unsupported number-to-words contract member kind '{member.Ki
Error message
Unsupported number-to-words contract member kind '{member.Kind}'. What it means
The number-to-words contract factory maps each contract member 'kind' to a C# expression generator via a switch expression. If a member's kind is not one of the ~20 supported kinds (string, int64, enum, builder, profile-object, etc.), the default arm throws. This is a generator-level configuration error, not a locale YAML issue.
Source
Thrown at src/Humanizer.SourceGenerators/Generators/ProfileCatalogs/NumberToWordsEngineContractFactory.cs:423
"string-array" => CreateStringArrayExpression(EngineContractUtilities.GetRequiredElement(root, member.SourcePath)),
"optional-string-array" => EngineContractUtilities.TryGetElement(root, member.SourcePath, out var optionalArray)
? CreateStringArrayExpression(optionalArray)
: "Array.Empty<string>()",
"int-string-dictionary" => CreateStringIntFrozenDictionaryExpression(EngineContractUtilities.GetRequiredElement(root, member.SourcePath)),
"nullable-int-string-dictionary" => CreateNullableIntStringDictionaryValue(root, member),
"string-string-dictionary" => CreateStringStringFrozenDictionaryExpression(EngineContractUtilities.GetRequiredElement(root, member.SourcePath)),
"nullable-string-string-dictionary" => EngineContractUtilities.TryGetElement(root, member.SourcePath, out var optionalStringDictionary)
? CreateOptionalStringStringFrozenDictionaryExpression(root, EngineContractUtilities.GetLeafPropertyName(member.SourcePath))
: member.MissingValue == "empty"
? "FrozenDictionary<string, string>.Empty"
: "null",
"char-string-dictionary" => CreateCharStringFrozenDictionaryExpression(EngineContractUtilities.GetRequiredElement(root, member.SourcePath)),
"nullable-char-string-dictionary" => EngineContractUtilities.TryGetElement(root, member.SourcePath, out var optionalCharDictionary)
? CreateCharStringFrozenDictionaryExpression(optionalCharDictionary)
: "null",
"nullable-int-set" => CreateNullableIntSetValue(root, member),
"builder" => CreateBuilderValue(root, member),
_ => throw new InvalidOperationException($"Unsupported number-to-words contract member kind '{member.Kind}'.")
};
static string CreateObjectValue(JsonElement root, EngineContractMember member, bool useCultureParameter)
{
var objectRoot = string.IsNullOrWhiteSpace(member.SourcePath)
? root
: EngineContractUtilities.GetRequiredElement(root, member.SourcePath);
// Nested profile objects let the YAML stay grouped by meaning while the generated code
// still calls an explicit runtime constructor shape. This keeps the authoring surface
// readable without paying any runtime mapping cost.
return member.TypeName is null
? "new(" + CreateConstructorValues(objectRoot, member.Members, useCultureParameter) + ")"
: "new " + member.TypeName + "(" + CreateConstructorValues(objectRoot, member.Members, useCultureParameter) + ")";
}
static string CreateEnumValue(JsonElement root, EngineContractMember member)
{View on GitHub (pinned to ffc2b77c0f)
Solutions
- Add a matching case to the CreateMemberValue switch expression in NumberToWordsEngineContractFactory.cs for the new kind.
- If the kind was mistyped, correct the Kind value in the EngineContractMember definition to match a supported kind.
- Review the EngineContractCatalog definitions for the affected engine and verify every member kind has a factory case.
Example fix
// before (contract member with unsupported kind)
new EngineContractMember(
kind: "decimal",
sourcePath: "factor",
...)
// after — use a supported kind or add a factory case
new EngineContractMember(
kind: "int64",
sourcePath: "factor",
...) Defensive patterns
Strategy: validation
Validate before calling
// Validate all contract member kinds are supported before generation:
static readonly HashSet<string> SupportedKinds = new()
{
"profile-object","optional-profile-object","culture","string","nullable-string",
"bool","presence-bool","int64","nullable-int64","int32","enum",
"string-array","optional-string-array","int-string-dictionary",
"nullable-int-string-dictionary","string-string-dictionary",
"nullable-string-string-dictionary","char-string-dictionary",
"nullable-char-string-dictionary","nullable-int-set","builder"
};
foreach (var m in contract.Members)
if (!SupportedKinds.Contains(m.Kind))
throw new ArgumentException($"Unsupported kind '{m.Kind}' in contract."); Type guard
static bool IsSupportedMemberKind(string kind) =>
SupportedKinds.Contains(kind); Prevention
- When adding a new member kind to EngineContractMember, immediately add the corresponding factory case.
- Write a test that enumerates all contracts and asserts every member kind has a factory case.
- Use constants or an enum for kind values to prevent typos.
When it happens
Trigger: An EngineContractMember is defined with a Kind value not handled by the switch in CreateMemberValue. For example, a new kind 'decimal' was added to a contract definition but no corresponding case exists in the factory. Typically introduced by editing EngineContractCatalog.
Common situations: Adding a new member kind to a contract definition without adding a factory case. Renaming a kind in one place but not the other. Version mismatch between contract definitions and the factory after a partial merge.
Related errors
- Enum members require an enum type.
- Builder members require a builder name.
- Unexpected east-asian scale builder.
- Unsupported number-to-words builder '{member.Builder}'.
- Array-backed metric scale word contracts require a value pro
AI-assisted analysis of Humanizr/Humanizer@ffc2b77c0f (2026-08-13).
Data as JSON: /api/errors/958d609c53940ec3.
Report an issue: GitHub.