Skip to content

Commit

Permalink
Merge pull request #171 from mafintosh/ts-nullable
Browse files Browse the repository at this point in the history
Add nullable types to TypeScript typings
  • Loading branch information
LinusU committed Aug 13, 2018
2 parents 484197f + fad4c91 commit 1712811
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 20 deletions.
54 changes: 34 additions & 20 deletions index.d.ts
@@ -1,32 +1,25 @@
type AnySchema = NullSchema | BooleanSchema | NumberSchema | StringSchema | AnyEnumSchema | AnyArraySchema | AnyObjectSchema | AnyAllOptionalObjectSchema | AnyOneOfSchema
type AnySchema = NullSchema | BooleanSchema | NullableBooleanSchema | NumberSchema | NullableNumberSchema | StringSchema | NullableStringSchema | AnyEnumSchema | AnyArraySchema | AnyNullableArraySchema | AnyObjectSchema | AnyNullableObjectSchema | AnyAllOptionalObjectSchema | AnyNullableAllOptionalObjectSchema | AnyOneOfSchema
type StringKeys<T> = (keyof T) & string

interface NullSchema {
type: 'null'
}
interface NullSchema { type: 'null' }

interface BooleanSchema {
type: 'boolean'
}
interface BooleanSchema { type: 'boolean' }
interface NullableBooleanSchema { type: ('boolean' | 'null')[] }

interface NumberSchema {
type: 'number'
}
interface NumberSchema { type: 'number' }
interface NullableNumberSchema { type: ('number' | 'null')[] }

interface StringSchema {
type: 'string'
}
interface StringSchema { type: 'string' }
interface NullableStringSchema { type: ('string' | 'null')[] }

interface AnyEnumSchema extends EnumSchema<any> {}
interface EnumSchema<Enum> {
enum: Enum[]
}
interface EnumSchema<Enum> { enum: Enum[] }

interface AnyArraySchema extends ArraySchema<AnySchema> {}
interface ArraySchema<ItemSchema extends AnySchema> {
type: 'array'
items: ItemSchema
}
interface ArraySchema<ItemSchema extends AnySchema> { type: 'array', items: ItemSchema }

interface AnyNullableArraySchema extends NullableArraySchema<AnySchema> {}
interface NullableArraySchema<ItemSchema extends AnySchema> { type: ('array' | 'null')[], items: ItemSchema }

interface AnyObjectSchema extends ObjectSchema<Record<string, AnySchema>, string> {}
interface ObjectSchema<Properties extends Record<string, AnySchema>, Required extends StringKeys<Properties>> {
Expand All @@ -36,13 +29,28 @@ interface ObjectSchema<Properties extends Record<string, AnySchema>, Required ex
required: Required[]
}

interface AnyNullableObjectSchema extends NullableObjectSchema<Record<string, AnySchema>, string> {}
interface NullableObjectSchema<Properties extends Record<string, AnySchema>, Required extends StringKeys<Properties>> {
additionalProperties?: boolean
type: ('object' | 'null')[]
properties: Properties
required: Required[]
}

interface AnyAllOptionalObjectSchema extends AllOptionalObjectSchema<Record<string, AnySchema>> {}
interface AllOptionalObjectSchema<Properties extends Record<string, AnySchema>> {
additionalProperties?: boolean
type: 'object'
properties: Properties
}

interface AnyNullableAllOptionalObjectSchema extends NullableAllOptionalObjectSchema<Record<string, AnySchema>> {}
interface NullableAllOptionalObjectSchema<Properties extends Record<string, AnySchema>> {
additionalProperties?: boolean
type: ('object' | 'null')[]
properties: Properties
}

interface AnyOneOfSchema { oneOf: AnySchema[] }

interface ArrayFromSchema<ItemSchema extends AnySchema> extends Array<TypeFromSchema<ItemSchema>> {}
Expand All @@ -55,11 +63,17 @@ type TypeFromSchema<Schema extends AnySchema> = (
Schema extends EnumSchema<infer Enum> ? Enum
: Schema extends NullSchema ? null
: Schema extends BooleanSchema ? boolean
: Schema extends NullableBooleanSchema ? (boolean | null)
: Schema extends NumberSchema ? number
: Schema extends NullableNumberSchema ? (number | null)
: Schema extends StringSchema ? string
: Schema extends NullableStringSchema ? (string | null)
: Schema extends ArraySchema<infer ItemSchema> ? ArrayFromSchema<ItemSchema>
: Schema extends NullableArraySchema<infer ItemSchema> ? (ArrayFromSchema<ItemSchema> | null)
: Schema extends ObjectSchema<infer Properties, infer Required> ? ObjectFromSchema<Properties, Required>
: Schema extends NullableObjectSchema<infer Properties, infer Required> ? (ObjectFromSchema<Properties, Required> | null)
: Schema extends AllOptionalObjectSchema<infer Properties> ? ObjectFromSchema<Properties, never>
: Schema extends NullableAllOptionalObjectSchema<infer Properties> ? (ObjectFromSchema<Properties, never> | null)
: never
)

Expand Down
65 changes: 65 additions & 0 deletions test/typings.ts
Expand Up @@ -343,3 +343,68 @@ if (overengineeredColorValidator(input)) {
if (input !== 'yellow' && input !== 'cream' && input !== 'red' && input !== 'pink' && input !== 'green' && input !== 'olive' && input !== 'blue') assertType<'teal'>(input)
if (input !== 'cream' && input !== 'red' && input !== 'pink' && input !== 'green' && input !== 'olive' && input !== 'blue' && input !== 'teal') assertType<'yellow'>(input)
}

const nullableStringValidator = createValidator({
type: ['string', 'null']
})

if (nullableStringValidator(input)) {
if (typeof input !== 'object') assertType<string>(input)
if (typeof input !== 'string') assertType<null>(input)
}

const nullableNameValidator = createValidator({
type: 'object',
properties: {
name: { type: ['string', 'null'] }
},
required: [
'name'
]
})

if (nullableNameValidator(input)) {
if (typeof input.name !== 'object') assertType<string>(input.name)
if (typeof input.name !== 'string') assertType<null>(input.name)
}

const nullableInventoryValidator = createValidator({
type: 'object',
properties: {
inventory: {
type: ['array', 'null'],
items: { type: 'string' }
}
},
required: [
'inventory'
]
})

if (nullableInventoryValidator(input)) {
if (input.inventory === null) assertType<null>(input.inventory)
if (input.inventory !== null) assertType<string[]>(input.inventory)
}

const nullableParentValidator = createValidator({
type: 'object',
properties: {
parent: {
type: ['object', 'null'],
properties: {
name: { type: 'string' }
},
required: [
'name' as 'name'
]
}
},
required: [
'parent'
]
})

if (nullableParentValidator(input)) {
if (input.parent === null) assertType<null>(input.parent)
if (input.parent !== null) assertType<string>(input.parent.name)
}

0 comments on commit 1712811

Please sign in to comment.