Skip to main content
Version: 🚧 Next

Condition

A condition is a tree-shaped expression that is evaluated against a set of Harmoney data points. It is used in many parts of the configuration, for example in Validations, Actions, Providers, Flow Definitions and Form Definitions.

Every node of a condition tree must contain exactly one of the properties listed below: either a logical operator (not, and, or) that nests further conditions, or a comparison that targets a data point through a fieldPath.

A wrong operator never raises a configuration error: the condition simply evaluates to false (or, for a negated operator, to true). Check Choosing an operator before configuring one.

Logical operators​

Logical operators do not have a fieldPath of their own. They combine other conditions.

and​

A list of nested conditions; all of them must evaluate to true. Use it for "X and Y", and to guard a condition with isNotNull (see Unanswered questions).

KYC dossier of a company client
{
"and": [
{ "equals": { "fieldPath": "type", "value": "KNOW_YOUR_CUSTOMER" } },
{ "equals": { "fieldPath": "client.partyType", "value": "COMPANY" } }
]
}

or​

A list of nested conditions; at least one of them must evaluate to true. Use it when the alternatives target different field paths, or to combine several contains on a list. For several values of the same single-value field, prefer in.

High-risk or PEP client
{
"or": [
{ "equals": { "fieldPath": "metadata.HIGH_RISK", "value": true } },
{ "equals": { "fieldPath": "metadata.PEP", "value": true } }
]
}

not​

A single nested condition that must evaluate to false. Prefer the dedicated negated operator when there is one (notEquals, notIn, notContains, notLike, isNotNull, isNotInList); use not to invert a combination.

Not both SALARY and SAVINGS selected
{
"not": {
"and": [
{ "contains": { "fieldPath": "dossier.questions.SOURCES_OF_FUNDS", "value": "SALARY" } },
{ "contains": { "fieldPath": "dossier.questions.SOURCES_OF_FUNDS", "value": "SAVINGS" } }
]
}
}
Combining operators
{
"and": [
{ "equals": { "fieldPath": "type", "value": "KNOW_YOUR_CUSTOMER" } },
{ "not": { "equals": { "fieldPath": "status", "value": "CLOSED" } } },
{ "or": [
{ "equals": { "fieldPath": "metadata.HIGH_RISK", "value": true } },
{ "equals": { "fieldPath": "metadata.PEP", "value": true } }
]}
]
}

Comparison conditions​

All comparisons take a fieldPath that points to a data point. The available field paths depend on the context in which the condition is used (see Field path below). Each operator below states whether it works on a single value or on a list (for example a MULTIPLESELECT answer).

equals / notEquals​

Works on: a single value. The data point must (not) be equal to value.

  • equals compares text representations, so "true" matches a boolean and "COMPANY" matches an enum value.
  • For a decimal number, write the value with its decimals (25.0), otherwise 25 does not match 25.0.
  • On a list, equals is never true and notEquals is always true.
equals: company client
{ "equals": { "fieldPath": "client.partyType", "value": "COMPANY" } }
equals: boolean question answered yes
{ "equals": { "fieldPath": "dossier.questions.IS_PEP", "value": true } }
notEquals: dossier is not closed
{ "notEquals": { "fieldPath": "status", "value": "CLOSED" } }

in / notIn​

Works on: a single value. The data point must (not) be equal to one of the strings in values. Shorter than an or of equals on the same field.

  • On a list, in is never true and notIn is always true.
in: single select answer is SALARY or PENSION
{ "in": { "fieldPath": "dossier.questions.SOURCE_OF_FUNDS", "values": ["SALARY", "PENSION"] } }
notIn: document type is neither BYLAWS nor ID_CARD
{ "notIn": { "fieldPath": "type", "values": ["BYLAWS", "ID_CARD"] } }

contains / notContains​

Works on: a list. The data point must be a collection that does (not) contain value. This is the operator for MULTIPLESELECT, MULTI_AUTOCOMPLETE and MULTI_COUNTRY answers.

  • On a single value, contains is never true and notContains is always true.
  • Combine several contains with or (any of the options) or and (all of the options).
MULTIPLESELECT needs contains

A MULTIPLESELECT answer is a list, even when only one option is selected. equals, in and isInList never match it; notEquals, notIn and isNotInList always do; like and notLike match against the text of the whole list. Use contains / notContains.

contains: SALARY is one of the selected sources of funds
{ "contains": { "fieldPath": "dossier.questions.SOURCES_OF_FUNDS", "value": "SALARY" } }
contains + or: INHERITANCE or DONATION selected
{
"or": [
{ "contains": { "fieldPath": "dossier.questions.SOURCES_OF_FUNDS", "value": "INHERITANCE" } },
{ "contains": { "fieldPath": "dossier.questions.SOURCES_OF_FUNDS", "value": "DONATION" } }
]
}
contains + and: both SALARY and SAVINGS selected
{
"and": [
{ "contains": { "fieldPath": "dossier.questions.SOURCES_OF_FUNDS", "value": "SALARY" } },
{ "contains": { "fieldPath": "dossier.questions.SOURCES_OF_FUNDS", "value": "SAVINGS" } }
]
}
notContains: SAVINGS is not one of the selected sources of funds
{ "notContains": { "fieldPath": "dossier.questions.SOURCES_OF_FUNDS", "value": "SAVINGS" } }

like / notLike​

Works on: a single text value. The data point must (not) match the regex in value; the whole text must match, and % is interpreted as .*.

  • On a list, the result is unreliable: the pattern is matched against the text of the whole list.
like: name starts with ACME
{ "like": { "fieldPath": "name", "value": "ACME%" } }
notLike: name does not start with TEST
{ "notLike": { "fieldPath": "name", "value": "TEST%" } }

greaterThan / smallerThan​

Works on: a single number, or a date compared with a relative date-time. Both comparisons are strict; for "at least", combine with equals in an or.

greaterThan: UBO owns more than 25%
{ "greaterThan": { "fieldPath": "ubo.percentage", "value": 25 } }
smallerThan: income below 30000
{ "smallerThan": { "fieldPath": "dossier.questions.YEARLY_INCOME", "value": 30000 } }
At least 25%
{
"or": [
{ "greaterThan": { "fieldPath": "ubo.percentage", "value": 25 } },
{ "equals": { "fieldPath": "ubo.percentage", "value": 25 } }
]
}

isNull / isNotNull​

Works on: a single value and a list. isNull matches a missing value, a blank text or an empty list; isNotNull matches the opposite. Use them to check whether a question is answered.

isNull: question not answered
{ "isNull": { "fieldPath": "dossier.questions.SOURCES_OF_FUNDS" } }
isNotNull: a BYLAWS document exists
{ "isNotNull": { "fieldPath": "client.documents[?(@.type == 'BYLAWS')]" } }

isInList / isNotInList​

Works on: a single value. Checks the data point against a list defined on the organization. listIdentifier selects the list. listFilterValue is optional:

  • With listFilterValue: the list is treated as a keyβ†’value map. The condition matches when the entry keyed by the data point carries a value equal to listFilterValue.
  • Without listFilterValue (blank or omitted): a pure membership check. isInList matches when the data point exists as an entry (identifier) in the list, regardless of any value stored against it; isNotInList matches when it does not. Use this for plain membership lists that have no per-entry values.
  • On a list, isInList is never true and isNotInList is always true once the value is filled in.
isInList: value match, country flagged HIGH
{
"isInList": {
"fieldPath": "client.country",
"listIdentifier": "HIGH_RISK_COUNTRIES",
"listFilterValue": "HIGH"
}
}
isNotInList: membership only, nationality not in the EU-countries list
{
"isNotInList": {
"fieldPath": "party.individual.nationality",
"listIdentifier": "EU_COUNTRIES"
}
}

Choosing an operator​

Single value or list?​

The answer type of a question decides how its answer is stored in the data point (dossier.questions.<QUESTION_ID>, party.questions.<QUESTION_ID>).

Answer typeValue in the data pointExample value
TEXT, TEXT_AREA, RICH_TEXT, PHONE, AUTOCOMPLETE, COUNTRY, SINGLESELECT, SINGLEDROPDOWNa single text"SALARY"
NUMBERa single number25000
DATEa single date"2024-12-31"
BOOLEAN_RADIO, BOOLEAN_TOGGLE, BOOLEAN_CHECKBOXtrue or falsetrue
MULTIPLESELECT, MULTI_AUTOCOMPLETE, MULTI_COUNTRYa list of texts, even when only one option is selected["SALARY", "SAVINGS"]

For a select question, compare with the option's stored value (case-sensitive), not its translated label.

Outside questions, a field path that ends on a collection (for example documents) is a list. A wildcard path that continues after the collection (for example documents[*].type) is different: the operator is evaluated on the single value of each element, and all elements must match. To check that at least one element matches, filter the collection and use isNotNull:

At least one PASSPORT document
{ "isNotNull": { "fieldPath": "documents[?(@.type == 'PASSPORT')]" } }
All documents are PASSPORT documents
{ "equals": { "fieldPath": "documents[*].type", "value": "PASSPORT" } }

Which operator for which check​

