Skip to content

Field definition validation

General information

Product definitions have several properties which contain lists of fields with validation rules that apply in a specific object scope.

A field definition describes one input field: what it is called, what type of value it accepts, whether it is mandatory, what range the value must fall in, and under which circumstances it applies at all. Because the rules live in product configuration, the required information varies depending on the product and the product provider: to order a T-Shirt not the same properties are required as to buy a train ticket.

Info

On the backend these definitions are used to validate the data dynamically. For best user experience you should do this already on the client.

Field definitions should be used to build a dynamic UI which collects the necessary information for ordering different products.

Important

There can be several field definitions with the same propertyId in the same list of fields. That doesn't mean that the property has to be rendered twice — it means different validation rules are applied to the same property.

Scopes

Each list of field definitions validates a different object:

In Order context:

Field definition list Validated object
customerField order.customer
customerField with address. prefix the customer's billing / shipping address
itemField order.orderedItem[i]
travelerField order.orderedItem[i].orderedItem.traveler[j]
vehicleField order.orderedItem[i].orderedItem.vehicle[j]

itemField, travelerField and vehicleField are also used to validate get-offers requests — see Required for offers.

In Stay context:

Field definition list Validated object
stayField the stay itself
customerField stay.customer
travelerField stay.member[i]
travelerGroupField stay.group[i]

Note

travelerField is used in both contexts against different objects: order travelers and stay members. A definition written for one context will also be applied in the other if the same list is configured, so the propertyIds must make sense for both.

Field definition properties

Property Type Meaning
propertyId string Which value of the payload this definition applies to. See Providing values.
type string Value type. See Field types.
additionalType string Semantic role of the field. See Additional types.
name string Human-readable field name. Substituted into validation error messages.
required bool/null Mandatory for orders. Tri-state — see Required.
requiredForOffers bool/null Mandatory for offer requests. Tri-state — see Required for offers.
possibleValue dictionary Allowed values. Keys are the accepted values, values are display labels. Empty = unrestricted.
rangeMin / rangeMax string Value bounds. Interpretation depends on type — see Range validation.
rangeBasePropertyId string For date/datetime fields: the property the range is measured from.
displayConditions array Conditions defining whether this field applies at all — see Display conditions.
customErrorNames dictionary Override validation error messages per rule — see Custom error messages.
readonly bool/null Value cannot be modified — see Readonly.
description string Human-readable description of the field. Not used for validation.

Legacy properties

parentFieldPropertyId, parentFieldValue, parentFieldOperator, parentFieldType and parentFieldModel are obsolete but still honoured — they are superseded by displayConditions. requiredErrorName is obsolete as well — superseded by customErrorNames. See Migrating from parentField properties.

Field types

type Expected value rangeMin / rangeMax mean
integer, int Whole number Numeric bounds
number Decimal number Numeric bounds
text String
select, radio String; one key of possibleValue
media String (a media reference)
medialist JSON array of strings
bool true / false
date Date; any time component is ignored (in Swiss local time) ISO 8601 duration offset
datetime Date and time ISO 8601 duration offset
duration ISO 8601 duration (e.g. PT2H30M) ISO 8601 duration
multiselect JSON array of strings; each item a key of possibleValue
array JSON array Number of items
checksum JSON array of strings Sum of the selected child items — see Checksum
checksumItem Whole number See Checksum item

Type names are matched case-insensitively: "Date", "date" and "DATE" are equivalent. A missing or unrecognised type is not an error — the field falls back to a plain existence check.

Additional types

additionalType marks the semantic role of a field. Most values are display or client-side hints; the only value that changes backend validation behaviour is sales-cut-off.

Value Description
sales-cut-off Defines when the product stops being sellable — see Sales cut-off validation. The only value that affects backend validation.
ProfileImage Profile picture of a person
TravelerMediaImage Media image associated with a traveler
SupportingDocument Supporting document (e.g. proof of eligibility for special conditions)
AddonSelector Links this field to an add-on product. The client can resolve the add-on via the product ID stored in the field definition's parentFieldValue property
WidthOneOfThree Display hint: field occupies one third of the row width
WidthTwoOfThree Display hint: field occupies two thirds of the row width
ageCategory Marks the field as an age category that is subject to age validation
mask-keycard Input mask hint: the value is a keycard number — see Input masks
mask-swisspass Input mask hint: the value is a SwissPass number — see Input masks

