Skip to content

Commit 659c47e

Browse files
committed
review: Rely on schema instead of data, remove string/number prop usage
- remove usage of any number or string property as fallback - choose type, kind default prop based on combinator schema with const entry instead of presence in the data object - Adapt tests - remove usage of id as a default prop
1 parent f83c4a6 commit 659c47e

File tree

4 files changed

+119
-150
lines changed

4 files changed

+119
-150
lines changed

MIGRATION.md

+5-5
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,12 @@ Thus, custom renderers using either method might have behavior changes.
1010
This rework is part of an ongoing effort to remove mandatory usage of AJV from JSON Forms.
1111

1212
Before this change, AJV was used to validate the current data against all schemas of the combinator.
13-
This was replaced by using a heuristic which tries to match the schema via an identification property
14-
against a `const` entry in the schema.
13+
This was replaced by a heuristic which tries to match the schema via an identification property against a `const` entry in the schema.
1514

1615
The identification property is determined as follows in descending order of priority:
1716

1817
1. The schema contains a new custom property `x-jsf-type-property` next to the combinator to define the identification property.
19-
2. The data has any of these properties: `type`, `kind`, `id`. They are considered in the listed order.
20-
3. The data has any string or number property. The first encountered one is used.
18+
2. At least one of the combinator schemas has this property with a const declaration: `type`, `kind`. They are considered in the listed order.
2119

2220
If no combinator schema can be matched, fallback to the first one as before this update.
2321

