feat(json-schema-2020-12): infer type of schema if not defined

Handles schema cycles as well.

Refs #8513
This commit is contained in:
Vladimir Gorej
2023-04-19 09:10:53 +02:00
committed by Vladimír Gorej
parent 83ba76c117
commit e517397fd2
2 changed files with 87 additions and 8 deletions

View File

@@ -8,7 +8,6 @@ import classNames from "classnames"
import * as propTypes from "../../prop-types"
import {
useComponent,
useFn,
useLevel,
useIsEmbedded,
useIsExpandedDeeply,
@@ -22,7 +21,6 @@ const JSONSchema = ({ schema, name }) => {
const isExpandedDeeply = useIsExpandedDeeply()
const [expanded, setExpanded] = useState(isExpandedDeeply)
const [expandedDeeply, setExpandedDeeply] = useState(false)
const fn = useFn()
const [level, nextLevel] = useLevel()
const isEmbedded = useIsEmbedded()
const Accordion = useComponent("Accordion")

View File

@@ -16,16 +16,97 @@ export const getTitle = (schema) => {
return ""
}
export const getType = (schema) => {
if (Array.isArray(schema?.type)) {
return schema.type.map(String).join(" | ")
}
if (schema?.type != null) {
return String(schema.type)
}
export const getType = (schema, processedSchemas = new WeakSet()) => {
if (schema == null) {
return "any"
}
if (typeof schema === "boolean") {
return schema ? "any" : "never"
}
if (typeof schema !== "object") {
return "any"
}
if (processedSchemas.has(schema)) {
return "any" // detect a cycle
}
processedSchemas.add(schema)
const { type, items } = schema
const getArrayType = () => {
if (!items) {
return "array<any>"
}
const itemsType = getType(items, processedSchemas)
return `array<${itemsType}>`
}
const inferType = () => {
if (items) {
return getArrayType()
} else if (schema.properties || schema.additionalProperties) {
return "object"
} else if (
schema.pattern ||
schema.format ||
schema.minLength ||
schema.maxLength
) {
return "string"
} else if (
schema.minimum ||
schema.maximum ||
schema.exclusiveMinimum ||
schema.exclusiveMaximum ||
schema.multipleOf
) {
return "number|integer"
} else if (schema.const !== undefined) {
if (schema.const === null) {
return "null"
} else if (typeof schema.const === "boolean") {
return "boolean"
} else if (typeof schema.const === "number") {
return Number.isInteger(schema.const) ? "integer" : "number"
} else if (typeof schema.const === "string") {
return "string"
} else if (typeof schema.const === "object") {
return "object"
}
}
return null
}
const typeString = Array.isArray(type)
? type.map((t) => (t === "array" ? getArrayType() : t)).join(" | ")
: type && type.includes("array")
? getArrayType()
: type || inferType()
const handleCombiningKeywords = (keyword, separator) => {
if (Array.isArray(schema[keyword])) {
const combinedTypes = schema[keyword].map((subSchema) =>
getType(subSchema, processedSchemas)
)
return `(${combinedTypes.join(separator)})`
}
return null
}
const oneOfString = handleCombiningKeywords("oneOf", " | ")
const anyOfString = handleCombiningKeywords("anyOf", " | ")
const allOfString = handleCombiningKeywords("allOf", " & ")
const combinedStrings = [typeString, oneOfString, anyOfString, allOfString]
.filter(Boolean)
.join(" | ")
processedSchemas.delete(schema)
return combinedStrings || "any"
}
export const isBooleanJSONSchema = (schema) => typeof schema === "boolean"