-1

Using Ajv on NodeJS, I can't seem to get a circular TypeScript structure to be defined in Ajv.

A simple circular structure works (see "parent"), but I can't get an array to work (see "children").

Uncomment the "children" lines to see the error.

'use strict'
import Ajv, {JSONSchemaType} from "ajv"

type MyType = {
  name : string,
  parent? : MyType,
  //children? : Array<MyType>
};

const MySchema : JSONSchemaType<MyType> = {
  type:"object",
  properties:{
    name:{type:"string"},
    parent:{$ref:"#"},
    //children:{type:"array",items:{$ref:"#"}}
  },
  required:["name"]
}

let pnt:MyType = { name : "Parent" }
let c1:MyType = { name : "Child1" }
let c2:MyType = { name : "Child2" }
let c3:MyType = { name : "Child3" }

let node:MyType = { name : "Node1", parent : pnt }

//node.children = [c1,c2,c3];

// Validate
const ajv = new Ajv({discriminator:true,allErrors:true,allowUnionTypes:true})
const validate = ajv.compile(MySchema)

console.log(validate(pnt));
console.log(validate(c1));
console.log(validate(c2));
console.log(validate(c3));
console.log(validate(node));

The error, when the "children" lines are uncommented

Type '{ type: "object"; properties: { name: { type: "string"; }; parent: { $ref: string; }; children: { type: "array"; items: { $ref: string; }; }; }; required: "name"[]; }' is not assignable to type 'UncheckedJSONSchemaType<MyType, false>'. The types of 'properties.children' are incompatible between these types. Type '{ type: "array"; items: { $ref: string; }; }' is not assignable to type '{ $ref: string; } | (UncheckedJSONSchemaType<MyType[] | undefined, false> & { nullable: true; const?: null | undefined; enum?: readonly (MyType[] | null | undefined)[] | undefined; default?: MyType[] | ... 1 more ... | undefined; })'. Types of property 'items' are incompatible. Type '{ $ref: string; }' is not assignable to type 'UncheckedJSONSchemaType<MyType, false>'. Type '{ $ref: string; }' is not assignable to type '{ type: "object"; additionalProperties?: boolean | UncheckedJSONSchemaType<unknown, false> | undefined; unevaluatedProperties?: boolean | UncheckedJSONSchemaType<unknown, false> | undefined; ... 7 more ...; maxProperties?: number | undefined; } & { ...; } & { ...; } & { ...; }'. Property 'type' is missing in type '{ $ref: string; }' but required in type '{ type: "object"; additionalProperties?: boolean | UncheckedJSONSchemaType<unknown, false> | undefined; unevaluatedProperties?: boolean | UncheckedJSONSchemaType<unknown, false> | undefined; ... 7 more ...; maxProperties?: number | undefined; }'.

J. McNerney
  • 576
  • 4
  • 15
  • Does adding `"$id"` property to the schema fix the issue? – Biller Builder Sep 25 '22 at 19:18
  • I was unable to get $id to help out. My best guess is it's a limitation of the JSONSchemaType library. Objects seem to work fine, no matter how many levels nested they are, but arrays do not work. – J. McNerney Oct 05 '22 at 20:39

1 Answers1

0

As a work around, declaring the Ajv schema's type as "any" lets TypeScript not error.

The TypeScript "types" are still fully defined, so TypeScript's pre-execution checks are still in place. Only the "types" for the Ajv schema declarations are not 100%, since using the "as any". Ajv is used for execution-time verification of data, and this function has not changed by adding the "as any", so this work-around looks reasonable.

const MySchema : JSONSchemaType<MyType> = {
  .......
} as any; // <----- added type declaration

The full code

'use strict'
import Ajv, {JSONSchemaType} from "ajv"

type MyType = {
  name : string,
  parent? : MyType,
  children? : Array<MyType>
};

const MySchema : JSONSchemaType<MyType> = {
  type:"object",
  properties:{
    name:{type:"string"},
    parent:{$ref:"#"},
    children:{type:"array",items:{$ref:"#"}}
  },
  required:["name"]
} as any; // <----- added type declaration

let pnt:MyType = { name : "Parent" }
let c1:MyType = { name : "Child1" }
let c2:MyType = { name : "Child2" }
let c3:MyType = { name : "Child3" }

let node:MyType = { name : "Node1", parent : pnt }

node.children = [c1,c2,c3];

// Validate
const ajv = new Ajv({discriminator:true,allErrors:true,allowUnionTypes:true})
const validate = ajv.compile(MySchema)

console.log(validate(pnt));
console.log(validate(c1));
console.log(validate(c2));
console.log(validate(c3));
console.log(validate(node));
J. McNerney
  • 576
  • 4
  • 15