openapi-typescript icon indicating copy to clipboard operation
openapi-typescript copied to clipboard

Required object field with additionalProperties does not generate a required TypeScript field

Open sgrimm opened this issue 2 years ago • 11 comments

Description

If a payload has a required object field whose additionalProperties specifies a schema for the values, the generated type shouldn't allow undefined values.

OpenAPI

The real schema where I'm hitting this is here but this toy schema demonstrates the problem.

openapi: 3.0.1
components:
  schemas:
    Payload:
      type: object
      required:
        - children
      properties:
        children:
          type: object
          additionalProperties:
            type: object
            required:
              - field
            properties:
              field: string

Generated TS

export type paths = Record<string, never>;

export type webhooks = Record<string, never>;

export interface components {
  schemas: {
    Payload: {
      children: {
        [key: string]: {
          field: string;
        } | undefined;
      };
    };
  };
  responses: never;
  parameters: never;
  requestBodies: never;
  headers: never;
  pathItems: never;
}

export type external = Record<string, never>;

export type operations = Record<string, never>;

Expected TS

Same as above but without the | undefined.

export type paths = Record<string, never>;

export type webhooks = Record<string, never>;

export interface components {
  schemas: {
    Payload: {
      children: {
        [key: string]: {
          field: string;
        };
      };
    };
  };
  responses: never;
  parameters: never;
  requestBodies: never;
  headers: never;
  pathItems: never;
}

export type external = Record<string, never>;

export type operations = Record<string, never>;

Checklist

  • [X] My OpenAPI schema passes a validator
  • [X] I’m willing to open a PR for this (see CONTRIBUTING.md)

sgrimm avatar Dec 12 '22 23:12 sgrimm

Any update on this? I've been bumping up against it, and right now am just grepping occurrences in the output TS, which is obviously sub-optimal.

acnebs avatar Jun 20 '23 15:06 acnebs

This behavior was a decision made to improve type safety because most users don’t have noUncheckedIndexAccess enabled, and this library can’t check that’s set. But when dealing with additionalProperties, their whole nature is “this key may either exist or not” so the | undefined is necessary in order to not have runtime bugs (if the key was known, and required, it would be part of the schema).

This behavior won’t be changed, but perhaps this a flag could be added if you’re assuming ownership of object index access and are confident you’re handling all the null cases properly.

drwpow avatar Jun 20 '23 15:06 drwpow

Understood, thanks for the explanation. My issue is that I'm actually only using the resulting object as an iterator, so of course noUncheckedIndexAccess is irrelevant because I'm not trying to access by index ever.

It would be nice if Typescript was smart enough to somehow handle both (know that the value is defined when iterating over the object directly, but warn about possibly undefined when accessing by key), but I guess not?

acnebs avatar Jun 20 '23 16:06 acnebs

My issue is that I'm actually only using the resulting object as an iterator, so of course noUncheckedIndexAccess is irrelevant because I'm not trying to access by index ever. … It would be nice if Typescript was smart enough to somehow handle both

Oh yup—when you’re letting the object list its own keys you’re right this doesn’t affect you. But in all other instances, this is unsafe, and like you pointed out, TS can’t tell the difference. This behavior is actually my biggest gripe with TypeScript 😄.

drwpow avatar Jun 20 '23 16:06 drwpow

I don’t think we’d ever add a flag that disallows some part of your schema; IMO that gets into linting and the Redocly CLI is so good I’d probably just encourage users to take advantage of that if desired (you can easily write your own lint rules like this)

However, I am starting to regret departing from default TypeScript behavior just because “that’s the way it should be.” On the one hand, there is a lot of gray area when converting OpenAPI → TypeScript (more than I imagined). Because they are different languages with different rules, more is a “best approximation” than most people realize. However, generating non-standard TS like this may come with more downsides than upsides. Even if it does help type safety for some, the end result usually does not behave as expected, especially when you get into complex composition (oneOf / anyOf / allOf) it tends to backfire. Like it or hate it, the OpenAPI spec is what it is, and TS is what it is. And this library should never impose on either’s design.

Though if we did remove the | undefined behavior for additionalProperties and just let TS do its thing, would that be a breaking change? 🤔 Probably in most instances I don’t think it would “break” users’ behavior; it just may be more lax on some checks in place. But anyway, I am starting to think that changing this behavior to remove | undefined and just encouraging the use of noUncheckedIndexAccess in the docs would be a better experience for most.

drwpow avatar Jul 31 '23 17:07 drwpow

@RWOverdijk ah, I see. Yes this original issue was over | undefined being appended to additionalProperties. Optional properties may just be a missing required: true on your schema? Feel free to open another issue or discussion with your schema on what you’re expecting vs what you’re getting.

drwpow avatar Jul 31 '23 18:07 drwpow

@drwpow I'll just crawl back in shame. It was a PEBKAC. I'll clear out my other issues.

RWOverdijk avatar Aug 01 '23 08:08 RWOverdijk

I support the removal of | undefined and encouraging the use of noUncheckedIndexAccess, as yes that is what I need to do everywhere else for this behavior to be as correct as possible.

I don't think it's really a breaking change. Anyone that is already handling the undefined case won't be broken by this change.

acnebs avatar Aug 01 '23 10:08 acnebs

The new release v6.5.0 features the removal of | undefined in certain scenarios, but not all. I’m going to be revisiting the existing ones this week.

drwpow avatar Aug 14 '23 17:08 drwpow

Bump, any update on this? :)

march08 avatar Jul 03 '24 17:07 march08

Revisiting upgrading to v7 today, still seems to be an issue.

psychedelicious avatar Jul 24 '24 07:07 psychedelicious