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 @@ -1110,13 +1110,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
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
34 changes: 19 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.getNode("#").node ?? node.if, data);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is out of scope of this merge request. I am wondering, why are you adding ref-resolution here?

If we were to add this:

  • getNode would need data as second argument to fully use its api
  • I would prefer to use node.if.resolveRef here to be explicit

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the short story here is that first I realised that types of getSchemaType are off, so I fixed them and created this PR, later while working on my product I've seen that the function is also not working correctly on oneOf nodes with refs, so I thought this PR is close enough to include this as well... If you prefer I can split this into two PRs (or we can keep it as single one if that's ok)

as for node.if.resolveRef - I think it should work... I pushed the changes about that, are those ok?
and if you won't mind - what's the real benefit of using resolveRef here? I think that getNode looks to be way more generic way of "get me whatever there is underneath" right? under the hood it's also looks to handle more edge cases than resolveRef 🤔

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is one more issue here:

  • with resolveRef I have errors in current tests
 Exception during run: TypeError: Cannot read properties of undefined (reading 'length')
    at resolveRecursiveRef (/projects/json-schema-library/src/draft2019-09/keywords/$ref.ts:108:26)
    at Object.resolveRef (/projects/json-schema-library/src/draft2019-09/keywords/$ref.ts:75:26)
    at getSchemaType (/projects/json-schema-library/src/utils/getSchemaType.ts:132:66)
    at Object.getData (/projects/json-schema-library/src/draft2019-09/methods/getData.ts:174:31)
    at Object.getData (/projects/json-schema-library/src/SchemaNode.ts:305:37)
    at /projects/json-schema-library/src/draft2019-09/methods/getData.ts:200:56
    at Array.forEach (<anonymous>)
    at Object.object (/projects/json-schema-library/src/draft2019-09/methods/getData.ts:193:42)
    at Object.getData (/projects/json-schema-library/src/draft2019-09/methods/getData.ts:175:38)
    at Object.getData (/projects/json-schema-library/src/SchemaNode.ts:305:37)
    at Object.getData (/projects/json-schema-library/src/draft2019-09/methods/getData.ts:161:36)
    at Object.getData (/projects/json-schema-library/src/SchemaNode.ts:305:37)
    at /projects/json-schema-library/src/draft2019-09/methods/getData.ts:126:39
    at Array.forEach (<anonymous>)
    at Object.getData (/projects/json-schema-library/src/draft2019-09/methods/getData.ts:125:27)
    at Object.getData (/projects/json-schema-library/src/SchemaNode.ts:305:37)
    at compileSchema (/projects/json-schema-library/src/compileSchema.ts:74:58)
    at /projects/json-schema-library/src/tests/utils/runTestCases.ts:26:39
    at Array.forEach (<anonymous>)
    at Suite.<anonymous> (/projects/json-schema-library/src/tests/utils/runTestCases.ts:22:22)
    at Object.create (/projects/json-schema-library/node_modules/mocha/lib/interfaces/common.js:148:19)
    at context.describe.context.context (/projects/json-schema-library/node_modules/mocha/lib/interfaces/bdd.js:42:27)
    at runTestCase (/projects/json-schema-library/src/tests/utils/runTestCases.ts:21:5)
    at /projects/json-schema-library/src/tests/utils/runTestCases.ts:103:30
    at Array.forEach (<anonymous>)
    at Suite.<anonymous> (/projects/json-schema-library/src/tests/utils/runTestCases.ts:103:14)
    at Object.create (/projects/json-schema-library/node_modules/mocha/lib/interfaces/common.js:148:19)
    at context.describe.context.context (/projects/json-schema-library/node_modules/mocha/lib/interfaces/bdd.js:42:27)
    at runAllTestCases (/projects/json-schema-library/src/tests/utils/runTestCases.ts:89:5)
    at Object.<anonymous> (/projects/json-schema-library/src/tests/spec/draft2019-09.spec.ts:10:16)
  • with getNode everything works correctly

I don't have a root cause of the error yet

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am unable to reproduce issues based your tests:
On your branch, using

const type = getSchemaType(node.allOf[i].resolveRef(), data);
if (type) {
    return type;
}

works just fine on my end.

getNode does a couple of things in addition to resolving refs, like

  • parsing the json-pointer to find the node we are looking for and
  • creating additional an extended response type for consumption.

We can save unnecessary work and simply use resolveRef. Especially since this is a core function we should be explicit on the actions we use and work on the simpler api.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm... maybe I'm running tests somehow wrongly... I'm just executing yarn test

but I don't know how it could work tbh...
I see quite clear bug in src/draft2019-09/keywords/$ref.ts in resolveRef function (line 72). It has optional path param, that I'm not providing to it (as it's optional and I don't need it). Then on line 75 it's calling resolveRecursiveRef with that optional path as second parameter, which is not optional in resolveRecursiveRef declaration (src/draft2019-09/keywords/$ref.ts:102-103), which obviously results in error on line 108 where it tries to do history.length with history being undefined.
I fixed that bug by providing empty path in src/draft2019-09/keywords/$ref.ts and then I used resolveRef in getSchemaType. Now it should all be good

}

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].getNode("#").node ?? 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].getNode("#").node ?? node.oneOf[i], data);
if (type) {
return type;
}
Expand All @@ -125,7 +129,7 @@ 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].getNode("#").node ?? node.anyOf[i], data);
if (type) {
return type;
}
Expand Down