Kay
Kay

Reputation: 19660

How to make oneOf field nullable with typescript and ajv

I have field called platform which can either be a string or a string[] this field can also be nullable/undefined (not passed).

typescript interface

export interface IEntityLeaderboardQuery {
    rank: string;
    entity_types: string[] | string;
    country?: string | undefined;
    region?: string | undefined;
    start_date: string;
    end_date: string;
    top?: string | undefined;
    platform?: string[] | string | undefined;
}

json schema

export const EntityLeaderboardQuerySchema: JSONSchemaType<IEntityLeaderboardQuery> = {
    type: "object",
    properties: {
        rank: { type: "string" },
        entity_types: {
            oneOf: [
                {
                    type: "string",
                },
                {
                    type: "array",
                    items: { type: "string" },
                },
            ],
        },
        country: { type: "string", nullable: true },
        region: { type: "string", nullable: true },
        start_date: { type: "string" },
        end_date: { type: "string" },
        top: { type: "string", nullable: true },
        platform: {
            oneOf: [
                {
                    type: "string",
                    nullable: true,
                },
                {
                    type: "array",
                    items: { type: "string" },
                    nullable: true,
                },
            ],
        },
    },
    required: ["rank", "entity_types", "start_date", "end_date"],
    additionalProperties: false,
};

As you can see ive attempted to add nullable field to both objects within the oneOf array. However there is still an issue with the types

[ERROR] 11:07:49 ⨯ Unable to compile TypeScript: src/api/leaderboard/entity/entity-leaderboard.interface.ts:43:14 - error TS2322: Type '{ type: "object"; properties: { rank: { type: "string"; }; entity_types: { oneOf: ({ type: "string"; } | { type: "array"; items: { type: "string"; }; })[]; }; country: { type: "string"; nullable: true; }; region: { ...; }; start_date: { ...; }; end_date: { ...; }; top: { ...; }; platform: { ...; }; }; required: ("ra...' is not assignable to type 'UncheckedJSONSchemaType<IEntityLeaderboardQuery, false>'. Type '{ type: "object"; properties: { rank: { type: "string"; }; entity_types: { oneOf: ({ type: "string"; } | { type: "array"; items: { type: "string"; }; })[]; }; country: { type: "string"; nullable: true; }; region: { ...; }; start_date: { ...; }; end_date: { ...; }; top: { ...; }; platform: { ...; }; }; required: ("ra...' is not assignable to type '{ type: "object"; additionalProperties?: boolean | UncheckedJSONSchemaType<unknown, false> | undefined; unevaluatedProperties?: boolean | UncheckedJSONSchemaType<unknown, false> | undefined; ... 7 more ...; maxProperties?: number | undefined; } & { ...; } & { ...; } & { ...; }'. Type '{ type: "object"; properties: { rank: { type: "string"; }; entity_types: { oneOf: ({ type: "string"; } | { type: "array"; items: { type: "string"; }; })[]; }; country: { type: "string"; nullable: true; }; region: { ...; }; start_date: { ...; }; end_date: { ...; }; top: { ...; }; platform: { ...; }; }; required: ("ra...' is not assignable to type '{ type: "object"; additionalProperties?: boolean | UncheckedJSONSchemaType<unknown, false> | undefined; unevaluatedProperties?: boolean | UncheckedJSONSchemaType<unknown, false> | undefined; ... 7 more ...; maxProperties?: number | undefined; }'. The types of 'properties.platform' are incompatible between these types. Type '{ oneOf: ({ type: "string"; nullable: boolean; } | { type: "array"; items: { type: "string"; nullable: boolean; }; nullable: boolean; })[]; }' is not assignable to type '{ $ref: string; } | (UncheckedJSONSchemaType<string | string[] | undefined, false> & { nullable: true; const?: undefined; enum?: readonly (string | string[] | null | undefined)[] | undefined; default?: string | ... 2 more ... | undefined; })'. Type '{ oneOf: ({ type: "string"; nullable: boolean; } | { type: "array"; items: { type: "string"; nullable: boolean; }; nullable: boolean; })[]; }' is not assignable to type '{ type: "array"; items: UncheckedJSONSchemaType<string, false>; contains?: UncheckedPartialSchema | undefined; minItems?: number | undefined; ... 4 more ...; additionalItems?: undefined; } & { ...; } & { ...; } & { ...; }'. Type '{ oneOf: ({ type: "string"; nullable: boolean; } | { type: "array"; items: { type: "string"; nullable: boolean; }; nullable: boolean; })[]; }' is missing the following properties from type '{ type: "array"; items: UncheckedJSONSchemaType<string, false>; contains?: UncheckedPartialSchema | undefined; minItems?: number | undefined; ... 4 more ...; additionalItems?: undefined; }': type, items

43 export const EntityLeaderboardQuerySchema: JSONSchemaType = {

ajv: 8.6.2
typescipt: 4.3.5

https://replit.com/join/ngjynszvjr-kaykhan

Upvotes: 0

Views: 3352

Answers (2)

Salvador Real
Salvador Real

Reputation: 136

It doesn't compile as typescript thinks it's trying to assign an optional type to a non-optional type.

The problem arises since you are using the assignment of nullable: true to the items of oneOf, you must specify outside since the property is using optional chaining ? platform?: string[] | string

platform: {
  nullable: true, //<-- nullable Here
  type: ['string', 'array'], //<-- when using "nullable" you must specify the types
  oneOf: [
    { 
      type: 'string' 
    },
    {
      type: 'array',
      items: { type: 'string' },
    }
  ]
}

If you are using strict mode with Ajv you will probably get a warning:

strict mode: use allowUnionTypes to allow union type keyword at "#/properties/platform" (strictTypes)

Enabling allowUnionTypes should work:

const ajv = new Ajv({ allowUnionTypes: true })

Upvotes: 1

Ryan H.
Ryan H.

Reputation: 7844

I had a similar situation and this worked for me:

oneOf: [
    {
        type: "string",
    },
    {
        type: "array",
        items: { type: "string" },
    },
    {
        type: "object",
        nullable: true,
    },
],

Using { type: "null" } did not work.

Upvotes: 0

Related Questions