@@ -72,7 +70,9 @@ const dataWithUser = {
7270

7371
#### Example 2: Use a default identification property
7472

75-
In this example we use the `kind` property as the identification property. Like in the custom property case, subschemas are matched via a `const` definition in the identification property's schema. However, we do not need to explicitly specify `kind` being used.
73+
In this example we use the `kind` property as the identification property.
74+
Like in the custom property case, subschemas are matched via a `const` definition in the identification property's schema.
75+
However, we do not need to explicitly specify `kind` being used.
7676
The `default` keyword can be used to tell JSON Forms to automatically initialize the property.
7777

7878
```ts

packages/core/src/mappers/combinators.ts

+38-55
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export type CombinatorKeyword = 'anyOf' | 'oneOf' | 'allOf';
4040
export const COMBINATOR_TYPE_PROPERTY = 'x-jsf-type-property';
4141

4242
/** Default properties that are used to identify combinator schemas. */
43-
export const COMBINATOR_IDENTIFICATION_PROPERTIES = ['type', 'kind', 'id'];
43+
export const COMBINATOR_IDENTIFICATION_PROPERTIES = ['type', 'kind'];
4444

4545
export const createCombinatorRenderInfos = (
4646
combinatorSubSchemas: JsonSchema[],
@@ -75,22 +75,41 @@ export const createCombinatorRenderInfos = (
7575
});
7676

7777
/**
78-
* Returns the identification property of the given data object.
78+
* Returns the index of the schema in the given combinator keyword that matches the identification property of the given data object.
79+
* The heuristic only works for data objects with a corresponding schema. If the data is a primitive value or an array, the heuristic does not work.
80+
*
7981
* The following heuristics are applied:
8082
* If the schema defines a `x-jsf-type-property`, it is used as the identification property.
81-
* Otherwise, the first of the following data properties is used:
83+
* Otherwise, the first of the following properties is used if it exists in at least one combinator schema and has a `const` entry:
8284
* - `type`
8385
* - `kind`
84-
* - `id`
8586
*
86-
* If none of the above properties are present, the first string or number property of the data object is used.
87+
* If the index cannot be determined, `-1` is returned.
88+
*
89+
* @returns the index of the fitting schema or `-1` if no fitting schema was found
8790
*/
88-
export const getCombinatorIdentificationProp = (
91+
export const getCombinatorIndexOfFittingSchema = (
8992
data: any,
90-
schema: JsonSchema
91-
): string | undefined => {
93+
keyword: CombinatorKeyword,
94+
schema: JsonSchema,
95+
rootSchema: JsonSchema
96+
): number => {
9297
if (typeof data !== 'object' || data === null) {
93-
return undefined;
98+
return -1;
99+
}
100+
101+
// Resolve all schemas in the combinator.
102+
const resolvedCombinatorSchemas = [];
103+
for (let i = 0; i < schema[keyword]?.length; i++) {
104+
let resolvedSchema = schema[keyword][i];
105+
if (resolvedSchema.$ref) {
106+
resolvedSchema = Resolve.schema(
107+
rootSchema,
108+
resolvedSchema.$ref,
109+
rootSchema
110+
);
111+
}
112+
resolvedCombinatorSchemas.push(resolvedSchema);
94113
}
95114

96115
// Determine the identification property
@@ -101,58 +120,25 @@ export const getCombinatorIdentificationProp = (
101120
) {
102121
idProperty = schema[COMBINATOR_TYPE_PROPERTY];
103122
} else {
104-
// Use the first default identification property that is present in the data object
105-
for (const prop of COMBINATOR_IDENTIFICATION_PROPERTIES) {
106-
if (Object.prototype.hasOwnProperty.call(data, prop)) {
107-
idProperty = prop;
108-
break;
109-
}
110-
}
111-
}
112-
113-
// If no identification property was found, use the first string or number property
114-
// of the data object
115-
if (idProperty === undefined) {
116-
for (const key of Object.keys(data)) {
117-
if (typeof data[key] === 'string' || typeof data[key] === 'number') {
118-
idProperty = key;
119-
break;
123+
// Use the first default identification property that has a const entry in at least one of the schemas
124+
for (const potentialIdProp of COMBINATOR_IDENTIFICATION_PROPERTIES) {
125+
for (const resolvedSchema of resolvedCombinatorSchemas) {
126+
if (resolvedSchema.properties?.[potentialIdProp]?.const !== undefined) {
127+
idProperty = potentialIdProp;
128+
break;
129+
}
120130
}
121131
}
122132
}
123133

124-
return idProperty;
125-
};
126-
127-
/**
128-
* Returns the index of the schema in the given combinator keyword that matches the identification property of the given data object.
129-
* The heuristic only works for data objects with a corresponding schema. If the data is a primitive value or an array, the heuristic does not work.
130-
*
131-
* If the index cannot be determined, `-1` is returned.
132-
*
133-
* @returns the index of the fitting schema or `-1` if no fitting schema was found
134-
*/
135-
export const getCombinatorIndexOfFittingSchema = (
136-
data: any,
137-
keyword: CombinatorKeyword,
138-
schema: JsonSchema,
139-
rootSchema: JsonSchema
140-
): number => {
141134
let indexOfFittingSchema = -1;
142-
const idProperty = getCombinatorIdentificationProp(data, schema);
143135
if (idProperty === undefined) {
144136
return indexOfFittingSchema;
145137
}
146138

147-
for (let i = 0; i < schema[keyword]?.length; i++) {
148-
let resolvedSchema = schema[keyword][i];
149-
if (resolvedSchema.$ref) {
150-
resolvedSchema = Resolve.schema(
151-
rootSchema,
152-
resolvedSchema.$ref,
153-
rootSchema
154-
);
155-
}
139+
// Check if the data matches the identification property of one of the resolved schemas
140+
for (let i = 0; i < resolvedCombinatorSchemas.length; i++) {
141+
const resolvedSchema = resolvedCombinatorSchemas[i];
156142

157143
// Match the identification property against a constant value in resolvedSchema
158144
const maybeConstIdValue = resolvedSchema.properties?.[idProperty]?.const;
@@ -162,9 +148,6 @@ export const getCombinatorIndexOfFittingSchema = (
162148
data[idProperty] === maybeConstIdValue
163149
) {
164150
indexOfFittingSchema = i;
165-
console.debug(
166-
`Data matches the resolved schema for property ${idProperty}`
167-
);
168151
break;
169152
}
170153
}

0 commit comments

Comments
 (0)