Skip to content
5 changes: 3 additions & 2 deletions dist/index.d.mts
Original file line number Diff line number Diff line change
Expand Up @@ -636,13 +636,14 @@ declare function mergeNode(a: SchemaNode, b?: SchemaNode, ...omit: string[]): Sc
declare function mergeSchema<T extends JsonSchema>(a: T, b: T, ...omit: string[]): T;
//#endregion
//#region src/utils/getSchemaType.d.ts
declare const SCHEMA_TYPES: string[];
declare const SCHEMA_TYPES: readonly ["string", "number", "integer", "boolean", "null", "array", "object"];
type SchemaType = (typeof SCHEMA_TYPES)[number];
/**
* @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.
*/
declare function getSchemaType(node: SchemaNode, data: unknown): keyof typeof SCHEMA_TYPES | undefined;
declare function getSchemaType(node: SchemaNode, data: unknown): SchemaType | undefined;
//#endregion
//#region remotes/index.d.ts
/** remote meta-schema stored by schema $id */
Expand Down
5 changes: 3 additions & 2 deletions dist/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -636,13 +636,14 @@ declare function mergeNode(a: SchemaNode, b?: SchemaNode, ...omit: string[]): Sc
declare function mergeSchema<T extends JsonSchema>(a: T, b: T, ...omit: string[]): T;
//#endregion
//#region src/utils/getSchemaType.d.ts
declare const SCHEMA_TYPES: string[];
declare const SCHEMA_TYPES: readonly ["string", "number", "integer", "boolean", "null", "array", "object"];
type SchemaType = (typeof SCHEMA_TYPES)[number];
/**
* @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.
*/
declare function getSchemaType(node: SchemaNode, data: unknown): keyof typeof SCHEMA_TYPES | undefined;
declare function getSchemaType(node: SchemaNode, data: unknown): SchemaType | undefined;
//#endregion
//#region remotes/index.d.ts
/** remote meta-schema stored by schema $id */
Expand Down
2 changes: 1 addition & 1 deletion dist/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/index.mjs

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions dist/jlib.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/draft2019-09/keywords/$ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export function parseRef(node: SchemaNode) {
export function resolveRef({ pointer, path }: { pointer?: string; path?: ValidationPath } = {}) {
const node = this as SchemaNode;
if (node.schema.$recursiveRef) {
const nextNode = resolveRecursiveRef(node, path);
const nextNode = resolveRecursiveRef(node, path ?? []);
path?.push({ pointer, node: nextNode });
return nextNode;
}
Expand Down
6 changes: 3 additions & 3 deletions src/draft2019-09/methods/getData.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import copy from "fast-copy";
import { getTypeOf } from "../../utils/getTypeOf";
import { getSchemaType } from "../../utils/getSchemaType";
import { getSchemaType, SchemaType } from "../../utils/getSchemaType";
import { getValue } from "../../utils/getValue";
import { isEmpty } from "../../utils/isEmpty";
import { isJsonError } from "../../types";
Expand Down Expand Up @@ -172,11 +172,11 @@ export function getData(node: SchemaNode, data?: unknown, opts?: TemplateOptions
// }

const type = getSchemaType(currentNode, defaultData);
const templateData = TYPE[type as string]?.(currentNode, defaultData, opts);
const templateData = TYPE[type]?.(currentNode, defaultData, opts);
return templateData === undefined ? defaultData : templateData;
}

const TYPE: Record<string, (node: SchemaNode, data: unknown, opts: TemplateOptions) => unknown> = {
const TYPE: Record<SchemaType, (node: SchemaNode, data: unknown, opts: TemplateOptions) => unknown> = {
null: (node, data, opts) => getDefault(node, data, null, opts.useTypeDefaults),
string: (node, data, opts) => getDefault(node, data, "", opts.useTypeDefaults),
number: (node, data, opts) => getDefault(node, data, 0, opts.useTypeDefaults),
Expand Down
101 changes: 101 additions & 0 deletions src/utils/getSchemaType.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { compileSchema } from "../compileSchema";
import { getSchemaType } from "./getSchemaType";
import { strict as assert } from "assert";

describe("issue#90 - types or refs", () => {
it("should get correct type for simple ref", () => {
const schema = compileSchema({
type: "object",
$defs: {
customConst: {
type: "string"
}
},
properties: {
name: { $ref: "#/$defs/customConst" }
}
});
const nameProp = schema.getNodeChild("name").node;
assert(nameProp != null);
assert(getSchemaType(nameProp, undefined) === "string");
});

it("should handle oneOf refs", () => {
const schema = compileSchema({
type: "object",
$defs: {
a: {
type: "string",
const: "a"
},
b: {
type: "string",
const: "b"
}
},

properties: {
oneOf: {
oneOf: [{ $ref: "#/$defs/a" }, { $ref: "#/$defs/b" }]
}
}
});
const oneOfProp = schema.getNodeChild("oneOf").node;
assert(oneOfProp != null);
assert(getSchemaType(oneOfProp, undefined) === "string");
});
it("should handle anyOf refs", () => {
const schema = compileSchema({
type: "object",
$defs: {
a: { type: "string", const: "a" },
b: { type: "string", const: "b" }
},
properties: {
anyOf: {
anyOf: [{ $ref: "#/$defs/a" }, { $ref: "#/$defs/b" }]
}
}
});
const anyOfProp = schema.getNodeChild("anyOf").node;
assert(anyOfProp != null);
assert(getSchemaType(anyOfProp, undefined) === "string");
});
it("should handle allOf refs", () => {
const schema = compileSchema({
type: "object",
$defs: {
a: { type: "object", properties: { a: { type: "string" } } },
b: { type: "object", properties: { b: { type: "string" } } }
},
properties: {
allOf: {
allOf: [{ $ref: "#/$defs/a" }, { $ref: "#/$defs/b" }]
}
}
});
const allOfProp = schema.getNodeChild("allOf").node;
assert(allOfProp != null);
assert(getSchemaType(allOfProp, undefined) === "object");
});
it("should handle if/then/else refs", () => {
const schema = compileSchema({
type: "object",
$defs: {
stringSchema: {
type: "string"
}
},
properties: {
conditional: {
if: { $ref: "#/$defs/stringSchema" },
then: { minLength: 5 },
else: { maxLength: 2 }
}
}
});
const conditionalProp = schema.getNodeChild("conditional").node;
assert(conditionalProp != null);
assert(getSchemaType(conditionalProp, undefined) === "string");
});
});
41 changes: 26 additions & 15 deletions src/utils/getSchemaType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ 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"];
export const SCHEMA_TYPES = ["string", "number", "integer", "boolean", "null", "array", "object"] as const;
export type SchemaType = (typeof SCHEMA_TYPES)[number];
const OBJECT_PROPERTIES = [
"additionalProperties",
// "allOf",
Expand Down Expand Up @@ -43,11 +44,14 @@ const ARRAY_PROPERTIES = [
* 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 {
export function getSchemaType(node: SchemaNode, data: unknown): SchemaType | 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;
if (dataType === "bigint") {
return "number";
}
return SCHEMA_TYPES.some((schemaType) => schemaType === dataType) ? (dataType as SchemaType) : undefined;
}
// boolean schema false or invalid schema
if (!isObject(schema)) {
Expand All @@ -58,32 +62,32 @@ export function getSchemaType(node: SchemaNode, data: unknown): keyof typeof SCH
// type: []
if (Array.isArray(schemaType)) {
if (schemaType.includes(dataType)) {
return dataType as keyof typeof SCHEMA_TYPES;
return dataType as SchemaType;
}
const defaultType = getTypeOf(schema.default);
if (schemaType.includes(defaultType)) {
return defaultType as keyof typeof SCHEMA_TYPES;
return defaultType as SchemaType;
}
return schemaType[0];
}

// type: ""
if (schemaType) {
return schemaType as keyof typeof SCHEMA_TYPES;
return schemaType as SchemaType;
}

// 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;
return dataType as SchemaType;
}
const defaultType = getTypeOf(schema.default);
if (enumSchemaType.includes(defaultType)) {
return defaultType as keyof typeof SCHEMA_TYPES;
return defaultType as SchemaType;
}
return enumSchemaType[0] as keyof typeof SCHEMA_TYPES;
return enumSchemaType[0] as SchemaType;
}

// type: undefined, enum: undefined -- define type by schema-properties
Expand All @@ -93,21 +97,21 @@ export function getSchemaType(node: SchemaNode, data: unknown): keyof typeof SCH
const arrayProperties = schemaProperties.filter((p) => ARRAY_PROPERTIES.includes(p));

if (objectProperties.length > 0 && objectProperties.length > arrayProperties.length) {
return "object" as keyof typeof SCHEMA_TYPES;
return "object";
}

if (arrayProperties.length > 0 && arrayProperties.length > objectProperties.length) {
return "array" as keyof typeof SCHEMA_TYPES;
return "array";
}

// nothing found yet check dynamic properties for a type
if (node.if) {
return getSchemaType(node.if, data);
return getSchemaType(node.if.resolveRef?.() ?? node.if, data);
}

if (node.allOf) {
for (let i = 0; i < node.allOf.length; i += 1) {
const type = getSchemaType(node.allOf[i], data);
const type = getSchemaType(node.allOf[i].resolveRef?.() ?? node.allOf[i], data);
if (type) {
return type;
}
Expand All @@ -116,7 +120,7 @@ export function getSchemaType(node: SchemaNode, data: unknown): keyof typeof SCH

if (node.oneOf) {
for (let i = 0; i < node.oneOf.length; i += 1) {
const type = getSchemaType(node.oneOf[i], data);
const type = getSchemaType(node.oneOf[i].resolveRef?.() ?? node.oneOf[i], data);
if (type) {
return type;
}
Expand All @@ -125,12 +129,19 @@ export function getSchemaType(node: SchemaNode, data: unknown): keyof typeof SCH

if (node.anyOf) {
for (let i = 0; i < node.anyOf.length; i += 1) {
const type = getSchemaType(node.anyOf[i], data);
const type = getSchemaType(node.anyOf[i].resolveRef?.() ?? node.anyOf[i], data);
if (type) {
return type;
}
}
}

if (schema.$ref) {
const refNode = node.resolveRef?.();
if (refNode) {
return getSchemaType(refNode, data);
}
}

return undefined;
}