Humanizr/Humanizer · error · InvalidOperationException

Array-backed metric scale word contracts require a value pro

Error message

Array-backed metric scale word contracts require a value property.

What it means

When collecting metric scale words from an array-backed contract, the factory needs to know which property in each array row holds the numeric scale value. If the MetricScaleWordContract has no FixedValue (which would bypass the array) and no ValueProperty (which names the value column), the code cannot enumerate rows and throws.

Source

Thrown at src/Humanizer.SourceGenerators/Generators/ProfileCatalogs/NumberToWordsEngineContractFactory.cs:108

                AddContractMetricScaleWords(scaleWordContract, root, builderRoot, words);
            }
        }

        static void AddContractMetricScaleWords(
            MetricScaleWordContract contract,
            JsonElement profileRoot,
            JsonElement root,
            Dictionary<char, MetricScaleWordDefinition> words)
        {
            if (contract.FixedValue is { } fixedValue)
            {
                AddContractMetricScaleWord(contract, fixedValue, profileRoot, root, words);
                return;
            }

            if (contract.ValueProperty is null)
            {
                throw new InvalidOperationException("Array-backed metric scale word contracts require a value property.");
            }

            foreach (var row in root.EnumerateArray())
            {
                AddContractMetricScaleWord(
                    contract,
                    GetRequiredInt64(row, contract.ValueProperty),
                    profileRoot,
                    row,
                    words);
            }
        }

        static void AddContractMetricScaleWord(
            MetricScaleWordContract contract,
            long value,
            JsonElement profileRoot,
            JsonElement row,

View on GitHub (pinned to ffc2b77c0f)

Solutions

  1. In the engine contract definition (C# code, not YAML), set the valueProperty parameter on the MetricScaleWordContract to the YAML property name holding the numeric value.
  2. Alternatively, set fixedValue to a specific metric value if the contract describes a single known scale word rather than an array.
  3. Audit the MetricScaleWordContract constructor call to ensure at least one of valueProperty or fixedValue is non-null.

Example fix

// before (in EngineContractCatalog C# code)
new MetricScaleWordContract(
    valueProperty: null,
    singularProperty: "singular",
    pluralProperty: "plural")
// after
new MetricScaleWordContract(
    valueProperty: "value",
    singularProperty: "singular",
    pluralProperty: "plural")
Defensive patterns

Strategy: validation

Validate before calling

// In the engine contract definition code, assert the contract is well-formed at startup:
if (contract.FixedValue is null && contract.ValueProperty is null)
    throw new ArgumentException("MetricScaleWordContract must define either FixedValue or ValueProperty.");

Type guard

// Type guard for MetricScaleWordContract completeness
static bool IsMetricScaleWordContractValid(MetricScaleWordContract c) =>
    c.FixedValue is not null || !string.IsNullOrEmpty(c.ValueProperty);

Prevention

When it happens

Trigger: A MetricScaleWordContract is constructed with both fixedValue=null and valueProperty=null, then AddContractMetricScaleWords is called. The contract reaches line 106 where ValueProperty is null and throws. This is typically a bug in the engine contract definition code, not in locale YAML.

Common situations: Defining a new engine contract in EngineContractCatalog that includes a metric-scale-words member without specifying either a fixed value or a value property. Refactoring contract definitions and dropping the valueProperty parameter. A contract was designed for FixedValue but the caller passes array data.

Related errors


AI-assisted analysis of Humanizr/Humanizer@ffc2b77c0f (2026-08-13). Data as JSON: /api/errors/fd10baefe81f2bdc. Report an issue: GitHub.