json-schema-library
Version:
Customizable and hackable json-validator and json-schema utilities for traversal, data generation and validation
137 lines (123 loc) • 4.1 kB
text/typescript
import { getTypeOf } from "./getTypeOf";
import { isObject } from "../utils/isObject";
import { BooleanSchema, JsonSchema, SchemaNode } from "../types";
export const SCHEMA_TYPES = ["string", "number", "integer", "boolean", "null", "array", "object"];
const OBJECT_PROPERTIES = [
"additionalProperties",
// "allOf",
// "anyOf",
"dependencies",
"dependentSchemas",
"dependentRequired",
// "enum",
// "format",
// "if",
"maxProperties",
"minProperties",
// "not",
// "oneOf",
"patternProperties",
"properties",
"propertyNames",
"required",
"unevaluatedProperties" // 2019-09
];
const ARRAY_PROPERTIES = [
// "allOf",
// "anyOf",
"contains",
// "enum",
// "if",
"items",
"maxItems",
"minItems",
// "not",
// "oneOf",
"unevaluatedItems",
"uniqueItems"
];
/**
* @helper for getData
* returns schema type, which might be an educated guess based on defined schema
* properties if an exact type cannot be retried from type.
*/
export function getSchemaType(node: SchemaNode, data: unknown): keyof typeof SCHEMA_TYPES | undefined {
const dataType = getTypeOf(data);
const schema = node.schema as JsonSchema | BooleanSchema;
if (schema === true) {
return SCHEMA_TYPES.includes(dataType) ? (dataType as keyof typeof SCHEMA_TYPES) : undefined;
}
// boolean schema false or invalid schema
if (!isObject(schema)) {
return undefined;
}
const schemaType = schema.type;
// type: []
if (Array.isArray(schemaType)) {
if (schemaType.includes(dataType)) {
return dataType as keyof typeof SCHEMA_TYPES;
}
const defaultType = getTypeOf(schema.default);
if (schemaType.includes(defaultType)) {
return defaultType as keyof typeof SCHEMA_TYPES;
}
return schemaType[0];
}
// type: ""
if (schemaType) {
return schemaType as keyof typeof SCHEMA_TYPES;
}
// type: undefined, enum: []
if (Array.isArray(schema.enum)) {
const schemaEnum: unknown[] = schema.enum;
const enumSchemaType = schemaEnum.map((value) => getTypeOf(value)).filter((p, i, l) => l.indexOf(p) === i);
if (enumSchemaType.includes(dataType)) {
return dataType as keyof typeof SCHEMA_TYPES;
}
const defaultType = getTypeOf(schema.default);
if (enumSchemaType.includes(defaultType)) {
return defaultType as keyof typeof SCHEMA_TYPES;
}
return enumSchemaType[0] as keyof typeof SCHEMA_TYPES;
}
// type: undefined, enum: undefined -- define type by schema-properties
// @attenation this is prone to wrong results
const schemaProperties = Object.keys(node.schema);
const objectProperties = schemaProperties.filter((p) => OBJECT_PROPERTIES.includes(p));
const arrayProperties = schemaProperties.filter((p) => ARRAY_PROPERTIES.includes(p));
if (objectProperties.length > 0 && objectProperties.length > arrayProperties.length) {
return "object" as keyof typeof SCHEMA_TYPES;
}
if (arrayProperties.length > 0 && arrayProperties.length > objectProperties.length) {
return "array" as keyof typeof SCHEMA_TYPES;
}
// nothing found yet check dynamic properties for a type
if (node.if) {
return getSchemaType(node.if, data);
}
if (node.allOf) {
for (let i = 0; i < node.allOf.length; i += 1) {
const type = getSchemaType(node.allOf[i], data);
if (type) {
return type;
}
}
}
if (node.oneOf) {
for (let i = 0; i < node.oneOf.length; i += 1) {
const type = getSchemaType(node.oneOf[i], data);
if (type) {
return type;
}
}
}
if (node.anyOf) {
for (let i = 0; i < node.anyOf.length; i += 1) {
const type = getSchemaType(node.anyOf[i], data);
if (type) {
return type;
}
}
}
return undefined;
}