TypeScript - Define a subset of type - javascript

Say I have a type like so:
interface IAll {
foo: boolean,
bar: Function,
baz: number
}
instead of manually defining all the possible subtypes of IAll, like so:
interface IAll1 {
foo: boolean,
bar: Function,
}
interface IAll2 {
bar: Function,
baz: number
}
interface IAll3 {
foo: boolean,
}
interface IAll4 {
foo: boolean,
}
...etc
and then doing
type IAll = IAll1 | IAll2 | IAll3 ... etc.
Is there a way for TypeScript to statically check whether an object is a subtype or subset of another?
This is useful for some cases where we combine several subtypes or subsets to form a full type.

You can use Partial<T>. This will make all the properties in IAll optional:
type SubsetOfIAll = Partial<IAll>;

Related

Passing a generic type to a function in typescript

Consider example:
interface A {
foo: string;
}
interface B {
foo: string;
}
function a<T>() {
function b(arg: T) {
return arg.foo;
^^^^^^ Property 'foo' does not exist on type 'T'.
}
return b;
}
a<A>();
a<B>();
How to make it work so I dont have to hardcore the type like:
function b(arg: A | B)
So I will be able to pass the type as a generic so if there's like 5 or even more different types I will not have to do it like A | B | C | D | E... but just as a generic?
In order for you to be able to access arg.foo, the compiler must be convinced that, at the bare minimum, arg might actually have a property named foo. So the type of arg must be assignable to something like {foo?: unknown}, where foo is an optional property and the type of the property is the unknown type.
It's not clear from your example if you want to make something stricter than this (say that it definitely has a property named foo, or that the value at that property must be a string). For now, I'm only going to assume that you want to access arg.foo without an error.
If you are using the generic type parameter T as the type of arg, then T must be constrained to something assignable to {foo?: unknown}.
For example:
function a<T extends { foo?: unknown }>() {
function b(arg: T) {
return arg.foo; // okay, no error
}
return b;
}
This saves you from having to write T extends A | B | C | ..., assuming that all of the A, B, C, etc... types are themselves assignable to { foo?: unknown }. Your given A and B types are assignable, so the following works:
a<A>();
a<B>();
Do note that your A and B types from the example are actually identical. TypeScript's type system is structural. Because types A and B have the same structure, they are the same type. It doesn't matter that there are two declarations and two different names:
let aVal: A = { foo: "a" };
let bVal: B = { foo: "b" };
aVal = bVal; // okay
bVal = aVal; // okay
So writing T extends A would be fine if you want the compiler to enforce that foo exists and is a string; it wouldn't stop B from working. A structural type system frees you from having to anticipate all possible named types:
function a2<T extends A>() {
function b(arg: T) {
return arg.foo.toUpperCase();
}
return b;
}
a2<A>(); // okay
a2<B>(); // okay
interface C {
foo: string;
bar: number;
}
a2<C>(); // okay
a2<Date>(); // error
// ~~~~
// Property 'foo' is missing in type 'Date' but required in type 'A'.
If you care about "hardcoding" the name A, you can use an anonymous type like {foo: string} as in T extends {foo: string}, but that doesn't really change much from the type system's perspective, since A and {foo: string} are the same type.
Playground link to code
you can try something like
interface A {
foo: string;
}
interface B extends A {
}
function a<T extends A>() {
function b(arg: T) {
return arg.foo;
}
return b;
}
a<A>();
a<B>();

How to set a functions parameter type to be a property KEY of ONE of a type's properties? Is this even possible?

How does one set a function's parameter type to accept a key of one of a type's properties? Is this even possible?
type A = {
['prop1']: string;
['props2']: boolean;
}
// arg would be either 'prop1' or 'prop2'
const example = (arg: property in A ) => {}
My assumption is that this is not even possible. If so, is there a way to get around this?
Is there a way to declare a type similar to how you can declare an object with an enum? Obviously, this is an object below, but could you do something similar with a type?
Example:
enum Enum {
bold = 'bold',
italic = 'italic',
}
const objWithEnumProperties: {[key in Enum]: any} = {
[Enum.bold]: '',
[Enum.italic]: ''
}
How to set a functions parameter type to be a property KEY of ONE of a types properties? Is this even possible?
Yes, using TypeScript keyof:
type Foo = { bar: string; baz: boolean; }
// arg is 'bar' | 'baz'
const fn = (arg: keyof Foo) => {}
Is there a way to declare a type similar to how you can declare a object with an enum?
You are probably wanting a const object assertion or an interface:
// Usable at runtime
const Foo = {
bar: 'bar',
baz: 'baz'
} as const;
// Only for typing
interface Foo {
bar: string;
baz: boolean;
}

TypeScript nested object interface with known subset of keys

