Building Dynamic Front-ends
Guide to using Swift's JSON Schemas to render robust and responsive UI forms.
Because Swift payment requirements might change per region or corridor, building a dynamic frontend that parses the JSON schema will keep you future-proof.
Here, we will demonstrate the design patterns for building a dynamic frontend reading off the schemas, with examples for both React and Angular.
Parsing Standard Properties
You can iterate through the properties object of the downloaded schema to construct your user interface. Check for strings, adjust your max character limits, and map title strings to your labels.
export function BasicForm({ schema }) {
const properties = schema.properties;
return (
<form>
{Object.keys(properties).map((fieldKey) => {
const field = properties[fieldKey];
const isRequired = schema.required?.includes(fieldKey);
return (
<div key={fieldKey} className="input-group">
<label>
{field.title} {isRequired && <span className="text-red-500">*</span>}
</label>
<input
type="text"
minLength={field.minLength}
maxLength={field.maxLength}
placeholder={field.description}
/>
</div>
)
})}
</form>
)
}@Component({
selector: 'app-basic-form',
template: `
<form [formGroup]="paymentForm">
<div *ngFor="let field of schema.properties | keyvalue" class="input-group">
<label>
{{ field.value.title }}
<span *ngIf="schema.required?.includes(field.key)" class="text-red-500">*</span>
</label>
<input
type="text"
[formControlName]="field.key"
[placeholder]="field.value.description || ''"
class="form-control"
/>
</div>
</form>
`
})
export class BasicFormComponent {
@Input() schema: any;
paymentForm: FormGroup;
constructor(private fb: FormBuilder) {
// Dynamically initialize form based on schema properties
this.paymentForm = this.fb.group({});
}
}Using x-UIErrorMessage
When your frontend validation fails (e.g., character length is exceeded), you want to guide the user to fix the data properly. The Swift schemas come packaged with x-UIErrorMessage.
function getErrorMessage(field, errorType) {
if (field['x-UIErrorMessage']) {
return field['x-UIErrorMessage'][errorType];
}
return "Invalid input"; // fallback
}
// Example usage when checking length validity
if (inputValue.length < field.minLength) {
showError(getErrorMessage(field, 'minLength'));
}Handling oneOf Conditionals
When you encounter a oneOf attribute, your UI should intuitively let the user choose which subset of data they wish to provide. A common UI component for this is a "Toggle", "Radio Button list", or "Dropdown" dictating which block of inputs to render.
For creditor_account_and_agent, you might see a single oneOf (as in Australia, requiring just Account Number + BSB) or multiple (as in the US, requiring either Account Number + ABA, OR Account + BIC).
import { useState } from 'react';
export function ConditionalFormGroup({ fieldDefinition }) {
if (fieldDefinition.oneOf) {
const options = fieldDefinition.oneOf;
const [selectedOption, setSelectedOption] = useState(0);
const currentProperties = options[selectedOption].properties;
return (
<div className="border p-4">
{options.length > 1 && (
<select
onChange={(e) => setSelectedOption(Number(e.target.value))}
value={selectedOption}
>
{options.map((opt, i) => (
<option key={i} value={i}>
Option {i + 1}: {Object.keys(opt.properties).join(' & ')}
</option>
))}
</select>
)}
{Object.keys(currentProperties).map(targetKey => (
<input key={targetKey} placeholder={currentProperties[targetKey].title} />
))}
</div>
);
}
}@Component({
selector: 'app-conditional-field',
template: `
<div *ngIf="fieldDefinition.oneOf" class="border p-4">
<select *ngIf="fieldDefinition.oneOf.length > 1" (change)="onOptionChange($event)">
<option *ngFor="let opt of fieldDefinition.oneOf; let i = index" [value]="i">
Option {{ i + 1 }}: {{ getPropertyKeys(opt) }}
</option>
</select>
<div class="nested-inputs">
<input *ngFor="let p of currentProperties | keyvalue"
[placeholder]="p.value.title" />
</div>
</div>
`
})
export class ConditionalFieldComponent {
@Input() fieldDefinition: any;
selectedOption = 0;
get currentProperties() {
return this.fieldDefinition.oneOf[this.selectedOption].properties;
}
getPropertyKeys(opt: any): string {
return Object.keys(opt.properties).join(' & ');
}
onOptionChange(event: any): void {
this.selectedOption = Number(event.target.value);
}
}