question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

TS is not able to infer that an optional field might be undefined

See original GitHub issue

What version of Ajv are you using?

v8.11.2 ( latest )

Your typescript code

    import Ajv, { JSONSchemaType } from "ajv";

    interface MyType {
    	myProp?: OtherType;
    }
    
    interface OtherType {
    	foo: string;
    	bar: number;
    }
    
    const otherSchema: JSONSchemaType<OtherType> = {
    	type: 'object',
    	properties: {
    		foo: { type: 'string', minLength: 1 },
    		bar: { type: 'number' },
    	},
    	required: ['foo', 'bar'],
    	additionalProperties: false
    };
    
    const mySchema: JSONSchemaType<MyType> = {
    	type: 'object',
    	properties: {
    		myProp: otherSchema,
    	},
    	required: [],
    	additionalProperties: false,
    };

Typescript compiler error messages

app.ts:22:7 - error TS2322: Type '{ type: "object"; properties: { myProp: { type: "object"; additionalProperties?: boolean | UncheckedJSONSchemaType<unknown, false> | undefined; unevaluatedProperties?: boolean | ... 1 more ... | undefined; ... 7 more ...; maxProperties?: number | undefined; } & { ...; } & { ...; } & { ...; }; }; required: never[]; a...' is not assignable to type 'UncheckedJSONSchemaType<MyType, false>'.
  The types of 'properties.myProp' are incompatible between these types.
    Type '{ type: "object"; additionalProperties?: boolean | UncheckedJSONSchemaType<unknown, false> | undefined; unevaluatedProperties?: boolean | UncheckedJSONSchemaType<unknown, false> | undefined; ... 7 more ...; maxProperties?: number | undefined; } & { ...; } & { ...; } & { ...; }' is not assignable to type '{ $ref: string; } | (UncheckedJSONSchemaType<OtherType | undefined, false> & { nullable: true; const?: null | undefined; enum?: readonly (OtherType | null | undefined)[] | undefined; default?: OtherType | ... 1 more ... | undefined; })'.
      Type '{ type: "object"; additionalProperties?: boolean | UncheckedJSONSchemaType<unknown, false> | undefined; unevaluatedProperties?: boolean | UncheckedJSONSchemaType<unknown, false> | undefined; ... 7 more ...; maxProperties?: number | undefined; } & { ...; } & { ...; } & { ...; }' is not assignable to type '{ $ref: string; }'.
        Types of property '$ref' are incompatible.
          Type 'string | undefined' is not assignable to type 'string'.
            Type 'undefined' is not assignable to type 'string'.

Describe the change that should be made to address the issue?

I’m not sure but I think this error occurs because TS doesn’t know that myProp in mySchema might be undefined. The required array does not contain myProp.

I could try to assign { nullable: true } to myProp but there is no field for schema so this is not possible

myProp: { schema: otherSchema, nullable: true }

Are you going to resolve the issue?

Unfortunately I don’t know how.

Issue Analytics

  • State:open
  • Created 9 months ago
  • Comments:5 (2 by maintainers)

github_iconTop GitHub Comments

1reaction
epoberezkincommented, Dec 19, 2022

I am not an authority on that… There is usually some difference between the types that are convenient for your application (e.g., not allow nulls, as it’s an object in JS) and the types that are convenient for the API, as it is just JSON and other languages call it… So in some cases it’s convenient to have types that fit API definition (that is to allow null) and treat it in the application correctly, and in some cases you may end up having some API-facing types that get “parsed” (transformed) into some more narrow internal types.

I very much agree with “parse don’t validate” philosophy (which is somewhat ironic), so I’ve been using JTD for quite some time, and not JSON schema, as it doesn’t support “parsing” approach, being mostly incompatible with type systems…

There is definitely an argument to challenge that decision (that optional properties must be nullable in JSON) and either revise it in the next major version or make it optional, but the number of various options is very large already, so not sure…

0reactions
Tofandelcommented, Dec 21, 2022

I also find it weird that nullable is required for optional fields, I’ve been migrating to TS and an otherwise very long valid schema, I need to add nullable almost everywhere or type or empty required in some very specific places that you can only find in a very hard to read error message…

There is the same issue with the required field on objects

type Options = {
  renderer?: Record<string, unknown> // It's a class but simpliying for example
}

// This fails TS
/* TS2322: Type '{ properties: { renderer: { type: "object"; description: string; }; }; }' is not assignable to type 
'GenericSchema<Options>'.   Type '{ properties: { renderer: { type: "object"; description: 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; } & { ...; } & { ...; } & { ...; } & Extend'.     Property 'type' is missing in type '{ properties: { renderer: { type: 
"object"; description: string; }; }; }' but required in type '{ type: "object"; additionalProperties?: boolean | 
UncheckedJSONSchemaType<unknown, false> | undefined; unevaluatedProperties?: boolean | 
UncheckedJSONSchemaType<unknown, false> | undefined; ... 7 more ...; maxProperties?: number | undefined; }'. */
export const schema1: JSONSchema<Options> = {
  properties: {
    renderer: {
      type: 'object',
      description: 'The renderer class or instance to use',
    },
  },
}

// Only way to get it to pass
export const schema2: JSONSchema<Options> = {
  type: 'object',
  properties: {
    renderer: {
      type: 'object',
      description: 'The renderer class or instance to use',
      nullable: true,
      required: [],
    },
  },
}

But clearly those properties are not required as this doesn’t seem to change anything in the behavior of the validation (eg: required is by default empty, nullable is by default true)

Edit - another one: using instanceOf requires the type: 'object' to be defined, which should be inferred by the usage of instanceOf

Makes it too cumbersome to convert to TS

Read more comments on GitHub >

github_iconTop Results From Across the Web

Why doesn't typescript undefined type behave same as ...
It won't compile because property bar is missing. But we say that it can be undefined, which will work if we explicitly set...
Read more >
How to Deal with Optional Things and "Undefined" in TypeScript
Working with JavaScript means working with undefined . It's a standard way to say, “This thing you asked for doesn't exist.”.
Read more >
TypeScript - Sequelize
'CreationOptional' is a special type that marks the field as optional ... Note that attributes that accept null , or undefined do not...
Read more >
Distinguish missing and undefined · Issue #13195 - GitHub
I have met JavaScript libraries that used 'prop' in options or options.hasOwnProperty('prop') to check for the presence of an option, and then ...
Read more >
Documentation - TypeScript 2.0
TypeScript has two special types, Null and Undefined, that have the values null and undefined respectively. Previously it was not possible to explicitly ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found