I use simple-schema to define DB schemas in an object:
{
name: 'string',
age: 'integer',
...
}
Is it somehow possible to create an interface or class from this object, so I don't have to type everything twice?
You can do this, but it might be more trouble than it's worth unless you think you might be changing the schema. TypeScript doesn't have built-in ways of inferring types in a way that you want, so you have to coax and cajole it to do so:
First, define a way of mapping the literal names 'string' and 'integer' to the TypeScript types they represent (presumably string and number respectively):
type MapSchemaTypes = {
string: string;
integer: number;
// others?
}
type MapSchema<T extends Record<string, keyof MapSchemaTypes>> = {
-readonly [K in keyof T]: MapSchemaTypes[T[K]]
}
Now if you can take an appropriately typed schema object like the one you specified, and get the associated type from it:
const personSchema = {name: 'string', age: 'integer'};
type Person = MapSchema<typeof personSchema>; // ERROR
Oops, the problem is that personSchema is being inferred as {name: string; age: string} instead of the desired {name: 'string'; age: 'integer'}. You can fix that with a type annotation:
const personSchema: { name: 'string', age: 'integer' } = { name: 'string', age: 'integer' };
type Person = MapSchema<typeof personSchema>; // {name: string; age: number};
But now it feels like you're repeating yourself. Luckily there is a way to force it to infer the proper type:
function asSchema<T extends Record<string, keyof MapSchemaTypes>>(t: T): T {
return t;
}
const personSchema = asSchema({ name: 'string', age: 'integer' }); // right type now
type Person = MapSchema<typeof personSchema>; // {name: string; age: number};
UPDATE 2020-06: in more recent TS versions you can use a const assertion to get the same result:
const personSchema = { name: 'string', age: 'integer' } as const;
type Person = MapSchema<typeof personSchema>;
That works!
See it in action on the Typescript Playground. Hope that helps; good luck!
const personObj = { name: 'string', age: 'integer' } type Person = MapSchema<typeof asSchema(personObj)>; I have been very unsuccessful at encapsulating this.as-clauses-in-mapped-types would help but we're not.I don't think you can declare dynamic interfaces. However, you can create a type for objects with known properties.
You can create an object that maps string literals to actual types, e.g. 'integer' => number, but that is not relevant to the question. I don't know what framework you're using but the following example works for a similar looking framework: Mongoose.
users.js
export const UserSchema = mongoose.Schema({
name: String,
value: Number
});
export const Users = mongoose.Model('users', UserSchema);
export type User = { [K in keyof typeof UserSchema]: any } ;
usage:
import { User, Users } from './user';
Users.find({}).exec((err: Error, res: User) => { ... })
The returned result should have the same keys as UserSchema, but all values are mapped to any as you would still have to map string literals to types.
User) contains every key of the javascript object (UserSchema)?MapSchema util, but I'm not working with any sort of real database schema, I just have a simple object of field names and their default values. I didn't like using the { [K in keyof Thing]: string } syntax cause I wanted to assert that every key of my "schema" is enforced for the created type - I was able to answer my own question here: stackoverflow.com/questions/64778252/…Here is an example if you have defined the type in your object e.g. when you use mongoose schema
const schema = {
name: {
type: String,
required: true,
unique: true,
index: true
},
decimals: { type: BigInt, required: true }
}
type MongooseSchemaInterface = {
[K in keyof typeof schema]: MongooseSchemaType<typeof schema[K]['type']>;
};
const test: MongooseSchemaInterface = {
name: 123 // <-- Will not work
}