how to convert all optional object fields into non-optional - javascript

I have the following Object type in Flow:
type Identity = {
name: string,
lastName: string
};
type ExtraInfo = {
favoriteColor: string,
age: number
};
type Person = {
a: Identity,
b?: ExtraInfo
};
Is there a way to define from Person a type without any maybe type?:
// final result, but I don't want to redeclare all the properties, I want to derive from the Person type
type PersonWithExtraInfo = {
a: Identity,
b: ExtraInfo
};
I thought that by applying this would work, but apparently, not. Maybe becuase it's not a maybe type what's defined, but an optional property:
type NonMaybeType = <Type>(Type) => $NonMaybeType<Type>;
export type PersonWithExtraInfo = $ObjMap<Person, NonMaybeType>;

Related

Is there a way to show a TypeScript error when an unknown property is passed in an object to function? [duplicate]

Here is my code
async getAll(): Promise<GetAllUserData[]> {
return await dbQuery(); // dbQuery returns User[]
}
class User {
id: number;
name: string;
}
class GetAllUserData{
id: number;
}
getAll function returns User[], and each element of array has the name property, even if its return type is GetAllUserData[].
I want to know if it is possible "out of the box" in TypeScript to restrict an object only to properties specified by its type.
I figured out a way, using built-in types available since TypeScript version 3, to ensure that an object passed to a function does not contain any properties beyond those in a specified (object) type.
// First, define a type that, when passed a union of keys, creates an object which
// cannot have those properties. I couldn't find a way to use this type directly,
// but it can be used with the below type.
type Impossible<K extends keyof any> = {
[P in K]: never;
};
// The secret sauce! Provide it the type that contains only the properties you want,
// and then a type that extends that type, based on what the caller provided
// using generics.
type NoExtraProperties<T, U extends T = T> = U & Impossible<Exclude<keyof U, keyof T>>;
// Now let's try it out!
// A simple type to work with
interface Animal {
name: string;
noise: string;
}
// This works, but I agree the type is pretty gross. But it might make it easier
// to see how this works.
//
// Whatever is passed to the function has to at least satisfy the Animal contract
// (the <T extends Animal> part), but then we intersect whatever type that is
// with an Impossible type which has only the keys on it that don't exist on Animal.
// The result is that the keys that don't exist on Animal have a type of `never`,
// so if they exist, they get flagged as an error!
function thisWorks<T extends Animal>(animal: T & Impossible<Exclude<keyof T, keyof Animal>>): void {
console.log(`The noise that ${animal.name.toLowerCase()}s make is ${animal.noise}.`);
}
// This is the best I could reduce it to, using the NoExtraProperties<> type above.
// Functions which use this technique will need to all follow this formula.
function thisIsAsGoodAsICanGetIt<T extends Animal>(animal: NoExtraProperties<Animal, T>): void {
console.log(`The noise that ${animal.name.toLowerCase()}s make is ${animal.noise}.`);
}
// It works for variables defined as the type
const okay: NoExtraProperties<Animal> = {
name: 'Dog',
noise: 'bark',
};
const wrong1: NoExtraProperties<Animal> = {
name: 'Cat',
noise: 'meow'
betterThanDogs: false, // look, an error!
};
// What happens if we try to bypass the "Excess Properties Check" done on object literals
// by assigning it to a variable with no explicit type?
const wrong2 = {
name: 'Rat',
noise: 'squeak',
idealScenarios: ['labs', 'storehouses'],
invalid: true,
};
thisWorks(okay);
thisWorks(wrong1); // doesn't flag it as an error here, but does flag it above
thisWorks(wrong2); // yay, an error!
thisIsAsGoodAsICanGetIt(okay);
thisIsAsGoodAsICanGetIt(wrong1); // no error, but error above, so okay
thisIsAsGoodAsICanGetIt(wrong2); // yay, an error!
Typescript can't restrict extra properties
Unfortunately this isn't currently possible in Typescript, and somewhat contradicts the shape nature of TS type checking.
Answers in this thread that relay on the generic NoExtraProperties are very elegant, but unfortunately they are unreliable, and can result in difficult to detect bugs.
I'll demonstrate with GregL's answer.
// From GregL's answer
type Impossible<K extends keyof any> = {
[P in K]: never;
};
type NoExtraProperties<T, U extends T = T> = U & Impossible<Exclude<keyof U, keyof T>>;
interface Animal {
name: string;
noise: string;
}
function thisWorks<T extends Animal>(animal: T & Impossible<Exclude<keyof T, keyof Animal>>): void {
console.log(`The noise that ${animal.name.toLowerCase()}s make is ${animal.noise}.`);
}
function thisIsAsGoodAsICanGetIt<T extends Animal>(animal: NoExtraProperties<Animal, T>): void {
console.log(`The noise that ${animal.name.toLowerCase()}s make is ${animal.noise}.`);
}
const wrong2 = {
name: 'Rat',
noise: 'squeak',
idealScenarios: ['labs', 'storehouses'],
invalid: true,
};
thisWorks(wrong2); // yay, an error!
thisIsAsGoodAsICanGetIt(wrong2); // yay, an error!
This works if at the time of passing an object to thisWorks/thisIsAsGoodAsICanGet TS recognizes that the object has extra properties. But in TS if it's not an object literal, a value can always have extra properties:
const fun = (animal:Animal) =>{
thisWorks(animal) // No Error
thisIsAsGoodAsICanGetIt(animal) // No Error
}
fun(wrong2) // No Error
So, inside thisWorks/thisIsAsGoodAsICanGetIt you can't trust that the animal param doesn't have extra properties.
Solution
Simply use pick (Lodash, Ramda, Underscore).
interface Narrow {
a: "alpha"
}
interface Wide extends Narrow{
b: "beta"
}
const fun = (obj: Narrow) => {
const narrowKeys = ["a"]
const narrow = pick(obj, narrowKeys)
// Even if obj has extra properties, we know for sure that narrow doesn't
...
}
Typescript uses structural typing instead of nominal typing to determine type equality. This means that a type definition is really just the "shape" of a object of that type. It also means that any types which shares a subset of another type's "shape" is implicitly a subclass of that type.
In your example, because a User has all of the properties of GetAllUserData, User is implicitly a subtype of GetAllUserData.
To solve this problem, you can add a dummy property specifically to make your two classes different from one another. This type of property is called a discriminator. (Search for discriminated union here).
Your code might look like this. The name of the discriminator property is not important. Doing this will produce a type check error like you want.
async function getAll(): Promise<GetAllUserData[]> {
return await dbQuery(); // dbQuery returns User[]
}
class User {
discriminator: 'User';
id: number;
name: string;
}
class GetAllUserData {
discriminator: 'GetAllUserData';
id: number;
}
I don't think it's possible with the code structure you have. Typescript does have excess property checks, which sounds like what you're after, but they only work for object literals. From those docs:
Object literals get special treatment and undergo excess property checking when assigning them to other variables, or passing them as arguments.
But returned variables will not undergo that check. So while
function returnUserData(): GetAllUserData {
return {id: 1, name: "John Doe"};
}
Will produce an error "Object literal may only specify known properties", the code:
function returnUserData(): GetAllUserData {
const user = {id: 1, name: "John Doe"};
return user;
}
Will not produce any errors, since it returns a variable and not the object literal itself.
So for your case, since getAll isn't returning a literal, typescript won't do the excess property check.
Final Note: There is an issue for "Exact Types" which if ever implemented would allow for the kind of check you want here.
Following up on GregL's answer, I'd like to add support for arrays and make sure that if you've got one, all the objects in the array have no extra props:
type Impossible<K extends keyof any> = {
[P in K]: never;
};
export type NoExtraProperties<T, U extends T = T> = U extends Array<infer V>
? NoExtraProperties<V>[]
: U & Impossible<Exclude<keyof U, keyof T>>;
Note: The type recursion is only possible if you've got TS 3.7 (included) or above.
The accepted answer, with a discriminator, is right. TypeScript uses structural typing instead of nominal typing. It means that the transpiler will check to see if the structure match. Since both classes (could be interface or type) has id of type number it matches, hence interchangeable (this is true one side since User is having more properties.
While this might be good enough, the issue is that at runtime the returned data from your method getAll will contains the name property. Returning more might not be an issue, but could be if you are sending back the information somewhere else.
If you want to restrict the data to only what is defined in the class (interface or type), you have to build or spread a new object manually. Here is how it can look for your example:
function dbQuery(): User[] {
return [];
}
function getAll(): GetAllUserData[] {
const users: User[] = dbQuery();
const usersIDs: GetAllUserData[] = users.map(({id}) => ({id}));
return usersIDs;
}
class User {
id: number;
name: string;
}
class GetAllUserData {
id: number;
}
Without going with the runtime approach of pruning the fields, you could indicate to TypeScript that both classes are different with a private field. The code below won't let you return a User when the return type is set to GetAllUserData
class User {
id: number;
name: string;
}
class GetAllUserData {
private _unique: void;
id: number;
}
function getAll(): GetAllUserData[] {
return dbQuery(); // Doesn't compile here!
}
I found this another workaround:
function exactMatch<A extends C, B extends A, C = B>() { }
const a = { a: "", b: "", c: "" }
const b = { a: "", b: "", c: "", e: "" }
exactMatch<typeof a, typeof b>() //invalid
const c = { e: "", }
exactMatch<typeof a, typeof c>() //invalid
const d = { a: "", b: "", c: "" }
exactMatch<typeof a, typeof d>() //valid
const e = {...a,...c}
exactMatch<typeof b, typeof e>() //valid
const f = {...a,...d}
exactMatch<typeof b, typeof f>() //invalid
See the original Post
Link to Playground
As an option, you can go with a hack:
const dbQuery = () => [ { name: '', id: 1}];
async function getAll(): Promise<GetAllUserData[]> {
return await dbQuery(); // dbQuery returns User[]
}
type Exact<T> = {[k: string | number | symbol]: never} & T
type User = {
id: number;
name: string;
}
type GetAllUserData = Exact<{
id: number;
}>
Error this produces:
Type '{ name: string; id: number; }[]' is not assignable to type '({ [k: string]: never; [k: number]: never; [k: symbol]: never; } & { id: number; })[]'.
Type '{ name: string; id: number; }' is not assignable to type '{ [k: string]: never; [k: number]: never; [k: symbol]: never; } & { id: number; }'.
Type '{ name: string; id: number; }' is not assignable to type '{ [k: string]: never; [k: number]: never; [k: symbol]: never; }'.
Property 'name' is incompatible with index signature.
Type 'string' is not assignable to type 'never'.
When using types instead of interfaces, the property are restricted. At least in the IDE (no runtime check).
Example
type Point = {
x: number;
y: number;
}
const somePoint: Point = {
x: 10,
y: 22,
z: 32
}
It throws :
Type '{ x: number; y: number; z: number; }' is not assignable to type 'Point'. Object literal may only specify known properties, and 'z' does not exist in type 'Point'.
I think types are good for defining closed data structures, compared to interfaces. Having the IDE yelling (actually the compiler) when the data does not match exactly the shape is already a great type guardian when developping

TypeScript: Create typed Record without explicitly defining the keys

What I'd like to do is to create an object which is a Record of a certain interface and have TypeScript be able to infer the keys based on what's in my object. I've tried a few things, none of which do exactly what I'm looking for.
interface Person {
firstName:string
}
const peopleObj = {
Jason: {
firstName: "Jason"
},
Tim: {
firstName: "Tim"
}
} as const;
console.log(peopleObj);
Here, if you look at peopleObj, TypeScript knows the exact keys because of the as const. The problem here is I'm not enforcing each object to be a Person. So I tried this next:
const peopleObj: Record<string, Person> = {
Jason: {
firstName: "Jason"
},
Tim: {
firstName: "Tim"
}
} as const;
Here, each object has to be a Person because of the Record defined, but TypeScript loses its ability to know all of the keys because now they are just string instead of the constants 'Jason' | 'Tim', and this is the crux of the issue. I know I could explicitly use 'Jason' | 'Tim' in place of my string type, but this is a fairly large object in real life and updating that type explicitly every time I add to it or remove from it is getting to be tedious.
Is there a way to have the best of both worlds, where I can have TypeScript infer the keys in the object just based solely on what's in the object? I have found a way, although it's not super clean and I feel like there's likely a better way:
interface Person {
firstName:string
}
type PeopleType<T extends string> = Record<T, Person>;
const peopleObj: Record<string, Person> = {
Jason: {
firstName: "Jason"
},
Tim: {
firstName: "Tim"
}
} as const;
const People:Record<keyof typeof peopleObj, Person> = peopleObj;
console.log(People.Jason);
Your third method doesn't actually work I believe - as you can access People.foo without error. This is because when you construct peopleObj as Record<string, Person>, its type is now that. You then do keyof Record<string, Person>, which evaluates to string.
The only way I'm aware of to achieve this is via using a function with generics. This allows you to apply a constraint on the input parameter, and then return the original type.
const createPeople = <T extends Record<string, Person>>(people: T) => people;
const myPeople = createPeople({
Jason: {
firstName: "Jason"
},
Tim: {
firstName: "Tim"
}
});
console.log(myPeople.Jason);
console.log(myPeople.foo); // error
You have a catch 22 situation otherwise - i.e - I want to enforce that my keys are of type Person, but I don't know what my keys are.
One other way that you may prefer - which is basically the same thing but without the function:
interface Person {
firstName:string
}
// Force evaluation of type to expanded form
type EvaluateType<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
type PeopleType<T extends Record<string, Person>> = EvaluateType<{[key in keyof T]: Person }>;
const peopleLookup = {
Jason: {
firstName: "Jason"
},
Tim: {
firstName: "Tim"
}
};
const people: PeopleType<typeof peopleLookup> = peopleLookup;
console.log(people.Jason);
console.log(people.foo);
I'm expanding on #mbdavis's answer because there seems to be an additional constraint, which is that the key for each Person in the record is that Person's firstName property. This allows us to use mapped types.
A PeopleRecord should be an object such that for every key name, the value is either a Person object with {firstName: name} or undefined;
type PeopleRecord = {
[K in string]?: Person & {firstName: K}
}
Unfortunately it doesn't work the way we want it to if we just apply this type to peopleObj, since it doesn't see the names as anything more specific than string. Maybe another user can figure out how to make it work, but I can't.
Like #mbdavis, I need to reassign the object to enforce that it matches a more specific constraint.
I use the mapped type ValidateRecord<T> which takes a type T and forces that every key on that type is a Person with that first name.
type ValidateRecord<T> = {
[P in keyof T]: Person & {firstName: P}
}
Now we can reassign your peopleObj, which should only work if the object is valid, and will throw errors otherwise.
const validatedObj: ValidateRecord<typeof peopleObj> = peopleObj;
But it's a lot cleaner to do this assertion through a function. Note that the function itself merely returns the input. You could do any other validation checking on the JS side of things here.
const asValid = <T extends {}>( record: T & ValidateRecord<T> ): ValidateRecord<T> => record;
const goodObj = {
Jason: {
firstName: "Jason"
},
Tim: {
firstName: "Tim"
}
} as const;
// should be ok
const myRecordGood = asValid(goodObj);
// should be a person with {firstName: "Tim"}
const A = myRecordGood["Tim"]
// should be a problem
const B = myRecordGood["Missing"]
const badObj = {
Jason: {
firstName: "Bob"
},
Tim: {
firstName: "John"
}
} as const;
// should be a problem
const myRecordBad = asValid(badObj);
Playground Link

typescript undefined on the entire interface

How to declare the entire MyCustomObject interface can fallback to an empty object?
interface MyCustomObject {
name: string,
age: number
}
Below case is safe, it has default property of name and age as fallback, but sometime if the obj is from other source like an api, it can be some other type like an empty {} or even an empty []
const obj: MyCustomObject = {
name: "",
age: 0
}
I tried this
interface MyCustomObject {
name: string,
age: number
} | {}
it doesn't work that way.
the most correct way is to create a union
type IUnionType = MyCustomObject | {};
other ways to handle this is to make each property optional
interface MyCustomObject {
name?: string,
age?: number
}
Or create an optional object property where a union is defined in the original interface.
interface MyCustomObject2 {
obj: MyCustomObject | {}
}
examples
I have one interface having some member variables
suppose my interface name is IUser and variable name is iUser.
I have to make it undefine but I m getting error that we cannot make iUser = undefine
so I did something like this.
let obj: any;
this.iUser = obj;
obj = undefined
and it works for me

Error: Type '{}' is missing the following properties from type

interface Person {
name: string;
surname: string;
}
let person1: Person = {};
person1.name = "name"
person1.surname = "surname"
When I declare person1 I get this error:
Type '{}' is missing the following properties from type Person
This is a better way:
let person1: Person = {name: '', surname: ''};
But if you want exactly empty object than you can hack it like this:
let person1: Person = {} as Person;
Update after comment:
Look at this unpredictableFunction:
const unpredictableFunction = (): string|number|string[] => {
return Math.random() > 0.5 ? 'string' : Math.random() > 0.5 ? 9999 : ['1', '2', '3']
};
It may return number or it may return string or it may return array of strings
const person: Person = {name: '', surname: ''};
person.name = unpredictableFunction (); // this is a case you are talking about
In this case you will see
Type 'string | number | string[]' is not assignable to type 'string'.
Answers are:
Look at your code and ensure that you assign only strings to a Person properties,
Or update interface to be ready to a different values:
interface Person {
name: string | number | string[];
surname: string;
}
You have defined an interface with two required properties. So when you define an object with the type of the Person interface you must define these properties right away like this:
let person: Person = {
name: '',
surname: ''
}
However if you believe these properties are not required but are rather optional you can change your interface to this:
interface Person {
name?: string;
surname?: string;
}
Using the ? syntax you mark the property as optional. The following code should then work:
let person: Person = {};
in Typescript 2.0 we can do this better
let person1 ! : Person;
this "!" is Non-null assertion operator
according to documentation
A new ! post-fix expression operator may be used to assert that its operand is non-null and non-undefined in contexts where the type checker is unable to conclude that fact. Specifically, the operation x! produces a value of the type of x with null and undefined excluded. Similar to type assertions of the forms x and x as T, the ! non-null assertion operator is simply removed in the emitted JavaScript code.

How do I type the values of an object's fields with a specific subset of keys?

I have an object containing multiple types of values:
interface Foo {
id: number;
data: FooData;
username: string;
notes: string;
}
const foo: Foo = {
...
}
I have a function that requires a string, and am iterating through a specific list of fields in my object to use in that function, all of which contain string values:
const renderString = (value: string) => {
...
}
const fooKeys: keyof Foo = ["username", "notes"];
fooKeys.map((key) => {
renderString(foo[key])
}
The issue is that foo[key] can be a string, number, or FooData object, so I want to specify that foo[key] will ONLY be a field value of Foo with a key matching one in fooKeys, since they are all going to be string values.
We can assert foo[key] as string but that doesn't protect us against bad keys in fooKeys.
One option is to use a conditional type to only allow string properties since that appears to be the only ones allowed? For example:
type StringPropsOnly<T> = {
[Key in keyof T]: T[Key] extends string ? Key : never;
}[keyof T]
const fooKeys: StringPropsOnly<Foo>[] = ["username", "notes"];
fooKeys.map((key) => {
renderString(foo[key])
});
TypeScript Playground
This answer I found mentions as const:
const fooKeys = ["username", "notes"] as const;

Categories