An unrecognised additionalType value is ignored rather than rejected.

Input masks

The mask-keycard and mask-swisspass additional types tell the client to apply an input mask so the value is collected in the expected format (X stands for a digit):

additionalType Format
mask-keycard XX-XXXX XXXX XXXX XXXX XXXX-X
mask-swisspass SXX-XXX-XXX-XXX

These are hints for the client UI — the backend does not validate the value against the mask.

Providing values

propertyId defines where the value of a field lives in the request payload:

Additional properties. A propertyId beginning with additionalProperty. (case-insensitive) refers to an entry of the payload's additionalProperty collection with the same propertyId:

{
  "propertyId": "additionalProperty.info1",
  "type": "text",
  "name": "swim experience",
  "required": true
}
{
  "additionalProperty": [
    {
      "propertyId": "additionalProperty.info1",
      "value": "good swimmer"
    }
  ]
}
{
  "additionalProperty": [
    {
      "propertyId": "info1",
      "value": "good swimmer"
    }
  ]
}

Note

Alternative request contains additional property defined with propertyId = info1 instead of propertyId = additionalProperty.info1. This is an alternative way of defining additional properties which works completely the same meanwhile the field definition propertyId always have prefix additionalProperty.* when targets at additional property.

Additional property values are always strings on the wire and are parsed according to the declared type. A value that cannot be parsed produces a "not allowed" validation error.

Object properties. Any other propertyId refers to a property of the validated object itself. Paths are dot-separated and case-insensitive — e.g. orderedItem.validFrom on an order item.

Note

For array-typed fields an empty or absent value counts as a valid empty array. Only a value that is present but is not a JSON array is invalid.

Validation rules

Required

When a field definition has the required property defined with value true, then this field must be present when the object is created or modified.

Value explanations:

  • null - field is not relevant for this product
  • true - field is required
  • false - field is not required, but can be provided and used

Required for offers

Properties which have the requiredForOffers property defined with a value different from null will be used to validate get offers requests.

Value explanations:

  • null - field is not used for offer
  • true - field is required for offer
  • false - field is not required for offer, but can be provided and used in offer requests

Note

For both flags the distinction between null and false is meaningful, and only true enforces presence. A value that is provided is still validated against type, range and possibleValue even when the flag is false. When no value is provided and the flag is not true, no further checks run.

Readonly

When a field definition has the readonly property defined with value true, then this field will not be modified (a new value will be ignored) during any edit operation.

Range validation

When a field definition has rangeMin and/or rangeMax properties defined, then the value provided for this field must be in the defined range. Either bound may be given on its own, and both bounds are inclusive: a value exactly equal to rangeMax passes.

How the bounds are interpreted depends on the field type:

type rangeMin / rangeMax format Bounds
integer, int Whole number, e.g. "10" The value itself
number Decimal number, e.g. "9.95" The value itself
duration ISO 8601 duration, e.g. "PT1H" The duration's length
date, datetime ISO 8601 duration offset, e.g. "-P6Y", "P2M" An offset from a base date — see below
array Whole number The number of items in the array
checksum Whole number The sum of the selected checksum items — see Checksum

Types not listed in the table ignore rangeMin and rangeMax entirely — including text, select, radio, bool, multiselect and medialist.

Watch the T in durations

In ISO 8601, hours, minutes and seconds live in the time part of a duration and require a T designator. PT16H is sixteen hours; P16H is not a valid duration and is rejected. Likewise PT10H30M, not P10H30M. P alone prefixes years, months, weeks and days: P2Y, P1D.

Date and datetime ranges

For fields with type date and datetime the bounds are offsets, not absolute dates. The allowed window is:

base date + rangeMin  ≤  provided value  ≤  base date + rangeMax

The base date is the value of the property defined in rangeBasePropertyId. If rangeBasePropertyId is not defined then the current UTC time is used as base value. When validating travelers, rangeBasePropertyId can also refer to a property of the related order item (e.g. orderedItem.validFrom).

Offsets may be negative. Note that a later date is a larger value, so rangeMax is the more recent bound:

{
  "propertyId": "birthDate",
  "type": "date",
  "name": "Date of birth",
  "required": true,
  "rangeMax": "-P6Y",
  "rangeBasePropertyId": "orderedItem.validFrom"
}

Read as: the traveler must be at least 6 years old on the travel date rather than today.

Info

rangeBasePropertyId can be applied for any date/datetime field. e.g., it concerns the validation of the traveler's age at the travel date.

Sales cut-off validation

When a field definition has type=date and additionalType=sales-cut-off defined, then special validation rules are applied. Such a field definition defines until when the product can be sold.

The field definition must have the rangeMin property defined with a value in ISO 8601 duration format. This range defines a negative offset which will be subtracted from datetime.now and compared with the value provided for this field.

Sales cut-off validation will properly work only with the following properties:

  • dateFrom - in get offers request
  • validFrom - in create order item request

A sales cut-off definition can sit alongside another definition for the same property: two definitions for dateFrom differing only in additionalType are valid and both apply — one carries the ordinary range rule, the other the cut-off.

Calculation explanation:

When (requestedDate - (datetime.now + duration(rangeMin))).totalSeconds < 0) then the product cannot be sold anymore.

  • requestedDate - contains date only value (e.g. 2025-10-09T00:00:00)
  • datetime.now - current date and time in UTC (e.g. 2025-10-09T15:00:00Z)
  • duration(rangeMin) - duration parsed from rangeMin property (e.g. -PT16H means negative 16 hours)

When the validation fails, a dedicated error message is returned: Sales for this product closed {relative time}.

Example

The next field definition means that sales will be closed at 16:00 swiss time today. So an offer cannot be received for today after 16:00 swiss time. But it is still possible to receive an offer for tomorrow.

{
  "propertyId": "dateFrom",
  "type": "date",
  "additionalType": "sales-cut-off",
  "rangeMin": "-PT16H",
  "requiredForOffers": true
}

Example 2

The next field definition means that sales will be closed at 10:30 swiss time today. So an order item cannot be created for the same day after 10:30 swiss time.

{
  "propertyId": "validFrom",
  "type": "date",
  "additionalType": "sales-cut-off",
  "rangeMin": "-PT10H30M"
}

Multiselect and checksum fields

Multiselect

Multiselect fields support selecting multiple answers. To do that it is necessary to provide a serialized JSON array as the value of the property specified in the request.

{
  "propertyId": "additionalProperty.question0",
  "type": "multiselect",
  "name": "How many people would you prefer to see in you travel group?",
  "required": true,
  "possibleValue": {
    "answer0": "Form 2 to 5",
    "answer1": "From 6 to 10",
    "answer2": "More than",
    "answer3": "Less than"
  }
},
{
  "propertyId": "additionalProperty.question0_answer2_numeric",
  "type": "integer",
  "name": "More than",
  "required": false,
  "possibleValue": {},
  "rangeMin": "0",
  "rangeMax": "2147483647",
  "parentFieldPropertyId": "additionalProperty.question0",
  "parentFieldValue": "answer2"
},
{
  "propertyId": "additionalProperty.question0_answer3_numeric",
  "type": "integer",
  "name": "Less than",
  "required": false,
  "possibleValue": {},
  "rangeMin": "0",
  "rangeMax": "2147483647",
  "parentFieldPropertyId": "additionalProperty.question0",
  "parentFieldValue": "answer3"
}
{
  "additionalProperty": [
    {
      "propertyId": "additionalProperty.question0",
      "value": "[\"answer0\", \"answer2\", \"answer3\"]"
    },
    {
      "propertyId": "additionalProperty.question0_answer2_numeric",
      "value": "25"
    },
    {
      "propertyId": "additionalProperty.question0_answer3_numeric",
      "value": "30"
    }
  ]
}

Multiselect field definitions can have related child field definitions which are gated by a display condition (or the legacy parentFieldPropertyId / parentFieldValue pair) referring to the multiselect field. For a multiselect parent, the condition is met when the selected array contains the configured value.

Information

It's not possible to specify multiple values in the condition value. So specifying an array (e.g. value = "[\"answer0\", \"answer1\"]") will not have any effect. Only single values are allowed.

Checksum

A checksum is a multiselect whose selected options each carry a number, where what matters is the total. "Choose your dishes, three courses in all": the customer picks options, enters a quantity for each, and the quantities must add up to a permitted total.

A checksum is a multiselect field which has child fields with type checksumItem related to its answer values. Whenever a value of the checksum multiselect is selected, the field with type checksumItem, with parentFieldPropertyId equal to the propertyId of the checksum field and with parentFieldValue equal to the selected value, will be taken into account for the calculation.

A checksum is considered valid only when the sum of the values of the checksum items (which are related to the selected values of the checksum) is between rangeMin and rangeMax specified in the checksum field.

Checksum item

It behaves as an int field and is used in conjunction with a checksum field. The main idea is to let the user type a number for the selected value of the checksum field.

Two constraints follow from how the linkage is matched:

  • parentFieldPropertyId and parentFieldValue are compared exactly — case and whitespace must match the checksum's propertyId and its possibleValue key.
  • Checksum items must live in the same field definition list as their checksum.

Note

Checksum items are the one place where parentFieldPropertyId / parentFieldValue are still required: the checksum linkage is not established via displayConditions. Also note that on the backend only the checksum's aggregate bounds are enforced — the rangeMin / rangeMax of individual checksumItem definitions should be enforced by the client UI.

Example of checksum items:

[
  {
    "type": "checksum",
    "propertyId": "additionalProperty.checksum_example",
    "name": "Where have you been?",
    "possibleValue": {
        "ukraine": "Ukraine",
        "switzerland": "Switzerland",
        "italy": "Italy"
    },
    "rangeMin": "3",
    "rangeMax": "6",
    "required": true
  },
  {
    "type": "checksumItem",
    "propertyId": "additionalProperty.checksum_item_ukraine",
    "parentFieldPropertyId": "additionalProperty.checksum_example",
    "parentFieldValue": "ukraine",
    "name": "How many time you've been to Ukraine?",
    "rangeMin": "0",
    "rangeMax": "6",
    "required": false
  },
  {
    "type": "checksumItem",
    "propertyId": "additionalProperty.checksum_item_switzerland",
    "parentFieldPropertyId": "additionalProperty.checksum_example",
    "parentFieldValue": "switzerland",
    "name": "How many time you've been to Switzerland?",
    "rangeMin": "0",
    "rangeMax": "6",
    "required": false
  },
  {
    "type": "checksumItem",
    "propertyId": "additionalProperty.checksum_item_italy",
    "parentFieldPropertyId": "additionalProperty.checksum_example",
    "parentFieldValue": "italy",
    "name": "How many time you've been to Italy?",
    "rangeMin": "0",
    "rangeMax": "6",
    "required": false
  }
]
    // next example has total sum of selected items euqal to 4 which is in the range [3..6]
    {
      "additionalProperty": [
        {
          "propertyId": "additionalProperty.checksum_example",
          "value": "[\"italy\", \"ukraine\"]"
        },
        {
          "propertyId": "additionalProperty.checksum_item_ukraine",
          "value": 3
        },
        { // this value will be ignored because it was not selected
          "propertyId": "additionalProperty.checksum_item_switzerland",
          "value": 6
        },
        {
          "propertyId": "additionalProperty.checksum_item_italy",
          "value": 1
        }
      ]
    }
    // the same as previous one
    {
      "additionalProperty": [
        {
          "propertyId": "additionalProperty.checksum_example",
          "value": "[\"italy\", \"ukraine\"]"
        },
        {
          "propertyId": "additionalProperty.checksum_item_ukraine",
          "value": 3
        },
        {
          "propertyId": "additionalProperty.checksum_item_italy",
          "value": 1
        }
      ]
    }

    // next example has total sum of selected items euqal to 6 which is in the range [3..6]
    {
      "additionalProperty": [
        {
          "propertyId": "additionalProperty.checksum_example",
          "value": "[\"switzerland\"]"
        },
        {
          "propertyId": "additionalProperty.checksum_item_ukraine",
          "value": 3
        },
        { // only this value will be taken into calculation
          "propertyId": "additionalProperty.checksum_item_switzerland",
          "value": 6
        },
        {
          "propertyId": "additionalProperty.checksum_item_italy",
          "value": 1
        }
      ]
    }
    ```

=== "Not valid order item requests"
``` json
// next example has total sum of selected items euqal to 10 which is not in the range [3..6]
{
  "additionalProperty": [
    {
      "propertyId": "additionalProperty.checksum_example",
      "value": "[\"italy\",\"ukraine\", \"switzerland\"]"
    },
    {
      "propertyId": "additionalProperty.checksum_item_ukraine",
      "value": 3
    },
    {
      "propertyId": "additionalProperty.checksum_item_switzerland",
      "value": 6
    },
    {
      "propertyId": "additionalProperty.checksum_item_italy",
      "value": 1
    }
  ]
}
// next example has total sum of selected items euqal to 1 which is not in the range [3..6]
{
  "additionalProperty": [
    {
      "propertyId": "additionalProperty.checksum_example",
      "value": "[\"italy\"]"
    },
    {
      "propertyId": "additionalProperty.checksum_item_italy",
      "value": 1
    },
  ]
}

Display conditions

A display condition answers "does this field apply right now?". Ask for a shipping method only when the delivery mode is shipping; ask for a guardian's name only for a minor. When the conditions are not met the field is skipped entirely — not validated, and by the same token not shown in the UI.

displayConditions is a list. All entries must be satisfied for the field to be validated; one unmet condition is enough to skip it.

{
  "type": "select",
  "propertyId": "orderItemDelivery.method",
  "name": "Order Delivery Method",
  "required": true,
  "displayConditions": [
    { "propertyId": "orderItemDelivery.mode", "value": "shipping" }
  ]
}

The delivery method is required only when the mode is shipping.

Condition properties

Property Meaning
propertyId The property to inspect. Same addressing rules as a field's own propertyId — dotted paths and the additionalProperty. prefix both work. Ignored by odataFilter and by function types.
operator How to compare. Defaults to equal.
value What to compare against.
type The type of the inspected property. Only present when the type cannot be inferred automatically (e.g. multiselect / checksum properties, properties on another model or dotted paths). A *Function value instead selects a function type, which computes the value itself.
model Which object to inspect. Defaults to the object being validated — see Reading a value from another object.

Operators

Operator Meaning Needs value
equal (default) String equality Yes
notequal String inequality Yes
notnullorempty Property has a value (non-empty string, non-empty collection, non-default primitive) No
nullorempty Property has no value No
lt, le, gt, ge Typed comparison — see Comparison operators Yes
odataFilter Evaluate an OData expression — see OData filter conditions Yes (the filter)

Operator names are matched case-sensitively: notEqual and ODataFilter are not recognised and will be rejected as unknown operators.

For a multiselect or checksum property, equal means "the selected array contains this value". Only a single value can be tested — putting an array in value does not work.

Comparison operators

lt (less than), le (less or equal), gt (greater than) and ge (greater or equal) require a condition type that defines an ordering:

type value format Comparison
integer, int Whole number Numeric
number Decimal number Numeric
date Date, e.g. 2025-06-15 Date part only; any time is ignored
datetime Date and time, e.g. 2025-06-15T14:30:00Z Full precision

Any other type — text included — is a configuration error. Numbers and dates are parsed with invariant culture, so use . as the decimal separator and ISO 8601 dates.

If either side is null the condition is simply not met — the dependent field is skipped rather than erroring.

Reading a value from another object

By default a condition inspects the object being validated. model redirects it:

model Inspects
(omitted) The object being validated
Customer The customer on the order
OrderItem The order item the object belongs to
Stay The stay
{
  "type": "text",
  "propertyId": "givenName",
  "name": "First Name",
  "required": true,
  "displayConditions": [
    {
      "propertyId": "email",
      "model": "Customer",
      "type": "text",
      "operator": "notnullorempty"
    }
  ]
}

The traveler's first name is required only when the customer has an email address.

Which models are available depends on the context:

In Order context:

Field definition list Available models
customerField -
customerField.billingAddress Customer
customerField.shippingAddress Customer
itemField Customer
travelerField OrderItem, Customer
vehicleField OrderItem, Customer

In Offer context:

Warning

In offer context parent models are not available. Conditions can only reference properties on the same object which is being validated.

In Stay context:

Field definition list Available models
stayField -
customerField Stay
travelerGroupField Stay
travelerField Stay

Information

A model that is not available in the current context falls back to the object being validated rather than failing. Check the tables above when a condition behaves unexpectedly.

Function types

Some conditions depend on a value that is not stored anywhere — a traveler's age at the travel date, for instance, which is derived from a birth date and a departure date. A function type computes such a value at validation time so a display condition can test it.

type Result type Returns
ageFunction integer The traveler's age in whole years at travel start

Set the condition's type to the function's name. A function computes its own value, so the condition needs no propertyId:

{
  "type": "text",
  "propertyId": "additionalProperty.guardianName",
  "name": "Guardian name",
  "required": true,
  "displayConditions": [
    {
      "value": "16",
      "operator": "lt",
      "type": "ageFunction"
    }
  ]
}

A guardian's name is required only for travelers under 16 at travel start.

The function type is matched case-insensitively, so ageFunction and AgeFunction are equivalent. All operators except odataFilter are supported; the comparison operators use the function's result type.

A function that cannot compute a value (e.g. ageFunction when no birth date is set, or when the validated object is not a traveler) returns nothing, which makes comparison conditions false — the dependent field is skipped rather than erroring.

ageFunction

Returns the traveler's age in whole years at travel start. The travel start date is taken from the order item's orderedItem.validFrom, falling back to the current UTC date when missing. The age is the year difference, decremented when the birthday (by date part) has not yet occurred on the travel start date.

Clients that pre-check the same condition need matching arithmetic:

/**
 * Age at travel start.
 * @param {string|Date|null} birthDate  traveler's birth date
 * @param {string|Date|null} validFrom  orderItem.orderedItem.validFrom; falls back to "now" (UTC)
 * @returns {number|null} whole years, or null when the age cannot be calculated
 */
function ageAtTravelStart(birthDate, validFrom) {
  if (!birthDate) {
    return null;
  }

  const birth = new Date(birthDate);
  const travel = validFrom ? new Date(validFrom) : new Date();

  let age = travel.getUTCFullYear() - birth.getUTCFullYear();

  // decrement when the birthday has not yet occurred in the travel year (handles leap years)
  const hadBirthday =
    travel.getUTCMonth() > birth.getUTCMonth() ||
    (travel.getUTCMonth() === birth.getUTCMonth() && travel.getUTCDate() >= birth.getUTCDate());
  if (!hadBirthday) {
    age--;
  }

  return age;
}

// example: field is active (required) when the condition "age lt 16" is met
const age = ageAtTravelStart(traveler.birthDate, orderItem?.orderedItem?.validFrom);
const guardianNameRequired = age !== null && age < 16;

OData filter conditions

The odataFilter operator evaluates an OData $filter expression against the model. It exists for conditions the simple operators cannot express: several properties combined, or a test over the items of a collection.

Set operator to odataFilter and put the filter expression in value:

{
  "type": "text",
  "propertyId": "additionalProperty.groupLeaderName",
  "name": "Group leader",
  "required": true,
  "displayConditions": [
    {
      "operator": "odataFilter",
      "model": "OrderItem",
      "value": "orderQuantity gt 5"
    }
  ]
}

The field applies only when more than five items were ordered.

Three rules govern how filters are written:

  • propertyId is ignored. The filter names its own properties, so propertyId serves no purpose here.
  • Paths are lowerCamelCase, separated by / rather than .orderedItem/validFrom, not OrderedItem.ValidFrom.
  • value is required. An empty or whitespace filter is a configuration error.

The filter is evaluated against the object chosen by model, or against the object being validated when model is omitted.

