@fx-form/utils
Version:
vue json schema form 使用的基础utils工具类
426 lines (381 loc) • 15.1 kB
JavaScript
/**
* @param schema
* @param rootSchema
* @param formData
* @returns {{properties: *}|{}|{properties: *}|{}|{properties: *}|{additionalProperties}|*|{}|{allOf}}
* 源码来自:react-jsonschema-form
* 做了细节和模块调整
* 重写了allOf实现逻辑(解决使用allOf必须根节点同时存在,以及对json-schema-merge-allof依赖包过大)
* 移除对lodash 、json-schema-merge-allof、jsonpointer 等依赖重新实现
* https://github.com/rjsf-team/react-jsonschema-form/blob/master/packages/core/src/utils.js#L621
*/
import findSchemaDefinition from './findSchemaDefinition';
import { intersection } from '../arrayUtils';
import {
/* guessType, mergeSchemas, */ isObject, scm
} from '../utils';
// import { getMatchingOption, isValid } from './validate';
// 自动添加分割线
// export const ADDITIONAL_PROPERTY_FLAG = '__additional_property';
// resolve Schema - dependencies
// https://json-schema.org/understanding-json-schema/reference/object.html#dependencies
/*
export function resolveDependencies(schema, rootSchema, formData) {
// 从源模式中删除依赖项。
const { dependencies = {} } = schema;
let { ...resolvedSchema } = schema;
if ('oneOf' in resolvedSchema) {
resolvedSchema = resolvedSchema.oneOf[
getMatchingOption(formData, resolvedSchema.oneOf, rootSchema)
];
} else if ('anyOf' in resolvedSchema) {
resolvedSchema = resolvedSchema.anyOf[
getMatchingOption(formData, resolvedSchema.anyOf, rootSchema)
];
}
return processDependencies(
dependencies,
resolvedSchema,
rootSchema,
formData
);
}
*/
// 处理依赖关系 dependencies
// https://json-schema.org/understanding-json-schema/reference/object.html#dependencies
/*
function processDependencies(
dependencies,
resolvedSchema,
rootSchema,
formData
) {
// Process dependencies updating the local schema properties as appropriate.
for (const dependencyKey in dependencies) {
// Skip this dependency if its trigger property is not present.
if (formData[dependencyKey] === undefined) {
// eslint-disable-next-line no-continue
continue;
}
// Skip this dependency if it is not included in the schema (such as when dependencyKey is itself a hidden dependency.)
if (
resolvedSchema.properties
&& !(dependencyKey in resolvedSchema.properties)
) {
// eslint-disable-next-line no-continue
continue;
}
const {
[dependencyKey]: dependencyValue,
...remainingDependencies
} = dependencies;
if (Array.isArray(dependencyValue)) {
resolvedSchema = withDependentProperties(resolvedSchema, dependencyValue);
} else if (isObject(dependencyValue)) {
resolvedSchema = withDependentSchema(
resolvedSchema,
rootSchema,
formData,
dependencyKey,
dependencyValue
);
}
return processDependencies(
remainingDependencies,
resolvedSchema,
rootSchema,
formData
);
}
return resolvedSchema;
}
*/
// 属性依赖
// https://json-schema.org/understanding-json-schema/reference/object.html#property-dependencies
/*
function withDependentProperties(schema, additionallyRequired) {
if (!additionallyRequired) {
return schema;
}
const required = Array.isArray(schema.required)
? Array.from(new Set([...schema.required, ...additionallyRequired]))
: additionallyRequired;
return { ...schema, required };
}
*/
// schema 依赖
// https://json-schema.org/understanding-json-schema/reference/object.html#schema-dependencies
/*
function withDependentSchema(
schema,
rootSchema,
formData,
dependencyKey,
dependencyValue
) {
const { oneOf, ...dependentSchema } = retrieveSchema(
dependencyValue,
rootSchema,
formData
);
schema = mergeSchemas(schema, dependentSchema);
// Since it does not contain oneOf, we return the original schema.
if (oneOf === undefined) {
return schema;
} if (!Array.isArray(oneOf)) {
throw new Error(`invalid: it is some ${typeof oneOf} instead of an array`);
}
// Resolve $refs inside oneOf.
const resolvedOneOf = oneOf.map(subschema => (subschema.hasOwnProperty('$ref')
? resolveReference(subschema, rootSchema, formData)
: subschema));
return withExactlyOneSubschema(
schema,
rootSchema,
formData,
dependencyKey,
resolvedOneOf
);
}
function withExactlyOneSubschema(
schema,
rootSchema,
formData,
dependencyKey,
oneOf
) {
// eslint-disable-next-line array-callback-return,consistent-return
const validSubschemas = oneOf.filter((subschema) => {
if (!subschema.properties) {
return false;
}
const { [dependencyKey]: conditionPropertySchema } = subschema.properties;
if (conditionPropertySchema) {
const conditionSchema = {
type: 'object',
properties: {
[dependencyKey]: conditionPropertySchema,
},
};
return isValid(conditionSchema, formData);
}
});
if (validSubschemas.length !== 1) {
console.warn(
"ignoring oneOf in dependencies because there isn't exactly one subschema that is valid"
);
return schema;
}
const subschema = validSubschemas[0];
const {
// eslint-disable-next-line no-unused-vars
[dependencyKey]: conditionPropertySchema,
...dependentSubschema
} = subschema.properties;
const dependentSchema = { ...subschema, properties: dependentSubschema };
return mergeSchemas(
schema,
retrieveSchema(dependentSchema, rootSchema, formData)
);
}
*/
// resolve Schema - $ref
// https://json-schema.org/understanding-json-schema/structuring.html#using-id-with-ref
function resolveReference(schema, rootSchema, formData) {
// Retrieve the referenced schema definition.
const $refSchema = findSchemaDefinition(schema.$ref, rootSchema);
// Drop the $ref property of the source schema.
// eslint-disable-next-line no-unused-vars
const { $ref, ...localSchema } = schema;
// Update referenced schema definition with local schema properties.
return retrieveSchema(
{ ...$refSchema, ...localSchema },
rootSchema,
formData
);
}
// 深度递归合并 合并allOf的每2项
function mergeSchemaAllOf(...args) {
if (args.length < 2) return args[0];
let preVal = {};
const copyArgs = [...args];
while (copyArgs.length >= 2) {
const obj1 = isObject(copyArgs[0]) ? copyArgs[0] : {};
const obj2 = isObject(copyArgs[1]) ? copyArgs[1] : {};
preVal = Object.assign({}, obj1);
Object.keys(obj2).reduce((acc, key) => {
const left = obj1[key];
const right = obj2[key];
// 左右一边为object
if (isObject(left) || isObject(right)) {
// 两边同时为object
if (isObject(left) && isObject(right)) {
acc[key] = mergeSchemaAllOf(left, right);
} else {
// 其中一边为 object
const [objTypeData, baseTypeData] = isObject(left) ? [left, right] : [right, left];
if (key === 'additionalProperties') {
// 适配类型: 一边配置了对象一边没配置或者true false
// {
// additionalProperties: {
// type: 'string',
// },
// additionalProperties: false
// }
acc[key] = baseTypeData === true ? objTypeData : false; // default false
} else {
acc[key] = objTypeData;
}
}
// 一边为array
} else if (Array.isArray(left) || Array.isArray(right)) {
// 同为数组取交集
if (Array.isArray(left) && Array.isArray(right)) {
// 数组里面嵌套对象不支持 因为我不知道该怎么合并
if (isObject(left[0]) || isObject(right[0])) {
throw new Error('暂不支持如上数组对象元素合并');
}
// 交集
const intersectionArray = intersection([].concat(left), [].concat(right));
// 没有交集
if (intersectionArray.length <= 0) {
throw new Error('无法合并如上数据');
}
if (intersectionArray.length === 0 && key === 'type') {
// 自己取出值
acc[key] = intersectionArray[0];
} else {
acc[key] = intersectionArray;
}
} else {
// 其中一边为 Array
// 查找包含关系
const [arrayTypeData, baseTypeData] = Array.isArray(left) ? [left, right] : [right, left];
// 空值直接合并另一边
if (baseTypeData === undefined) {
acc[key] = arrayTypeData;
} else {
if (!arrayTypeData.includes(baseTypeData)) {
throw new Error('无法合并如下数据');
}
acc[key] = baseTypeData;
}
}
} else if (left !== undefined && right !== undefined) {
// 两边都不是 undefined - 基础数据类型 string number boolean...
if (key === 'maxLength' || key === 'maximum' || key === 'maxItems' || key === 'exclusiveMaximum' || key === 'maxProperties') {
acc[key] = Math.min(left, right);
} else if (key === 'minLength' || key === 'minimum' || key === 'minItems' || key === 'exclusiveMinimum' || key === 'minProperties') {
acc[key] = Math.max(left, right);
} else if (key === 'multipleOf') {
// 获取最小公倍数
acc[key] = scm(left, right);
} else {
if (left !== right) {
throw new Error('无法合并如下数据');
}
acc[key] = left;
}
} else {
// 一边为undefined
acc[key] = left === undefined ? right : left;
}
return acc;
}, preVal);
// 先进先出
copyArgs.splice(0, 2, preVal);
}
return preVal;
}
// resolve Schema - allOf
export function resolveAllOf(schema, rootSchema, formData) {
// allOf item中可能存在 $ref
const resolvedAllOfRefSchema = {
...schema,
allOf: schema.allOf.map(allOfItem => retrieveSchema(allOfItem, rootSchema, formData)),
};
try {
const { allOf, ...originProperties } = resolvedAllOfRefSchema;
return mergeSchemaAllOf(originProperties, ...allOf);
} catch (e) {
console.warn(`无法合并allOf,丢弃allOf配置继续渲染: \n${e}`);
// eslint-disable-next-line no-unused-vars
const { allOf: errAllOf, ...resolvedSchemaWithoutAllOf } = resolvedAllOfRefSchema;
return resolvedSchemaWithoutAllOf;
}
}
// resolve Schema
function resolveSchema(schema, rootSchema = {}, formData = {}) {
// allOf 、$ref、dependencies 可能被同时配置
// allOf
if (schema.hasOwnProperty('allOf')) {
schema = resolveAllOf(schema, rootSchema, formData);
}
// $ref
if (schema.hasOwnProperty('$ref')) {
schema = resolveReference(schema, rootSchema, formData);
}
// dependencies
/*
if (schema.hasOwnProperty('dependencies')) {
const resolvedSchema = resolveDependencies(schema, rootSchema, formData);
schema = retrieveSchema(resolvedSchema, rootSchema, formData);
}
*/
// additionalProperties
/*
const hasAdditionalProperties = schema.hasOwnProperty('additionalProperties') && schema.additionalProperties !== false;
if (hasAdditionalProperties) {
return stubExistingAdditionalProperties(
schema,
rootSchema,
formData
);
}
*/
return schema;
}
// 这个函数将为formData中的每个键创建新的“属性”项
// 查找到附加属性统一到properties[key]格式 并且打上标准
/* function stubExistingAdditionalProperties(
schema,
rootSchema = {},
formData = {}
) {
// clone the schema so we don't ruin the consumer's original
schema = {
...schema,
properties: { ...schema.properties },
};
Object.keys(formData).forEach((key) => {
if (schema.properties.hasOwnProperty(key)) {
// No need to stub, our schema already has the property
return;
}
let additionalProperties;
if (schema.additionalProperties.hasOwnProperty('$ref')) {
additionalProperties = retrieveSchema(
{ $ref: schema.additionalProperties.$ref },
rootSchema,
formData
);
} else if (schema.additionalProperties.hasOwnProperty('type')) {
additionalProperties = { ...schema.additionalProperties };
} else {
additionalProperties = { type: guessType(formData[key]) };
}
// The type of our new key should match the additionalProperties value;
// 把追加进去的属性设置为标准 schema格式,同时打上标志
schema.properties[key] = additionalProperties;
// Set our additional property flag so we know it was dynamically added
schema.properties[key][ADDITIONAL_PROPERTY_FLAG] = true;
});
return schema;
} */
// 索引当前节点
export default function retrieveSchema(schema, rootSchema = {}, formData = {}) {
if (!isObject(schema)) {
return {};
}
return resolveSchema(schema, rootSchema, formData);
}