Skip to content

definitions for schemas w/ typed additionalProperties are not compatible w/ strictNullChecks #235

@G-Rath

Description

@G-Rath

This is all based around an attempt to resolve issues like #201, #210, etc

Currently, j2t generates definitions that are not strict mode compatible:

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "properties": {
    "myProp": {
      "type": "string"
    }
  },
  "additionalProperties": {
    "type": "string"
  }
}

results in

/* tslint:disable */
/**
 * This file was automatically generated by json-schema-to-typescript.
 * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
 * and run json-schema-to-typescript to regenerate this file.
 */

export interface Foo {
  myProp?: string;
  [k: string]: string;
}

This isn't valid under strict, specifically due to strictNullChecks.

To be valid, | undefined needs to be explicitly added.
The "downside" to this is that you have to guard against undefined:

interface Mx {
  [k: string]: string | undefined;
}

const o: Mx = {};

o.myProp.toUpperCase();
// ^^ Error:(53, 1) TS2532: Object is possibly 'undefined'.

for (const key in o) {
  o[key].toUpperCase();
  // ^^ Error:(57, 3) TS2532: Object is possibly 'undefined'.
}

Personally, I actually think this is for the better anyway, given that since you're not requiring myProp, it could very well be undefined.

This isn't actually a breaking change, since this will only affect code that has strictNullChecks, but if strictNullChecks is on, generated definitions would stop compiling anyway, making it a catch-22 😄


The implementation on how to support this is the big question - For package.json, the fix is easy: use tsType, but the problem w/ this is it's a complete override, which means its brittle:

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "properties": {
    "myProp": {
      "type": "string"
    }
  },
  "definitions": {
    "myDef": {
      "type": "string"
    }
  },
  "additionalProperties": {
    "$ref": "#/definitions/myDef",
    "tsType": "undefined"
  }
}

Desired:

export type MyDef = string;

export interface Foo2 {
  myProp?: string;
  [k: string]: MyDef | undefined;
}

Actual:

export interface Foo {
  myProp?: string;
  [k: string]: undefined;
}

Hence, ideally, what we need is a way to append types; I don't really see much of a use-case outside of appending undefined, so I'd be happy w/ something like "tsCanBeUndefined": true.

Alternatively, a solution that is a bit nicer but more breaking (IMO) would be whenever an interface is generated w/ an index type (other than any), to add | undefined if the interface has any optional properties.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions