1/** 2 * @param schema 3 * @param rootSchema 4 * @param formData 5 * @returns {{properties: *}|{}|{properties: *}|{}|{properties: *}|{additionalProperties}|*|{}|{allOf}} 6 * 源码来自:react-jsonschema-form 7 * 做了细节和模块调整 8 * 重写了allOf实现逻辑(解决使用allOf必须根节点同时存在,以及对json-schema-merge-allof依赖包过大) 9 * 移除对lodash 、json-schema-merge-allof、jsonpointer 等依赖重新实现 10 * https://github.com/rjsf-team/react-jsonschema-form/blob/master/packages/core/src/utils.js#L621 11 */ 12 13import findSchemaDefinition from './findSchemaDefinition'; 14import { intersection } from '../arrayUtils'; 15 16import { 17 /* guessType, mergeSchemas, */ isObject, scm 18} from '../utils'; 19 20// import { getMatchingOption, isValid } from './validate'; 21 22// 自动添加分割线 23 24// export const ADDITIONAL_PROPERTY_FLAG = '__additional_property'; 25 26// resolve Schema - dependencies 27// https://json-schema.org/understanding-json-schema/reference/object.html#dependencies 28/* 29export function resolveDependencies(schema, rootSchema, formData) { 30 // 从源模式中删除依赖项。 31 const { dependencies = {} } = schema; 32 let { ...resolvedSchema } = schema; 33 if ('oneOf' in resolvedSchema) { 34 resolvedSchema = resolvedSchema.oneOf[ 35 getMatchingOption(formData, resolvedSchema.oneOf, rootSchema) 36 ]; 37 } else if ('anyOf' in resolvedSchema) { 38 resolvedSchema = resolvedSchema.anyOf[ 39 getMatchingOption(formData, resolvedSchema.anyOf, rootSchema) 40 ]; 41 } 42 return processDependencies( 43 dependencies, 44 resolvedSchema, 45 rootSchema, 46 formData 47 ); 48} 49*/ 50 51// 处理依赖关系 dependencies 52// https://json-schema.org/understanding-json-schema/reference/object.html#dependencies 53/* 54 55function processDependencies( 56 dependencies, 57 resolvedSchema, 58 rootSchema, 59 formData 60) { 61 // Process dependencies updating the local schema properties as appropriate. 62 for (const dependencyKey in dependencies) { 63 // Skip this dependency if its trigger property is not present. 64 if (formData[dependencyKey] === undefined) { 65 // eslint-disable-next-line no-continue 66 continue; 67 } 68 // Skip this dependency if it is not included in the schema (such as when dependencyKey is itself a hidden dependency.) 69 if ( 70 resolvedSchema.properties 71 && !(dependencyKey in resolvedSchema.properties) 72 ) { 73 // eslint-disable-next-line no-continue 74 continue; 75 } 76 const { 77 [dependencyKey]: dependencyValue, 78 ...remainingDependencies 79 } = dependencies; 80 if (Array.isArray(dependencyValue)) { 81 resolvedSchema = withDependentProperties(resolvedSchema, dependencyValue); 82 } else if (isObject(dependencyValue)) { 83 resolvedSchema = withDependentSchema( 84 resolvedSchema, 85 rootSchema, 86 formData, 87 dependencyKey, 88 dependencyValue 89 ); 90 } 91 return processDependencies( 92 remainingDependencies, 93 resolvedSchema, 94 rootSchema, 95 formData 96 ); 97 } 98 return resolvedSchema; 99} 100*/ 101 102// 属性依赖 103// https://json-schema.org/understanding-json-schema/reference/object.html#property-dependencies 104 105/* 106function withDependentProperties(schema, additionallyRequired) { 107 if (!additionallyRequired) { 108 return schema; 109 } 110 const required = Array.isArray(schema.required) 111 ? Array.from(new Set([...schema.required, ...additionallyRequired])) 112 : additionallyRequired; 113 return { ...schema, required }; 114} 115*/ 116 117// schema 依赖 118// https://json-schema.org/understanding-json-schema/reference/object.html#schema-dependencies 119/* 120function withDependentSchema( 121 schema, 122 rootSchema, 123 formData, 124 dependencyKey, 125 dependencyValue 126) { 127 const { oneOf, ...dependentSchema } = retrieveSchema( 128 dependencyValue, 129 rootSchema, 130 formData 131 ); 132 schema = mergeSchemas(schema, dependentSchema); 133 // Since it does not contain oneOf, we return the original schema. 134 if (oneOf === undefined) { 135 return schema; 136 } if (!Array.isArray(oneOf)) { 137 throw new Error(`invalid: it is some ${typeof oneOf} instead of an array`); 138 } 139 // Resolve $refs inside oneOf. 140 const resolvedOneOf = oneOf.map(subschema => (subschema.hasOwnProperty('$ref') 141 ? resolveReference(subschema, rootSchema, formData) 142 : subschema)); 143 return withExactlyOneSubschema( 144 schema, 145 rootSchema, 146 formData, 147 dependencyKey, 148 resolvedOneOf 149 ); 150} 151 152function withExactlyOneSubschema( 153 schema, 154 rootSchema, 155 formData, 156 dependencyKey, 157 oneOf 158) { 159 // eslint-disable-next-line array-callback-return,consistent-return 160 const validSubschemas = oneOf.filter((subschema) => { 161 if (!subschema.properties) { 162 return false; 163 } 164 const { [dependencyKey]: conditionPropertySchema } = subschema.properties; 165 if (conditionPropertySchema) { 166 const conditionSchema = { 167 type: 'object', 168 properties: { 169 [dependencyKey]: conditionPropertySchema, 170 }, 171 }; 172 173 return isValid(conditionSchema, formData); 174 } 175 }); 176 if (validSubschemas.length !== 1) { 177 console.warn( 178 "ignoring oneOf in dependencies because there isn't exactly one subschema that is valid" 179 ); 180 return schema; 181 } 182 const subschema = validSubschemas[0]; 183 const { 184 // eslint-disable-next-line no-unused-vars 185 [dependencyKey]: conditionPropertySchema, 186 ...dependentSubschema 187 } = subschema.properties; 188 const dependentSchema = { ...subschema, properties: dependentSubschema }; 189 return mergeSchemas( 190 schema, 191 retrieveSchema(dependentSchema, rootSchema, formData) 192 ); 193} 194*/ 195 196// resolve Schema - $ref 197// https://json-schema.org/understanding-json-schema/structuring.html#using-id-with-ref 198function resolveReference(schema, rootSchema, formData) { 199 // Retrieve the referenced schema definition. 200 const $refSchema = findSchemaDefinition(schema.$ref, rootSchema); 201 // Drop the $ref property of the source schema. 202 // eslint-disable-next-line no-unused-vars 203 const { $ref, ...localSchema } = schema; 204 // Update referenced schema definition with local schema properties. 205 return retrieveSchema( 206 { ...$refSchema, ...localSchema }, 207 rootSchema, 208 formData 209 ); 210} 211 212 213// 深度递归合并 合并allOf的每2项 214function mergeSchemaAllOf(...args) { 215 if (args.length < 2) return args[0]; 216 217 let preVal = {}; 218 const copyArgs = [...args]; 219 while (copyArgs.length >= 2) { 220 const obj1 = isObject(copyArgs[0]) ? copyArgs[0] : {}; 221 const obj2 = isObject(copyArgs[1]) ? copyArgs[1] : {}; 222 223 preVal = Object.assign({}, obj1); 224 Object.keys(obj2).reduce((acc, key) => { 225 const left = obj1[key]; 226 const right = obj2[key]; 227 228 // 左右一边为object 229 if (isObject(left) || isObject(right)) { 230 231 // 两边同时为object 232 if (isObject(left) && isObject(right)) { 233 acc[key] = mergeSchemaAllOf(left, right); 234 } else { 235 // 其中一边为 object 236 const [objTypeData, baseTypeData] = isObject(left) ? [left, right] : [right, left]; 237 238 if (key === 'additionalProperties') { 239 // 适配类型: 一边配置了对象一边没配置或者true false 240 // { 241 // additionalProperties: { 242 // type: 'string', 243 // }, 244 // additionalProperties: false 245 // } 246 acc[key] = baseTypeData === true ? objTypeData : false; // default false 247 } else { 248 acc[key] = objTypeData; 249 } 250 } 251 // 一边为array 252 } else if (Array.isArray(left) || Array.isArray(right)) { 253 254 // 同为数组取交集 255 if (Array.isArray(left) && Array.isArray(right)) { 256 257 // 数组里面嵌套对象不支持 因为我不知道该怎么合并 258 if (isObject(left[0]) || isObject(right[0])) { 259 throw new Error('暂不支持如上数组对象元素合并'); 260 } 261 262 // 交集 263 const intersectionArray = intersection([].concat(left), [].concat(right)); 264 265 // 没有交集 266 if (intersectionArray.length <= 0) { 267 throw new Error('无法合并如上数据'); 268 } 269 270 if (intersectionArray.length === 0 && key === 'type') { 271 // 自己取出值 272 acc[key] = intersectionArray[0]; 273 } else { 274 acc[key] = intersectionArray; 275 } 276 } else { 277 // 其中一边为 Array 278 // 查找包含关系 279 const [arrayTypeData, baseTypeData] = Array.isArray(left) ? [left, right] : [right, left]; 280 // 空值直接合并另一边 281 if (baseTypeData === undefined) { 282 acc[key] = arrayTypeData; 283 } else { 284 if (!arrayTypeData.includes(baseTypeData)) { 285 throw new Error('无法合并如下数据'); 286 } 287 acc[key] = baseTypeData; 288 } 289 } 290 } else if (left !== undefined && right !== undefined) { 291 // 两边都不是 undefined - 基础数据类型 string number boolean... 292 if (key === 'maxLength' || key === 'maximum' || key === 'maxItems' || key === 'exclusiveMaximum' || key === 'maxProperties') { 293 acc[key] = Math.min(left, right); 294 } else if (key === 'minLength' || key === 'minimum' || key === 'minItems' || key === 'exclusiveMinimum' || key === 'minProperties') { 295 acc[key] = Math.max(left, right); 296 } else if (key === 'multipleOf') { 297 // 获取最小公倍数 298 acc[key] = scm(left, right); 299 } else { 300 // if (left !== right) { 301 // throw new Error('无法合并如下数据'); 302 // } 303 acc[key] = left; 304 } 305 } else { 306 // 一边为undefined 307 acc[key] = left === undefined ? right : left; 308 } 309 return acc; 310 }, preVal); 311 312 // 先进先出 313 copyArgs.splice(0, 2, preVal); 314 } 315 316 return preVal; 317} 318 319// resolve Schema - allOf 320export function resolveAllOf(schema, rootSchema, formData) { 321 // allOf item中可能存在 $ref 322 const resolvedAllOfRefSchema = { 323 ...schema, 324 allOf: schema.allOf.map(allOfItem => retrieveSchema(allOfItem, rootSchema, formData)), 325 }; 326 327 try { 328 const { allOf, ...originProperties } = resolvedAllOfRefSchema; 329 return mergeSchemaAllOf(originProperties, ...allOf); 330 } catch (e) { 331 console.error(`无法合并allOf,丢弃allOf配置继续渲染: \n${e}`); 332 // eslint-disable-next-line no-unused-vars 333 const { allOf: errAllOf, ...resolvedSchemaWithoutAllOf } = resolvedAllOfRefSchema; 334 return resolvedSchemaWithoutAllOf; 335 } 336} 337 338// resolve Schema 339function resolveSchema(schema, rootSchema = {}, formData = {}) { 340 // allOf 、$ref、dependencies 可能被同时配置 341 342 // allOf 343 if (schema.hasOwnProperty('allOf')) { 344 schema = resolveAllOf(schema, rootSchema, formData); 345 } 346 347 // $ref 348 if (schema.hasOwnProperty('$ref')) { 349 schema = resolveReference(schema, rootSchema, formData); 350 } 351 352 // dependencies 353 /* 354 if (schema.hasOwnProperty('dependencies')) { 355 const resolvedSchema = resolveDependencies(schema, rootSchema, formData); 356 schema = retrieveSchema(resolvedSchema, rootSchema, formData); 357 } 358 */ 359 360 // additionalProperties 361 /* 362 const hasAdditionalProperties = schema.hasOwnProperty('additionalProperties') && schema.additionalProperties !== false; 363 if (hasAdditionalProperties) { 364 return stubExistingAdditionalProperties( 365 schema, 366 rootSchema, 367 formData 368 ); 369 } 370 */ 371 372 return schema; 373} 374 375// 这个函数将为formData中的每个键创建新的“属性”项 376// 查找到附加属性统一到properties[key]格式 并且打上标准 377/* function stubExistingAdditionalProperties( 378 schema, 379 rootSchema = {}, 380 formData = {} 381) { 382 // clone the schema so we don't ruin the consumer's original 383 schema = { 384 ...schema, 385 properties: { ...schema.properties }, 386 }; 387 388 Object.keys(formData).forEach((key) => { 389 if (schema.properties.hasOwnProperty(key)) { 390 // No need to stub, our schema already has the property 391 return; 392 } 393 394 let additionalProperties; 395 if (schema.additionalProperties.hasOwnProperty('$ref')) { 396 additionalProperties = retrieveSchema( 397 { $ref: schema.additionalProperties.$ref }, 398 rootSchema, 399 formData 400 ); 401 } else if (schema.additionalProperties.hasOwnProperty('type')) { 402 additionalProperties = { ...schema.additionalProperties }; 403 } else { 404 additionalProperties = { type: guessType(formData[key]) }; 405 } 406 407 // The type of our new key should match the additionalProperties value; 408 // 把追加进去的属性设置为标准 schema格式,同时打上标志 409 schema.properties[key] = additionalProperties; 410 // Set our additional property flag so we know it was dynamically added 411 schema.properties[key][ADDITIONAL_PROPERTY_FLAG] = true; 412 }); 413 414 return schema; 415} */ 416 417// 索引当前节点 418export default function retrieveSchema(schema, rootSchema = {}, formData = {}) { 419 if (!isObject(schema)) { 420 return {}; 421 } 422 423 return resolveSchema(schema, rootSchema, formData); 424} 425