Skip to content

Two-Field Consistency

Some rules check that two fields agree with each other — for example, that a room number prefix matches the level name prefix. This requires computing a boolean from two fields and surfacing it as a validation target.


The pattern

Compute both fields, compare them, and expose the result as a boolean in the output:

$[type = "Room"].(
  $roomPrefix  := $split(values.number, ".")[0];
  $levelPrefix := $split(values.level, " ")[0];

  {
    "id": id,
    "name": name,
    "number": values.number,
    "level": values.level,
    "prefixMatch": $roomPrefix = $levelPrefix
  }
)

The validator then checks prefixMatch = true.


With an applicability check

When the comparison only makes sense in some cases — for example, only when both fields are filled — add an applicability gate:

$[type = "FamilySymbol"].(
  $assemblyCode   := $string(values.assemblyCode);
  $fireRating     := values.fireRating;

  $checkApplicable := $exists(values.fireRating) and values.fireRating != null;
  $isEqual         := $checkApplicable
                        ? $assemblyCode ~> /^35\./
                        : false;

  {
    "id": id,
    "name": name,
    "assemblyCode": $assemblyCode,
    "fireRating": $fireRating,
    "checkApplicable": $checkApplicable,
    "isEqual": $isEqual
  }
)[checkApplicable = true]

The validator checks isEqual = true. Elements where checkApplicable is false never reach the validator.


Returning both fields in the output

Always include both compared fields in the output. This gives the validator context for the error message and allows Scriban templates to reference both values:

{
  "id": id,
  "name": name,
  "assemblyCode": values.assemblyCode,
  "fireRating": values.fireRating,
  "prefixMatch": $roomPrefix = $levelPrefix
}

Error messages can then reference {{assemblyCode}} and {{fireRating}} to show the user what was found.


Multi-step validation with collection validator

When there are multiple checks (exists → has value → value is correct), use a collection validator with and. Each sub-validation checks one field in the output:

{
  "id": id,
  "name": name,
  "param": {
    "exists":        $sp.exists,
    "hasValue":      $sp.hasValue,
    "valueAsString": $sp.valueAsString
  }
}

Sub-validations then check param.exists, param.hasValue, and param.valueAsString independently.


Common mistakes

  • Checking the result field in the filter predicate instead of a gate — $[type = "Room" and prefixMatch = false] is wrong because prefixMatch does not exist yet at the filter stage
  • Omitting one of the compared fields from the output — the error message cannot show the user what was found
  • Not using a gate when one field may be null — comparing null = "1" returns false but does not cause an error; the issue is that a null field should not be validated at all