Есть несколько вещей, которые мешают вам получить правильный результат. Во-первых, когда вы делаете const schema: Schema = { ... }
, вы фактически теряете информацию о типе буквенных строк. Решение состоит в том, чтобы удалить аннотацию : Schema
(поскольку она теряет информацию о типах) и использовать as const
для литерала, или использовать специальную функцию идентификации, которая должна захватывать литеральные типы.
Следующее, что вы необходимо сделать свойство type
обязательным для вашей схемы, в противном случае все свойства необязательны, чтобы любой тип соответствовал схеме, поэтому вы не можете выполнить сопоставление.
Наконец, свойство "default"
не имеет иметь правильный тип для объектов, поэтому вам нужно вместо этого использовать условные типы. Собрав все воедино, вы получите:
// ----- Types
interface SchemaString {
type: 'string',
required?: boolean,
default?: string
}
interface SchemaNumber {
type: 'number',
required?: boolean,
default?: number
}
interface SchemaObject {
type: 'object',
required?: boolean,
default?: {}
children?: Schema
}
type SchemaItem = SchemaString | SchemaNumber | SchemaObject;
type Schema = {[key:string]: SchemaItem };
type SchemaObjectChildrenToType<T extends Schema | undefined> =
T extends Schema ? SchemaToType<T> : {};
type SchemaItemToType<T extends SchemaItem> =
T extends SchemaString ? string :
T extends SchemaNumber ? number :
T extends SchemaObject ? SchemaObjectChildrenToType<T["children"]> :
never
type SchemaToType<T extends Schema> =
{ [P in keyof T]: SchemaItemToType<T[P]> };
// Stuff happens here
function check<T extends Schema>(schema: T): SchemaToType<T>{
// Processing
return null as any;
}
function makeSchema<T extends Schema>(schema: T): T {
return schema;
}
const schema = {
foo: {
type: 'string'
},
bar: {
type: 'object',
children: {
baz: { type: 'string' }
}
}
} as const;
// const schemaAlternative = makeSchema({
// foo: {
// type: 'string'
// },
// bar: {
// type: 'object',
// children: {
// baz: { type: 'string' }
// }
// }
// });
const result = check(schema);
result.bar.baz.endsWith("bar"); // works!
// 'result' type should be:
//
// {
// foo: string
// bar: {
// baz: string
// }
// }
//