Consider this object:
export const cypher = {
Foo: {
get Bar() {
return 'Foo_Bar';
},
get Baz() {
return 'Foo_Baz';
},
},
get Qux() {
return 'Qux';
},
Lan: {
Rok: {
get Saz() {
return 'Lan_Rok_Saz';
},
},
},
};
You can see the pattern: this is a "tree" where each leaf would return a string of the concatanated names of its entire branch, i.e.
console.log(cypher.Lan.Rok.Saz) // Lan_Rok_Saz
This manual object is strictly typed, so I get intellisense nicely. I would like to now create some constructor, that accepts an object of a type such as:
interface Entries {
[key: string]: (string | Entries)[];
}
And returns an object with the structure as the above cypher
, such that TS would be able to intellisense.
The internal implementation isn't necesarrily important, but the usage of X.Y.Z
is definitely a priority.
So far I've tried a recursive function that defines properties based on values:
interface Entries {
[key: string]: (string | Entries)[];
}
const mockEntries: (string | Entries)[] = ['Gat', 'Bay', {
Foo: ['Bar', 'Baz', { Cal: ['Car'] }],
}];
function buildCypherFromEntries(entries: (string | Entries)[], parentName: string, cypher: Record<string, any> = {}) {
entries.forEach(entry => {
if (typeof entry === 'string') {
Object.defineProperty(cypher, entry, {
get() { return (parentName ? `${parentName}_${entry}` : entry); },
});
} else {
Object.entries(entry).forEach(([key, furtherEntries]) => {
cypher[key] = {};
const furtherParentName = parentName ? `${parentName}_${key}` : key;
buildCypherFromEntries(furtherEntries, furtherParentName, cypher[key]);
})
}
})
return cypher;
}
const c = buildCypherFromEntries(mockEntries, '');
console.log(c.Gat) // Gat
console.log(c.Bay) // Bay
console.log(c.Foo.Bar); // Foo_Bar
console.log(c.Foo.Cal.Car) // Foo_Cal_Car
This works, but does not give any intellisense. It's also not perfect as it does not support top-level leaves that turn into getters. I also tried doing this in class form but again the typing confounds me.