Skip to content

Instantly share code, notes, and snippets.

@Kjaer
Last active July 17, 2023 12:22
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 Kjaer/e39e7046a8f7c920426227857a25eb2d to your computer and use it in GitHub Desktop.
Save Kjaer/e39e7046a8f7c920426227857a25eb2d to your computer and use it in GitHub Desktop.
Type narrowing with mapped types. This gist, iterate over nested union types and narrows the type definition for later filtering.
type Sitemap = {
pageIdentifier?: 'Page';
slug?: string | null;
seo?: { pageIdentifier: 'Seo'; noIndex?: boolean | null } | null;
content?:
| { pageIdentifier?: 'AppHomePage' }
| {
pageIdentifier: 'CategoryPage';
blockedInCountry?: boolean | null;
link?: string | null;
published?: boolean | null;
globalCanonical?: { pageIdentifier?: 'Page'; slug?: string | null } | null;
localizedCanonical?: { pageIdentifier?: 'Page'; slug?: string | null } | null;
}
| {
pageIdentifier: 'ContentPage';
title: string;
globalCanonical?: { pageIdentifier?: 'Page'; slug?: string | null } | null;
localizedCanonical?: { pageIdentifier?: 'Page'; slug?: string | null } | null;
}
| { pageIdentifier: 'StartPage' }
| { pageIdentifier: 'SystemPage' }
| { pageIdentifier: 'WorldHomePage' }
| null;
};
type CategoryPage = {
[Key in keyof Sitemap]: Sitemap[Key] extends Sitemap['content']
? Extract<Sitemap['content'], { pageIdentifier: 'CategoryPage' }>
: Sitemap[Key]
}
type ContentPage = {
[Key in keyof Sitemap]: Sitemap[Key] extends Sitemap['content']
? Extract<Sitemap['content'], { pageIdentifier: 'ContentPage' }>
: Sitemap[Key]
}
const isCategoryPageOrContentPage = <Page extends CategoryPage | ContentPage>(
item: Sitemap | null,
): item is Page =>
item?.content?.pageIdentifier === 'CategoryPage' || item?.content?.pageIdentifier === 'ContentPage'
const item: Sitemap = {}
console.log(isCategoryPageOrContentPage<CategoryPage>(item)) // Narrows type of `item` for `content` field is `pageIdentifier: 'CategoryPage'`
console.log(isCategoryPageOrContentPage<ContentPage>(item)) // Narrows type of `item` for `content` field is `pageIdentifier: 'ContentPage'`
console.log(isCategoryPageOrContentPage<ContentPage | CategoryPage>(item)) // // Narrows type of `item` for `content` field can be either `pageIdentifier: 'CategoryPage'` or `pageIdentifier: 'ContentPage'`
function isContentPageItem(item: Sitemap): false | void {
//narrows the `content` field type only for `pageIdentifier: 'ContentPage'`
if(!isCategoryPageOrContentPage<ContentPage>(item)) { return false }
console.log(item.content?.title);
}
function isCategoryPageItem(item: Sitemap): false | void {
//narrows the `content` field type only for `pageIdentifier: 'CategoryPage'`
if(!isCategoryPageOrContentPage<CategoryPage>(item)) { return false }
console.log(item.content?.published);
}
@Kjaer
Copy link
Author

Kjaer commented Jul 17, 2023

Playground link:

https://www.typescriptlang.org/play?ssl=64&ssc=2&pln=1&pc=1#code/C4TwDgpgBAyglsCBbAhmKBeKBvAUFKMFAcwgEkATCAO2DgDM4IAnAfgC4oByABRIi4BufFADOAGwCuxDmODM41YlAA+UapPHjhBURAD2s7IX6UadRi05cYBoev1lqVAB6yARvv3iIKaqvVNcSgAXwCNLR0oAGN9WnMOEQI1YyJSM1oGJjZrAEEwMAAJfSQIPlIuUKSAvAI6urTyKkzLZmsAYRREYn1mEHKBKPqCd3F9aIBrCAondv1JWj6PLx8-cKCh4fFFCdlReUVlNQjtavqwSVG4UQALaeXvX39jjbO64jH3FHFO6ji4aLfIwmdLNCzZWS8fj2CTSPYHJTrLShJGnYZ1MaA7YAL2mv3+WOBjQy4JYkIGMKkMk4+wUiJeyLCDLRwxC1RSbwIxLBWSs3Dm8VoFM29TowB8NIRxBF70+33x1ABQM4qVMPNa5OhgjEVPhdKOgUZqJlBEx3zguIoCqV4iJavMvJy3GFOrhkv1qJRzJlbPqKRBTQdrWsMGAKGYwApVT9OADJMdIZA+2QUd9dX93KD2WsAHVeuIKMVSqn2YbTiFhLhQJAoJ1ur1+vxMDgRABtADSEBAUEUUCmIH09FgCGQaAAupx4IhUGAO12x1AIC5EM5RMPp2hW1xYoLgFwx9VWFAAKLL5goaLAAA8U9Hs+3cRXe7HABpY5mWtn+V0ID0+qmAD5qknEcZznEADzZKtwGgAUnwGZtaigcCe38ftB3XO8J0wsDOwgxdlxoCg11vMCH13fdDxPM8L2vUjN3Ip99zfVVQSzPkuDg8xAOAnDNzwyDcFwHd9h7UQ61-BsBgAeWYLihSbLArwQpcV2I2sfz-RtSACeTI34ACAAoRFAkCN3QZkX1wABKThQLEqAEIwICCFA1gADodyfDyP1JZhMAwLBOM0qToVUNQ3M8x8Enc3zHQCoK9IpISROAHtpzMu9EKgkTHncsZiEM64JK0mS5OihTSCvErQtIIzQOs6yoAAemaqAADlw2YfQAHc12raAMIAA1AoaoHoXooCGrzzDGywCwcoa4uDb963-aEhuEuJRDygqivEkL1tIWSkv4aqKv0uqiunRqWrazrmG6vqoAGqBhtG8bJumi65qYBbrim5av04i6KU23KfHy-RCuKw7tIgE7QbO06dLUGqjogeqbqa1q7o6rrev6mC3qHEbpzmr6ZtoX6IAWwF-HcaAIAQO5-KW+1Pw49H4a4MavqBrmkYqTbcHoBZLzgOIxJR8hp2u5BMpnWzxu+PQAgAN30OAKBbAhWuoAnnuAO4pqp4AaYW164nEbsJv8wGOb8johYEEXXPoQyAEJYbW+HEd3AZzoDgz5aQW7jGYCBgEkZh-HoVXoCgggIYgKGYenKLdw8sUfGs4QoLF6gJaln3JIxsg5dMviwGV+PxDVtRNe13W7oNx7CZek3vt3C2dat6gbc++32bYzm2lWsuebdnsPe9g7fbKmXqrhgYseQcOoEj6PY5V+vE6E5Ptt26HQ8z7zYsubZbmmPPcBCIA

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment