Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save funnylookinhat/1b1979ea9ce7e43431005082f6001374 to your computer and use it in GitHub Desktop.
Save funnylookinhat/1b1979ea9ce7e43431005082f6001374 to your computer and use it in GitHub Desktop.
Typescript Flatten Nested Interface
// Source: https://www.typescriptlang.org/play?noErrorTruncation=true&ts=4.4.2#code/HYQwtgpgzgDiDGEAEB5ATgSwOZIN4CgkikNgAXCNAMwWQDEB7BvQ4tqpgLiSjM2Cys2RAEYg0Afm7AArmBGUhw4NAoATbgWHakHBt178sAGiU6kaiBBgAbAJ4A5VRA0tz5sZOlyFaMzoBffyIg8xlgDAZgAz5SHAAfN3ciPRijU2TRcSkkWXlFc1DiIKUyOxh6JjobEDIKFTUkAF4katr6gB5GBgA+JQB6ACokMorWqpq6iAbmpPYuHliBAG5-ACIVXhcAOj01tLjV7XDI6MWjI+E1k6jdpn3zw-9PHLzfJETwyypSF0u2DbONTbSzWexOLbAzxrV4+SgfJBfCA-Br-YjXCK3aGw-JoBFIlF-JQBQb9fBKUgUai0cYMNpTBoAWRAshANjmwlSjwEwSQL28uN5gMhdwYD0McSFm3UIKstkcQO22IFvnJ5n6-RIVERmOAJCgSBA3KwnF5NzOEp5vI1Wp1p31hr1DBEACsIPAyKbzBjTqLxUtBN7zUrxDCVQVimriKNkAA1SgYKh2ADK4GQLUZMjIMjZ9gAogAPepqKBdCbtaYuYy0+n1FzM1k2PolSmUGiIWkcshgNQicN4xJaHRkDBkGwQTS8tgMTBYUhsg48zJILCUMAsnKWwOBXkiGRqVdkHGq8xoCAAN2mMggx4j2jIICwNl+m4DvIYl7Q54wEAA7q+jF5GA0AYNQZA9U4AGEGDAOAImgHIh2SDA1FvPxlxsBgsAYAAFWoAAsAMlZdQEgIil0yGdsFIaDwj4OxyO3bQAgAbQAXV5M9xxAKAIAAEVqG9FyY4RV2AM8oEY9jOLojAyP7ICGC2LxjQRJD3DUWoQE0HhwFsCdHTsJAAiQKdiHgKJ6jIAAVcoDK3XkihCJQ0FqOIdPgfD+2WJA0E9XI4TxEzBxGBgHxsftjKUTSKGTKxgByASKCUKgoAAazQpQwDAHIRCYbjgHwEoY1aSY6zUbpZlraYy16fAhhGOzSorBpKpadTu17bylDWTqRG2EcxwgbYqLnUAbH9C4er67YxIk7YAAZJqeAEZpgJSqRBLTtigPTxweFk7DRXqe369blO2CzyGmGy7OWlYetckcBEu-CHjeShjqeuJtj897Aq+tyXrIMK2X+3Fjpmwbx1mtcWTDVTPmAb5fjUSHTqVfdDwRj6Bx1FHUWmjGz0vWQIBxwL8WR5FUfR3sBsfZ8VARrcqYJolVoxj9KG-P8WYDNmacJzn6eA0DwJHKJoNglkfygBGCVponRZAsCIKlmC4LlxbthQincUFwk0eVs7VYlqDNdl6Adcw7C8LIN7GMNpWRdN8X1eAaWtethbtlI8mnaR9njdd7YxbVyXPct+CoB10aaIYOi0DsfmjGd4X0RmriIB4-jBNTuJ045zOMbm6AFepo26f6tBZMgfX3iDoXi6IE7RY2ygC4EIuQ+IGKIDi6YckVjOUnStCe7RbLcvynO9SbquiqQUlyRKvNyGT2ZcCQNKIDsYTq3PNlr24Q7qwYGBI4XPlZ5ZYzVnwNeC1sUCIA6ayelmAB9QsX8sd+kAQCLNMEsvkc5qCiPYQy7EkASDwEgNYS1uDWRYrjNixkkDIJ6A-EqP9n6YX-h-ZoShrKAOAcjA0zo3QelgfAliABpEgepd52AYNqaybEAC0UheSMKAcWA0rM4G-wIW-FB9C2Kf34SAg0pAqDwjzLQxR0iKFIHXvRWBvJ1LmBYdwAABgAElwPQgIRi8wsQAOQsIseglRoC1hrFoQ4zBCDthrFMbgcxVi942ICHojImQj42BPmoyxQTrw2ICckC+V8IqhO8XYGxZCBEIMcXA3AJk7EGhwhgeAaV37VnoZ-OBfBrwuJoDYXiLivExNOGyGxjleTSAvPCZpn4XEqE-MSFiLC2FIA4S47eujUmH2PgZay59L51LiRUqpJRH5NWgjYGoMBeIAKyWojedhP4tAABRKG3ixRRpA1FFlch6Ap8DalRGvrM5AARP48XidYtibFuBePCRAJJTkkAADIkB4T8hgNkHRDnHL1IWPgCAyCXO3tc8a3BSn3MeQaLxLy3nxM+d8voABKZJMimHyLxCgWhhzGEnN6dqFAGKUAMPQSZNpigFljGqsAd+OykBLJWWskRr92V9FXk1TM2Zcx2EhTI9Z5DQEAFVqzSvxao2M1ZYyzCIS0c8DAUJAA
// Saved here in case it ever goes away, this could be useful if you wanted to do some specific
// bindings between a react state that is flattened and a nested interface.
namespace Orig {
interface Foo {
foo: string
bar?: number
nested: {
foo: string,
deeplyNested: {
bar?: number
}
}
union: string | {
foo: string,
bar?: number
}
}
type FooFlattened = Flatten<Foo>
/* type FooFlattened = {
foo: string;
"nested.foo": string;
union: string;
"union.foo": string;
bar?: number | undefined;
"nested.deeplyNested.bar"?: number | undefined;
"union.bar"?: number | undefined;
}*/
interface FooFlattenedManual {
foo: string
bar?: number
"nested.foo": string
"nested.deeplyNested.bar"?: number
// if union is a string:
union: string
// if union is an object:
"union.foo": string
"union.bar"?: number
}
type VerifySame = MutuallyExtends<FooFlattened, FooFlattenedManual>
}
interface Foo {
tmdb: number | {
title: {
original: string
german?: string
}
budget?: number
revenue?: number
tagline?: string
overview?: string
productionCompanies?: {
id?: number
logoPath?: string
name?: string
originCountry?: string
}[]
releaseDate?: string
genres?: string[]
runtime?: number
poster?: string | {
data: { sample: any }
contentType: string
}
}
rating: { ch: number; rt: number } | { total: number }
dateSeen?: Date
fsk?: number
mm?: boolean
}
type FlattenedFoo = Flatten<Foo>
/* type FlattenedFoo = {
tmdb: number;
"tmdb.title.original": string;
"tmdb.genres.0": string;
"tmdb.poster.data.sample": any;
"tmdb.poster.contentType": string;
"rating.ch": number;
"rating.rt": number;
"rating.total": number;
"tmdb.title.german"?: string | undefined;
"tmdb.budget"?: number | undefined;
"tmdb.revenue"?: number | undefined;
"tmdb.tagline"?: string | undefined;
"tmdb.overview"?: string | undefined;
"tmdb.productionCompanies"?: undefined;
"tmdb.productionCompanies.0.id"?: number | undefined;
"tmdb.productionCompanies.0.logoPath"?: string | undefined;
"tmdb.productionCompanies.0.name"?: string | undefined;
"tmdb.productionCompanies.0.originCountry"?: string | undefined;
"tmdb.releaseDate"?: string | undefined;
"tmdb.genres"?: undefined;
"tmdb.runtime"?: number | undefined;
"tmdb.poster"?: string | undefined;
dateSeen?: undefined;
fsk?: number | undefined;
mm?: boolean | undefined;
} */
type Entry = { key: string, value: any, optional: boolean };
type Explode<T> = _Explode<T extends readonly any[] ? { "0": T[number] } : T>;
type _Explode<T> =
T extends object ? { [K in keyof T]-?:
K extends string ? Explode<T[K]> extends infer E ? E extends Entry ?
{
key: `${K}${E['key'] extends "" ? "" : "."}${E['key']}`,
value: E['value'],
optional: E['key'] extends "" ? {} extends Pick<T, K> ? true : false : E['optional']
}
: never : never : never
}[keyof T] : { key: "", value: T, optional: false }
type Collapse<T extends Entry> = (
{ [E in Extract<T, { optional: false }> as E['key']]: E['value'] }
& Partial<{ [E in Extract<T, { optional: true }> as E['key']]: E['value'] }>
) extends infer O ? { [K in keyof O]: O[K] } : never
type Flatten<T> = Collapse<Explode<T>>
type MutuallyExtends<T extends U, U extends V, V = T> = void
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment