JSON Forms Data Validation
Specific fields of a JSON Forms config variable can have validation rules applied. For example, you can check that an input matches a regular expression, ensuring that it is an email address or phone number, or you can ensure a date is within a certain range.
But, not all data validation can be done with regex and min/max rules. Sometimes it's handy to examine the form in its entirety for correctness. For example, you may want to ensure that a Salesforce field mapper contains a one-to-one field mapping (so a customer can't map "Phone" to both "Cell Phone" and "Work Phone" fields, etc).
One easy way to tackle JSON Forms form validation is to feed the results of a JSON Form config variable into a subsequent JSON Form data source which validates that the data with JavaScript rules you write. This "validator" form can show helpful, human-readable error messages and prevent a user from completing a config wizard until they've corrected their mistakes.
In this example, we'll validate a simple JSON Form.
Our example JSON Form
For this example, we have a simple form that contains:
- A field mapper mapping "source" fields to "destination" fields.
For example, you can map
Source Option 2
toDestination Option 5
, etc. It's important that these mappings are unique. So, you can't map bothSource Option 2
andSource Option 3
toDestination Option 5
. - A number input representing a person's age.
While you could validate a number like this with
minimum
andmaximum
input validators, we'll show how to validate it with JavaScript here.
const myJsonForm = dataSource({
dataSourceType: "jsonForm",
display: {
label: "My JSON Form",
description: "A JSON form for testing",
},
inputs: {},
perform: async () => {
const schema = {
type: "object",
properties: {
mappings: {
type: "array",
items: {
type: "object",
properties: {
source: {
type: "string",
enum: [
"Source Option 1",
"Source Option 2",
"Source Option 3",
"Source Option 4",
"Source Option 5",
],
},
destination: {
type: "string",
enum: [
"Destination Option 1",
"Destination Option 2",
"Destination Option 3",
"Destination Option 4",
"Destination Option 5",
],
},
},
},
required: ["source", "destination"],
},
age: {
type: "integer",
},
},
};
const uiSchema = {
type: "VerticalLayout",
elements: [
{
type: "Control",
scope: "#/properties/mappings",
},
{
type: "Control",
scope: "#/properties/age",
},
],
};
return Promise.resolve({ result: { schema, uiSchema } });
},
});

Our JSON Forms config variable will yield an object that looks like this:
{
"mappings": [
{
"source": "Source Option 1",
"destination": "Destination Option 1"
},
{
"source": "Source Option 2",
"destination": "Destination Option 5"
},
{
"source": "Source Option 2",
"destination": "Destination Option 3"
},
{
"source": "Source Option 4",
"destination": "Destination Option 3"
}
],
"age": -5
}
Our example validator form
Within our form validator we want to:
- Verify that each
source
field was selected at most once. - Verify that each
destination
field was select at most once. - Verify that
age
was a positive number no greater than 130.
If any of these checks fail, we want to display an error and disallow a user from continuing (note the disabled "Finish" button).

If all checks pass, we want to display confirmation that their data looks correct, and allow the user to continue.

In our validator code below, we pass our previous JSON Form's results to our "validator" data source.
We initialize an array, errors
to []
.
Then, if we detect bad data in the form that is being processed, we push error messages onto our errors
array.
If errors
is empty at the end of the function, we display a JSON Form with a label that says ✅ No errors found
, and the user is able to click the "finish" button in the config wizard.
If errors
contains error messages, those messages like ❌ Age must be a positive number
are displayed in the config wizard.
The form then requires an invisible field, isInvalid
, which cannot be set because it is invisible.
This prevents a user from clicking "Finish" when errors are present.
interface JsonFormData {
mappings: { source: string; destination: string }[];
age: number;
}
const myValidator = dataSource({
dataSourceType: "jsonForm",
display: {
label: "Validator",
description: "Validates previous JSON form",
},
inputs: {
data: input({
label: "Data",
type: "data",
required: true,
clean: (value) => value as JsonFormData,
}),
},
perform: async (context, { data }) => {
const { mappings, age } = data;
// Initialize with an empty set of errors
const errors: string[] = [];
if ((mappings || []).length === 0) {
// If the user submitted no mappings, add an error
errors.push("❌ At least one mapping is required");
} else {
mappings.forEach((mapping, index) => {
// If multiple source fields are mapped to a single destination, add an error
if (mappings.findIndex((m) => m.source === mapping.source) !== index) {
errors.push(
`❌ Duplicate source of "${mapping.source}" selected. Only use each source once.`,
);
}
// If multiple destination fields were mapped to a single source, add an error
if (
mappings.findIndex((m) => m.destination === mapping.destination) !==
index
) {
errors.push(
`❌ Duplicate destination of "${mapping.destination}" selected. Only use each destination once.`,
);
}
});
}
// Add an error if there is no age, or the age is too high or low
if (age === undefined) {
errors.push("❌ You must specify an age");
} else {
if (age < 0) {
errors.push("❌ Age must be a positive number");
}
if (age > 130) {
errors.push("❌ Nobody is that old.");
}
}
// If any errors were added to the errors array, return a series of labels displaying the errors
if (errors.length) {
return Promise.resolve({
result: {
schema: {
type: "object",
properties: {
isInvalid: {
type: "string",
},
},
// Add an invisible, but required, input to prevent the "Finish" button from being clickable
required: ["isInvalid"],
},
uiSchema: {
type: "VerticalLayout",
elements: errors.map((error) => ({
type: "Label",
text: `Error: ${error}`,
})),
},
},
});
} else {
// If no errors were present, display a single affirmative label and allow a user to continue
return Promise.resolve({
result: {
schema: {
type: "object",
properties: {},
},
uiSchema: {
type: "VerticalLayout",
elements: [{ type: "Label", text: "✅ No errors found" }],
},
},
});
}
},
});