I have a JavaScript object created like this.
const meta = {
baz: null
}
const response = {
foo: 1,
bar: {
foo: 2,
buzz: {
foo: 3,
...meta
},
...meta
},
...meta,
qux: ‘qux’
}
The response object is a plain object that can contain any key. We know the type of keys foo, baz, and qux. We don’t know the other key names but when they do exist, they are objects with a known interface. In fact, the response object is an instance of this interface.
How do I create a TypeScript interface expressing this logic? I want the compiler to allow defining objects like this:
const foo: MyInterface = {
foo: 1,
dynamicKey: {
baz: null
}
}
Index signatures might work if you are okay with this limitation.
This worked for my use case. The trick was to expand the index signature interface with all values. This has a limitation as was pointed out, but it's a much better situation than not having any type checking or autocompletion. My interface now looks like this.
interface MyInterface extends KnownInterface {
[key: string]: MyInterface | KnownInterface[keyof KnownInterface];
}

How to dynamically make an interface key required (or make it optional)

Let's say I have a type
interface Test {
foo: number;
bar?: {
name: string;
};
}
const obj: Test;
// obj.bar is optional right now
But let's say I have doSomething(obj) and this method will make bar required and set it. I want doSomething to then return a new type where bar is required now.
Of course, I want this to be dynamic - as in, I don't want to create interface TestRequired and manually return that type.
Is this possible in Typescript?
The simplest way to change a property from optional to required is to intersect the original interface with a type where the containing only that property, but required. You can easily create such a type using Required and Pick:
declare const obj2: Test & Required<Pick<Test, 'bar'>>;
obj2.bar.name // bar is required so this is ok
Playground Link
Also note that if your function changes an existing object, you can use a custom type assertion to change the original type:
const obj: Test = { foo: 0}
let a = obj.bar // a is { name: string; } | undefined
obj.bar.name // err
function doSomething(o: Test): asserts o is Test & Required<Pick<Test, 'bar'>> {
o.bar = { name: ""}
}
doSomething(obj)
obj.bar.name // ok
Playground Link
You could also make a function that adds bar but a type assertion is necessary:
function addBar(o: Test) {
o.bar = { name: "" }
return o as Test & Required<Pick<Test, 'bar'>> ;
}
let obj = addBar({ foo: 0 });
obj.bar.name;
Playground Link
Of course. You can add or remove the optional modifier by property name or generally:
// StdLib already has the the type Partial<T> and Required<T> to make all properties of T optional or required respectively.
// Makes the named properties of T optional:
type PartialSome<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>
// Makes the named properties of T required:
type RequiredSome<T, K extends keyof T> = T & Required<Pick<T, K>>
So your example might then look like this:
interface Test {
foo: number;
bar?: {
name: string;
};
}
declare function doSomething<T extends {bar?: any}>(obj: T): RequiredSome<T, "bar">;
const obj: Test;
const other = doSomething(obj);
other.bar // bar is required!
Playground Link.

flow types with constant strings, and dependent types

Say I have the following constant string:
export default const FOO = 'FOO'
Say I import this in a flow annotated file like so:
import FOO from '../consts/Foo'
I then have a function:
const example = (foo : string) : {| type: FOO, foo: string |} => {
return {type: FOO, foo: foo}
}
This doesn't typecheck with:
6: const example = (foo : string) : {| type: FOO, foo: string |}=> {
^^^^^^^^^^^^^^ string. Ineligible value used in/as type annotation (did you forget 'typeof'?)
6: const example = (foo : string) : {| type: FOO, foo: string |}=> {
^^^^^^^^^^^^^^ FOO
So my questions are:
1) is it possible to use constants in flow types, how can I reproduce this behaviour?
2) Is it possible to do dependent types in flow? so for example, could I encode, through types, that the string that is returned must be the same string that is passed into the example function?
EDIT: Clarification to part 2: Is it possible to in some way indicate that the foo parameter passed into the example function is in fact the same string as the string at the foo key in the return object? Or to assert that the input and output have the same length (for say a shift cipher function). Or say contain a permutation of the same characters? (for a shuffle).
https://en.wikipedia.org/wiki/Dependent_type
Instead of declaring FOO as a const, declare it as a disjoint union with just one branch:
type FOO = "FOO"
Then your code can be updated like this:
const example = (foo : string) : {| type: FOO, foo: string |} => {
return {type: "FOO", foo: foo}
}
If you use any value besides the exact string literal "FOO" where a FOO is required, then it is a compile error.
If you would prefer to keep your constant, then you'll need to name the type differently, as they would collide. So you could do:
const FOO = "FOO"
type FooType = "FOO";
const example = (foo : string) : {| type: FooType, foo: string |} => {
return {type: FOO, foo: foo}
}
Unfortunately, I can't see a way to avoid duplicating the string literal, because type disjoint union definition syntax only permits literals and types, not variables even if they are constants.
Found a workaround for the issue,
Instead of using flow type inference we can specify the literal type
export default const FOO:'FOO' = 'FOO'
then in the function you can use as
const example = (foo : string) : {| type: typeof FOO, foo: string |} => {
return {type: FOO, foo: foo}
}
Because when you declare the constant type is inferred to be string also I believe flow doesn't support setting type definitions from variables or constants.

Categories

Resources