Examples:

Filter Meaning
orderQuantity gt 5 Simple property comparison
orderedItem/validFrom ge 2025-01-01T00:00:00Z Nested path
familyName eq 'Doe' String equality — single quotes
orderedItem/traveler/any(t: t/category eq 'adult') At least one traveler is an adult
orderedItem/traveler/all(t: t/category eq 'adult') Every traveler is an adult

any() and all() make this the only way to express a condition over the contents of a collection.

Migrating from parentField properties

The five parentField* properties are obsolete but still honoured. When displayConditions is absent or empty and parentFieldPropertyId is set, a single condition is derived from them:

Obsolete property Replacement
parentFieldPropertyId displayConditions[].propertyId
parentFieldValue displayConditions[].value
parentFieldOperator displayConditions[].operator
parentFieldType displayConditions[].type
parentFieldModel displayConditions[].model

displayConditions wins outright: when it is present the parentField* properties are ignored, not merged. The legacy form supports only a single condition, which is the main reason to migrate.

The one place where parentFieldPropertyId / parentFieldValue are still required is linking checksum items to their checksum — see Checksum item.

Custom error messages

Every validation failure produces a validation message with a localized message, a numeric code and level = Error. Messages are localized in German (fallback), English, French and Italian, based on the request language.

A field definition can carry a customErrorNames dictionary. It is used by the backend only: when the corresponding rule fails (Required, RangeMin or RangeMax), the validation message returned in the response contains a custom, product-specific text instead of the generic one — e.g. "A gift ticket can be issued for at most 2 travelers." instead of "The field Travelers must contain less than 2 items in array."

{
  "propertyId": "traveler",
  "type": "array",
  "name": "Travelers",
  "rangeMax": "2",
  "customErrorNames": {
    "RangeMax": "GiftTicket_MaxTravlers_Error"
  }
}

For the client this property is informational only — there is nothing to render or validate; it just explains why the error text for such a field differs from the default messages. The legacy requiredErrorName property is equivalent to a Required entry in customErrorNames.

Examples

Zürich Card

For Zürich Card 72 hours, the ItemField and TravelerField are defined as the following:

{
  "itemField": [
    {
      "propertyId": "orderedItem.validFrom",
      "type": "dateTime",
      "name": "Valid from",
      "required": true,
      "requiredForOffers": true,
      "possibleValue": {},
      "rangeMin": "PT1S",
      // one second in advance
      "rangeMax": "P2M"
      // less than 2 months
    }
  ],
  "travelerField": [
    {
      "propertyId": "givenName",
      "type": "text",
      "name": "First name",
      "required": true,
      "requiredForOffers": false,
      "possibleValue": {}
    },
    {
      "propertyId": "familyName",
      "type": "text",
      "name": "Last name",
      "required": true,
      "requiredForOffers": false,
      "possibleValue": {}
    },
    {
      "propertyId": "birthDate",
      "type": "date",
      "name": "Date of birth",
      "required": true,
      "requiredForOffers": true,
      "possibleValue": {},
      "rangeMax": "-P6Y",
      "rangeBasePropertyId": "orderedItem.validFrom"
    },
    {
      "propertyId": "gender",
      "type": "radio",
      "name": "Gender",
      "required": false,
      "requiredForOffers": false,
      "possibleValue": {
        "female": "Female",
        "male": "Male",
        "diverse": "Diverse"
      }
    }
  ]
}

Note

There are conditions to be fulfilled. For Gender and Salutation there are predefined values for the possibleValue property. For BirthDate the value should be within the age range.

Aleno multiselect/checksum with numeric fields

{
  "propertyId": "additionalProperty.question1",
  "type": "checksum",
  "name": "Question with sum check",
  "required": true,
  "possibleValue": {
      "answer0": "Salads",
      "answer1": "Soups"
  },
  "rangeMin": "3",
  "rangeMax": "3"
},
{
  "propertyId": "additionalProperty.question1_answer0_numeric",
  "type": "checksumItem",
  "name": "Salads",
  "required": true,
  "possibleValue": {},
  "rangeMin": "0",
  "rangeMax": "3",
  "parentFieldPropertyId": "additionalProperty.question1",
  "parentFieldValue": "answer0"
},
{
  "propertyId": "additionalProperty.question1_answer1_numeric",
  "type": "checksumItem",
  "name": "Soups",
  "required": true,
  "possibleValue": {},
  "rangeMin": "0",
  "rangeMax": "3",
  "parentFieldPropertyId": "additionalProperty.question1",
  "parentFieldValue": "answer1"
}
{
  "propertyId": "additionalProperty.question0",
  "type": "multiselect",
  "name": "Select Question",
  "required": true,
  "possibleValue": {
      "answer0": "How many Apéros you want? (Numeric)",
      "answer1": "Option en 1",
      "answer2": "Option en 2"
  }
},
{
  "propertyId": "additionalProperty.question0_answer0_numeric",
  "type": "integer",
  "name": "How many Apéros you want? (Numeric)",
  "required": true,
  "possibleValue": {},
  "rangeMin": "0",
  "rangeMax": "2147483647",
  "parentFieldPropertyId": "additionalProperty.question0",
  "parentFieldValue": "answer0"
}

As an example of representation of checksum with checksumitems and multiselect below are screenshots from Aleno widget which covers same logic:

Checksum with checksumitems(numeric fields)

Aleno - Checksum with checksumItems Aleno - Checksum with checksumItems

Multiselect with additional numeric fields attached to it by values

Aleno - Multiselect with numeric

Warning

Be aware that discover.swiss currently doesn't support multiselect fields with posiibility to add custom answer unlike Aleno widget.

Example Dynamic validation

This example shows how the automatic validation works on ItemField. In the case of Zürich Card, a customer can make a ticket as it is less than 2 months in the future. If it is more than the defined rangeMax or less than rangeMin, it will result in 400 Bad Request and gives a validation error

Info

Assuming today is February the first.

{
    "orderStatus": "Placed",
    "priceCurrency": "CHF",
    "orderedItem": [
    {
        "orderQuantity": 1,
        "orderedItem": {
            "product": {
                "identifier": "nova_zurichcard24"
            },
            "validFrom": "2022-05-03T00:00:00"
        }
    }
    ],
}
POST {marketUrl}/orders
{
  //not all response is shown
  "validationMessages": [
    {
        "level": "Error",
        "message": "The field Valid from must be less than 01/05/2022 15:17:03 +00:00.",
        "orderItemNumber": "22-103915-1",
        "source": "OrderedItem"
    }
  ]
}

Example of display conditions

This example shows how a field can be gated by the value of another field. The shipping method is validated (and shown) only when the delivery mode is shipping; the pickup method only when it is pickUp.

{
  "propertyId": "deliveryMode",
  "type": "select",
  "name": "delivery Mode",
  "required": true,
  "requiredForOffers": false,
  "possibleValue": {
      "shipping": "Shipping",
      "pickUp": "Pick-up"
      }
  },
{
  "propertyId": "shippingMethod",
  "type": "select",
  "name": "shipping Method",
  "required": true,
  "requiredForOffers": false,
  "possibleValue": {
      "train": "Train",
      "plane": "Plane"
      },
  "displayConditions": [
    { "propertyId": "deliveryMode", "value": "shipping" }
  ]
},
{
  "propertyId": "pickupMethod",
  "type": "select",
  "name": "pickup Method",
  "required": true,
  "requiredForOffers": false,
  "possibleValue": {
      "station1": "Station 1",
      "station2": "Station 2"
      },
  "displayConditions": [
    { "propertyId": "deliveryMode", "value": "pickUp" }
  ]
}

Example of display condition with model

In this example the traveler field specifies that telephone is required only when the customer field telephone is not provided.

{
  "propertyId": "telephone",
  "type": "text",
  "name": "Telephone",
  "required": true,
  "requiredForOffers": false,
  "displayConditions": [
    {
      "propertyId": "telephone",
      "operator": "nullorempty",
      "model": "Customer"
    }
  ]
}

Warning

In some cases itemFields can change after the first step in the checkout page because they can be dependent on the users choices.