diff --git a/src/core/plugins/json-schema-2020-12/components/JSONSchema/JSONSchema.jsx b/src/core/plugins/json-schema-2020-12/components/JSONSchema/JSONSchema.jsx index 053c2187..23ed1638 100644 --- a/src/core/plugins/json-schema-2020-12/components/JSONSchema/JSONSchema.jsx +++ b/src/core/plugins/json-schema-2020-12/components/JSONSchema/JSONSchema.jsx @@ -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") diff --git a/src/core/plugins/json-schema-2020-12/fn.js b/src/core/plugins/json-schema-2020-12/fn.js index 84bd17bd..3d5ec7ae 100644 --- a/src/core/plugins/json-schema-2020-12/fn.js +++ b/src/core/plugins/json-schema-2020-12/fn.js @@ -16,16 +16,97 @@ export const getTitle = (schema) => { return "" } -export const getType = (schema) => { - if (Array.isArray(schema?.type)) { - return schema.type.map(String).join(" | ") +export const getType = (schema, processedSchemas = new WeakSet()) => { + if (schema == null) { + return "any" } - if (schema?.type != null) { - return String(schema.type) + if (typeof schema === "boolean") { + return schema ? "any" : "never" } - return "any" + 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" + } + 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"