Skip to content

Added support for patternProperties #4582

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG_v6.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ should change the heading of the (upcoming) version to include a major version b
- BREAKING CHANGE: Moved the addition of `Bootstrap 3` classes from the `SchemaField` to the `WrapIfAdditionalTemplate`, thereby affecting all the other themes, fixing [#2280](https://github.com/rjsf-team/react-jsonschema-form/issues/2280)
- BREAKING CHANGE: Added `rjsf-` prefix onto the following marker classes used in the fields and templates:
- `field`, `field-<schema.type>`, `field-error`, `field-hidden`, `field-array`, `field-array-of-<schema.type>`, `field-array-fixed-items`, `array-item`, `config-error`, `array-item-add`, `array-item-copy`, `array-item-move-down`, `array-item-move-up`, `array-item-remove`, `object-property-expand`
- Added support for `patternProperties` [#1944](https://github.com/rjsf-team/react-jsonschema-form/issues/1944)

## @rjsf/daisyui

Expand Down Expand Up @@ -111,6 +112,7 @@ should change the heading of the (upcoming) version to include a major version b
- BREAKING CHANGE: Removed the deprecated `toErrorList()` function from the `ValidatorType` interface
- BREAKING CHANGE: Removed the deprecated `RJSF_ADDITONAL_PROPERTIES_FLAG` constant
- Updated the `WrapIfAdditionalTemplateProps` to include `hideError` and `rawErrors` in support of moving `Bootstrap 3` marker classes out of `SchemaField`
- Added support for `patternProperties` [#1944](https://github.com/rjsf-team/react-jsonschema-form/issues/1944)

## @rjsf/validator-ajv6

Expand All @@ -130,6 +132,7 @@ should change the heading of the (upcoming) version to include a major version b
- Updated the `playground` to add a `Layout Grid` example and made the selected example now be part of the shared export
- Replaced Lerna with Nx, updated all lerna commands to use the Nx CLI
- BREAKING CHANGE: Updated all `peerDependencies` to change minimal `React` support to `>=18`
- Added documentation and playground example for `patternProperties`

# 6.0.0-alpha.0

Expand Down
52 changes: 28 additions & 24 deletions packages/core/src/components/fields/ObjectField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -195,36 +195,40 @@ class ObjectField<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends Fo
* @param schema - The schema element to which the new property is being added
*/
handleAddClick = (schema: S) => () => {
if (!schema.additionalProperties) {
if (!(schema.additionalProperties || schema.patternProperties)) {
return;
}
const { formData, onChange, registry } = this.props;
const newFormData = { ...formData } as T;

let type: RJSFSchema['type'] = undefined;
let constValue: RJSFSchema['const'] = undefined;
let defaultValue: RJSFSchema['default'] = undefined;
if (isObject(schema.additionalProperties)) {
type = schema.additionalProperties.type;
constValue = schema.additionalProperties.const;
defaultValue = schema.additionalProperties.default;
let apSchema = schema.additionalProperties;
if (REF_KEY in apSchema) {
const { schemaUtils } = registry;
apSchema = schemaUtils.retrieveSchema({ $ref: apSchema[REF_KEY] } as S, formData);
type = apSchema.type;
constValue = apSchema.const;
defaultValue = apSchema.default;
}
if (!type && (ANY_OF_KEY in apSchema || ONE_OF_KEY in apSchema)) {
type = 'object';
const newKey = this.getAvailableKey('newKey', newFormData);
if (schema.patternProperties) {
// Cast this to make the `set` work properly
set(newFormData as GenericObjectType, newKey, null);
} else {
let type: RJSFSchema['type'] = undefined;
let constValue: RJSFSchema['const'] = undefined;
let defaultValue: RJSFSchema['default'] = undefined;
if (isObject(schema.additionalProperties)) {
type = schema.additionalProperties.type;
constValue = schema.additionalProperties.const;
defaultValue = schema.additionalProperties.default;
let apSchema = schema.additionalProperties;
if (REF_KEY in apSchema) {
const { schemaUtils } = registry;
apSchema = schemaUtils.retrieveSchema({ $ref: apSchema[REF_KEY] } as S, formData);
type = apSchema.type;
constValue = apSchema.const;
defaultValue = apSchema.default;
}
if (!type && (ANY_OF_KEY in apSchema || ONE_OF_KEY in apSchema)) {
type = 'object';
}
}
}

const newKey = this.getAvailableKey('newKey', newFormData);
const newValue = constValue ?? defaultValue ?? this.getDefaultValue(type);
// Cast this to make the `set` work properly
set(newFormData as GenericObjectType, newKey, newValue);
const newValue = constValue ?? defaultValue ?? this.getDefaultValue(type);
// Cast this to make the `set` work properly
set(newFormData as GenericObjectType, newKey, newValue);
}

onChange(newFormData);
};
Expand Down
14 changes: 7 additions & 7 deletions packages/docs/docs/advanced-customization/custom-templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ In version 5, all existing `templates` were consolidated into a new `TemplatesTy
They can also be overloaded globally on the `Form` via the `templates` prop as well as globally or per-field through the `uiSchema`.
Further, many new templates were added or repurposed from existing `widgets` and `fields` in an effort to simplify the effort needed by theme authors to build new and/or maintain current themes.
These new templates can also be overridden by individual users to customize the specific needs of their application.
A special category of templates, `ButtonTemplates`, were also added to support the easy replacement of the `Submit` button on the form, the `Add` and `Remove` buttons associated with `additionalProperties` on objects and elements of arrays, as well as the `Move up` and `Move down` buttons used for reordering arrays.
A special category of templates, `ButtonTemplates`, were also added to support the easy replacement of the `Submit` button on the form, the `Add` and `Remove` buttons associated with `additionalProperties` and `patternProperties` on objects and elements of arrays, as well as the `Move up` and `Move down` buttons used for reordering arrays.
This category, unlike the others, can only be overridden globally via the `templates` prop on `Form`.

Below is the table that lists all the `templates`, their props interface, their `uiSchema` name and from where they originated in the previous version of RJSF:
Expand Down Expand Up @@ -461,7 +461,7 @@ The following props are passed to the `BaseInputTemplate`:
- `multiple`: A boolean value stating if the widget can accept multiple values;
- `onChange`: The value change event handler; call it with the new value every time it changes;
- `onChangeOverride`: A `BaseInputTemplate` implements a default `onChange` handler that it passes to the HTML input component to handle the `ChangeEvent`. Sometimes a widget may need to handle the `ChangeEvent` using custom logic. If that is the case, that widget should provide its own handler via this prop;
- `onKeyChange`: The key change event handler (only called for fields with `additionalProperties`); pass the new value every time it changes;
- `onKeyChange`: The key change event handler (only called for fields with `additionalProperties` and `patternProperties`); pass the new value every time it changes;
- `onBlur`: The input blur event handler; call it with the widget id and value;
- `onFocus`: The input focus event handler; call it with the widget id and value;
- `options`: A map of options passed as a prop to the component (see [Custom widget options](./custom-widgets-fields.md#custom-widget-options)).
Expand Down Expand Up @@ -807,7 +807,7 @@ The following props are passed to each `ObjectFieldTemplate` as defined by the `
- `description`: A string value containing the description for the object.
- `disabled`: A boolean value stating if the object is disabled.
- `properties`: An array of object representing the properties in the object. Each of the properties represent a child with properties described below.
- `onAddClick: (schema: RJSFSchema) => () => void`: Returns a function that adds a new property to the object (to be used with additionalProperties)
- `onAddClick: (schema: RJSFSchema) => () => void`: Returns a function that adds a new property to the object (to be used with additionalProperties and patternProperties)
- `readonly`: A boolean value stating if the object is read-only.
- `required`: A boolean value stating if the object is required.
- `hideError`: A boolean value stating if the field is hiding its errors.
Expand Down Expand Up @@ -908,8 +908,8 @@ The following props are passed to each `UnsupportedFieldTemplate`:

## WrapIfAdditionalTemplate

The `WrapIfAdditionalTemplate` is used by the `FieldTemplate` to conditionally render additional controls if `additionalProperties` is present in the schema.
You may customize `WrapIfAdditionalTemplate` if you wish to change the layout or behavior of user-controlled `additionalProperties`.
The `WrapIfAdditionalTemplate` is used by the `FieldTemplate` to conditionally render additional controls if `additionalProperties` or `patternProperties` are present in the schema.
You may customize `WrapIfAdditionalTemplate` if you wish to change the layout or behavior of user-controlled `additionalProperties` and `patternProperties`.

```tsx
import { RJSFSchema, WrapIfAdditionalTemplateProps } from '@rjsf/utils';
Expand Down Expand Up @@ -987,7 +987,7 @@ Each button template (except for the `SubmitButton`) accepts, as props, the stan

### AddButton

The `AddButton` is used to render an add action on a `Form` for both a new `additionalProperties` element for an object or a new element in an array.
The `AddButton` is used to render an add action on a `Form` for both a new `additionalProperties` or `patternProperties` element for an object or a new element in an array.
You can customize the `AddButton` to render something other than the icon button that is provided by a theme as follows:

```tsx
Expand Down Expand Up @@ -1077,7 +1077,7 @@ render(

### RemoveButton

The `RemoveButton` is used to render a remove action on a `Form` for both a existing `additionalProperties` element for an object or an existing element in an array.
The `RemoveButton` is used to render a remove action on a `Form` for both a existing `additionalProperties` or `patternProperties` element for an object or an existing element in an array.
You can customize the `RemoveButton` to render something other than the icon button that is provided by a theme as follows:

```tsx
Expand Down
2 changes: 1 addition & 1 deletion packages/docs/docs/api-reference/form-props.md
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ Sometimes you may want to trigger events or modify external state when a field h
If you plan on being notified every time the form data are updated, you can pass an `onChange` handler, which will receive the same first argument as `onSubmit` any time a value is updated in the form.
It will also receive, as the second argument, the `id` of the field which experienced the change.
Generally, this will be the `id` of the field for which input data is modified.
In the case of adding/removing of new fields in arrays or objects with `additionalProperties` and the rearranging of items in arrays, the `id` will be that of the array or object itself, rather than the item/field being added, removed or moved.
In the case of adding/removing of new fields in arrays or objects with `additionalProperties` or `patternProperties` and the rearranging of items in arrays, the `id` will be that of the array or object itself, rather than the item/field being added, removed or moved.

## onError

Expand Down
3 changes: 2 additions & 1 deletion packages/docs/docs/api-reference/utility-functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ The UI for the field can expand if it has additional properties, is not forced a

#### Returns

- boolean: True if the schema element has additionalProperties, is expandable, and not at the maxProperties limit
- boolean: True if the schema element has additionalProperties or patternProperties keywords, is expandable, and not at the maxProperties limit

### createErrorHandler<T = any>()

Expand Down Expand Up @@ -392,6 +392,7 @@ If the type is not explicitly defined, then an attempt is made to infer it from
- schema.enum: Returns `string`
- schema.properties: Returns `object`
- schema.additionalProperties: Returns `object`
- schema.patternProperties: Returns `object`
- type is an array with a length of 2 and one type is 'null': Returns the other type

#### Parameters
Expand Down
31 changes: 29 additions & 2 deletions packages/docs/docs/json-schema/objects.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ const uiSchema: UiSchema = {
};
```

## Additional properties
## Additional and pattern properties

The `additionalProperties` keyword allows the user to add properties with arbitrary key names. Set this keyword equal to a schema object:

Expand Down Expand Up @@ -116,9 +116,36 @@ In this way, an add button for new properties is shown by default.

You can also define `uiSchema` options for `additionalProperties` by setting the `additionalProperties` attribute in the `uiSchema`.

The `patternProperties` keyword allows the user to add properties with names that match one or more of the specified regular expressions

```tsx
import { Form } from '@rjsf/core';
import { RJSFSchema } from '@rjsf/utils';
import validator from '@rjsf/validator-ajv8';

const schema: RJSFSchema = {
type: 'object',
properties: {
name: {
type: 'string',
},
},
patternProperties: {
'^foo+$': {
type: 'number',
enum: [1, 2, 3],
},
},
};

render(<Form schema={schema} validator={validator} />, document.getElementById('app'));
```

Also in this case, an add button for new properties is shown by default.

### `expandable` option

You can turn support for `additionalProperties` off with the `expandable` option in `uiSchema`:
You can turn support for `additionalProperties` and `patternProperties` off with the `expandable` option in `uiSchema`:

```ts
import { UiSchema } from '@rjsf/utils';
Expand Down
2 changes: 2 additions & 0 deletions packages/playground/src/samples/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import customField from './customField';
import layoutGrid from './layoutGrid';
import { Sample } from './Sample';
import deepFreeze from 'deep-freeze-es6';
import patternProperties from './patternProperties';

export type { Sample };

Expand Down Expand Up @@ -61,6 +62,7 @@ const _samples: Record<string, Sample> = {
'Property dependencies': propertyDependencies,
'Schema dependencies': schemaDependencies,
'Additional Properties': additionalProperties,
'Pattern Properties': patternProperties,
'Any Of': anyOf,
'Any Of with Custom Field': customFieldAnyOf,
'One Of': oneOf,
Expand Down
38 changes: 38 additions & 0 deletions packages/playground/src/samples/patternProperties.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Sample } from './Sample';

const patternProperties: Sample = {
schema: {
title: 'A customizable registration form',
description: 'A simple form with pattern properties example.',
type: 'object',
required: ['firstName', 'lastName'],
properties: {
firstName: {
type: 'string',
title: 'First name',
},
lastName: {
type: 'string',
title: 'Last name',
},
},
patternProperties: {
'^[a-z][a-zA-Z]+$': {
type: 'string',
},
},
},
uiSchema: {
firstName: {
'ui:autofocus': true,
'ui:emptyValue': '',
},
},
formData: {
firstName: 'Chuck',
lastName: 'Norris',
assKickCount: 'infinity',
},
};

export default patternProperties;
2 changes: 1 addition & 1 deletion packages/utils/src/canExpand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default function canExpand<T = any, S extends StrictRJSFSchema = RJSFSche
uiSchema: UiSchema<T, S, F> = {},
formData?: T,
) {
if (!schema.additionalProperties) {
if (!(schema.additionalProperties || schema.patternProperties)) {
return false;
}
const { expandable = true } = getUiOptions<T, S, F>(uiSchema);
Expand Down
1 change: 1 addition & 0 deletions packages/utils/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const ITEMS_KEY = 'items';
export const JUNK_OPTION_ID = '_$junk_option_schema_id$_';
export const NAME_KEY = '$name';
export const ONE_OF_KEY = 'oneOf';
export const PATTERN_PROPERTIES_KEY = 'patternProperties';
export const PROPERTIES_KEY = 'properties';
export const READONLY_KEY = 'readonly';
export const REQUIRED_KEY = 'required';
Expand Down
3 changes: 2 additions & 1 deletion packages/utils/src/getSchemaType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { RJSFSchema, StrictRJSFSchema } from './types';
* - schema.enum: Returns `string`
* - schema.properties: Returns `object`
* - schema.additionalProperties: Returns `object`
* - schema.patternProperties: Returns `object`
* - type is an array with a length of 2 and one type is 'null': Returns the other type
*
* @param schema - The schema for which to get the type
Expand All @@ -25,7 +26,7 @@ export default function getSchemaType<S extends StrictRJSFSchema = RJSFSchema>(
return 'string';
}

if (!type && (schema.properties || schema.additionalProperties)) {
if (!type && (schema.properties || schema.additionalProperties || schema.patternProperties)) {
return 'object';
}

Expand Down
Loading
Loading