You want to check that…Single valueList (MULTIPLESELECT, …)
the answer is exactly Xequalsβ€”
the answer is X or Yinor of two contains
the answer includes Xβ€”contains
the answer includes X and Yβ€”and of two contains
the answer is not XnotEqualsnotContains
the answer is none of X, YnotInand of two notContains
the answer is (not) in an organization listisInList / isNotInListβ€”
the answer matches a patternlike / notLikeβ€”
the answer is above / below a number or dategreaterThan / smallerThanβ€”
the question is answeredisNotNullisNotNull
the question is not answeredisNullisNull

A β€” means the operator does not work on that kind of value: the condition evaluates, but never gives the intended result.

Unanswered questions​

An unanswered question has no value. Positive operators (equals, in, contains, like, greaterThan, smallerThan, isInList) then evaluate to false, but negated operators (notEquals, notIn, notContains, notLike) evaluate to true. isNotInList is the exception: it evaluates to false on an unanswered question. When a negated operator must not match an unanswered question, guard it with isNotNull:

Answered, and SAVINGS is not selected
{
"and": [
{ "isNotNull": { "fieldPath": "dossier.questions.SOURCES_OF_FUNDS" } },
{ "notContains": { "fieldPath": "dossier.questions.SOURCES_OF_FUNDS", "value": "SAVINGS" } }
]
}

Field path​

A fieldPath selects a data point by name, using dot notation to descend into nested objects (for example client.individual.maritalStatus). For collection-valued data points, JSON-path style filters and wildcards are supported, for example dossier.client.documents[*].assessment.assessment or documents[?(@.type == 'BYLAWS')].

Always guard collection paths with isNotNull

When using JSON-path wildcards ([*]) on a collection β€” for example dossier.client.documents[*].assessment.assessment β€” always wrap the expression in an and condition whose first clause is an isNotNull check on the parent collection (e.g. dossier.client.additionalTaxInformation).

Without this guard, if the parent collection is null or empty, every operator (in, contains, equals, …) that traverses it will evaluate to true instead of false, producing incorrect results.

Safe pattern β€” guard the parent collection first
{
"and": [
{
"isNotNull": {
"fieldPath": "dossier.client.additionalTaxInformation"
}
},
{
"in": {
"fieldPath": "dossier.client.additionalTaxInformation[*].countryCode",
"values": ["IN", "US"]
}
}
]
}

Which paths are valid depends on the data point source bound to the condition by the consuming configuration. Each consumer of the condition decides which source applies. Sources currently in use:

  • PartyDataPoint (also PartyExternalDataPoint)
  • IdentityDataPoint
  • DossierDataPoint (also DossierExternalDataPoint)
  • DocumentDataPoint
  • RbaDataPoint
  • UboDataPoint
  • RelatedPartyDataPoint
  • SignatoryDataPoint
  • DueDiligenceSubjectDataPoint
  • DueDiligenceProviderInput

The Harmoney UI offers field path autocomplete based on the source of the surrounding configuration, so a condition embedded in a dossier-scoped setting will only show dossier paths, etc.

Examples​

Block status transition for closed dossiers of high-risk clients
{
"and": [
{ "equals": { "fieldPath": "type", "value": "KNOW_YOUR_CUSTOMER" } },
{ "equals": { "fieldPath": "metadata.HIGH_RISK", "value": true } },
{ "notEquals": { "fieldPath": "status", "value": "CLOSED" } }
]
}
Require a BYLAWS document for company clients
{
"and": [
{ "equals": { "fieldPath": "client.partyType", "value": "COMPANY" } },
{ "isNotNull": { "fieldPath": "dossier.documents[?(@.type == 'BYLAWS')]" } }
]
}
UBO with at least 25% ownership in a sanctioned country
{
"and": [
{ "greaterThan": { "fieldPath": "percentage", "value": 25 } },
{ "isInList": {
"fieldPath": "party.individual.nationality",
"listIdentifier": "SANCTIONED_COUNTRIES",
"listFilterValue": "code"
}}
]
}
Document cards driven by a multiple select and a single select question
{
"cards": {
"PAYSLIP": {
"requiredWhen": {
"contains": { "fieldPath": "dossier.questions.SOURCES_OF_FUNDS", "value": "SALARY" }
}
},
"PROOF_OF_INHERITANCE": {
"requiredWhen": {
"or": [
{ "contains": { "fieldPath": "dossier.questions.SOURCES_OF_FUNDS", "value": "INHERITANCE" } },
{ "contains": { "fieldPath": "dossier.questions.SOURCES_OF_FUNDS", "value": "DONATION" } }
]
}
},
"COMPANY_REGISTRATION": {
"optionalWhen": {
"in": { "fieldPath": "dossier.questions.EMPLOYMENT_STATUS", "values": ["SELF_EMPLOYED", "COMPANY_DIRECTOR"] }
}
}
}
}