Last active
August 14, 2019 13:43
-
-
Save StefanKern/56ba63d0549fade8974333830b1092e0 to your computer and use it in GitHub Desktop.
Deep Property Access with Typescript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {DeepPropertyAccess} from './deep-property-access'; | |
interface Customer { | |
name: string; | |
company?: { | |
name: string; | |
address?: { | |
city: string; | |
} | |
}; | |
} | |
describe('DeepPropertyAccess', () => { | |
const completeCustomer: Customer = { | |
name: 'Stefan', | |
company: { | |
name: 'Company', | |
address: { | |
city: 'Vienna' | |
} | |
} | |
}; | |
const nameOnlyCustomer: Customer = { | |
name: 'Stefan' | |
}; | |
it('should get the city "Vienna"', () => { | |
const city = DeepPropertyAccess.get(completeCustomer, 'company', 'address', 'city'); | |
expect(city).toBe('Vienna'); | |
}); | |
it('should return undefined because city is not defined', () => { | |
const city = DeepPropertyAccess.get(nameOnlyCustomer, 'company', 'address', 'city'); | |
expect(city).toBe(undefined); | |
}); | |
it('should return undefined because city is not defined', () => { | |
const city = DeepPropertyAccess.get(nameOnlyCustomer, 'company', 'address', 'city'); | |
expect(city).toBe(undefined); | |
}); | |
it('should have the correct type for city', () => { | |
const city = DeepPropertyAccess.get(completeCustomer, 'company', 'address', 'city'); | |
expect(typeof city).toBe('string'); | |
}); | |
it('should not detect "number" as type of city', () => { | |
const city = DeepPropertyAccess.get(completeCustomer, 'company', 'address', 'city'); | |
expect(typeof city).not.toBe('number'); | |
}); | |
it('should extract the correct value 1 levels deep', () => { | |
const level_2 = { | |
level_1: ['Hello'] | |
}; | |
const result = DeepPropertyAccess.get(level_2, 'level_1'); | |
expect(result.length).toBe(1); | |
expect(result[0]).toBe(<any> 'Hello'); | |
}); | |
it('should extract the correct value 2 levels deep', () => { | |
const level_3 = { | |
level_2: { | |
level_1: ['Hello'] | |
} | |
}; | |
const result = DeepPropertyAccess.get(level_3, 'level_2', 'level_1'); | |
expect(result.length).toBe(1); | |
expect(result[0]).toBe(<any> 'Hello'); | |
}); | |
it('should extract the correct value 3 levels deep', () => { | |
const level_4 = { | |
level_3: { | |
level_2: { | |
level_1: ['Hello'] | |
} | |
} | |
}; | |
const result = DeepPropertyAccess.get(level_4, 'level_3', 'level_2', 'level_1'); | |
expect(result.length).toBe(1); | |
expect(result[0]).toBe(<any> 'Hello'); | |
}); | |
it('should extract the correct value 4 levels deep', () => { | |
const level_5 = { | |
level_4: { | |
level_3: { | |
level_2: { | |
level_1: ['Hello'] | |
} | |
} | |
} | |
}; | |
const result = DeepPropertyAccess.get(level_5.level_4, 'level_3', 'level_2', 'level_1'); | |
expect(result.length).toBe(1); | |
expect(result[0]).toBe(<any> 'Hello'); | |
}); | |
it('should extract the correct value 5 levels deep', () => { | |
const level_6 = { | |
level_5: { | |
level_4: { | |
level_3: { | |
level_2: { | |
level_1: ['Hello'] | |
} | |
} | |
} | |
} | |
}; | |
// TODO: found out why it has to be cast, when it is 5 levels deep | |
const result = <Array<any>> <any> DeepPropertyAccess.get(level_6, 'level_5', 'level_4', 'level_3', 'level_2', 'level_1'); | |
expect(result.length).toBe(1); | |
expect(result[0]).toBe(<any> 'Hello'); | |
}); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Original code: https://codewithstyle.info/Deep-property-access-in-TypeScript/ | |
// thanks to Miłosz (https://disqus.com/by/miloszpiechocki/ ) for fixing it | |
export class DeepPropertyAccess { | |
public static get<T, | |
P1 extends keyof NonNullable<T>>(obj: T, prop1: P1): NonNullable<T>[P1] | undefined; | |
// tslint:disable:max-line-length | |
public static get<T, | |
P1 extends keyof NonNullable<T>, | |
P2 extends keyof NonNullable<NonNullable<T>[P1]>>(obj: T, prop1: P1, prop2: P2): NonNullable<NonNullable<T>[P1]>[P2] | undefined; | |
public static get<T, | |
P1 extends keyof NonNullable<T>, | |
P2 extends keyof NonNullable<NonNullable<T>[P1]>, | |
P3 extends keyof NonNullable<NonNullable<NonNullable<T>[P1]>[P2]>>(obj: T, prop1: P1, prop2: P2, prop3: P3): NonNullable<NonNullable<NonNullable<T>[P1]>[P2]>[P3] | undefined; | |
public static get<T, | |
P1 extends keyof NonNullable<T>, | |
P2 extends keyof NonNullable<NonNullable<T>[P1]>, | |
P3 extends keyof NonNullable<NonNullable<NonNullable<T>[P1]>[P2]>, | |
P4 extends keyof NonNullable<NonNullable<NonNullable<NonNullable<T>[P1]>[P2]>[P3]>>(obj: T, prop1: P1, prop2: P2, prop3: P3, prop4: P4): NonNullable<NonNullable<NonNullable<NonNullable<T>[P1]>[P2]>[P3]>[P4] | undefined; | |
public static get<T, | |
P1 extends keyof NonNullable<T>, | |
P2 extends keyof NonNullable<NonNullable<T>[P1]>, | |
P3 extends keyof NonNullable<NonNullable<NonNullable<T>[P1]>[P2]>, | |
P4 extends keyof NonNullable<NonNullable<NonNullable<NonNullable<T>[P1]>[P2]>[P3]>, | |
P5 extends keyof NonNullable<NonNullable<NonNullable<NonNullable<NonNullable<T>[P1]>[P2]>[P3]>[P4]>>(obj: T, prop1: P1, prop2: P2, prop3: P3, prop4: P4, prop5: P5): NonNullable<NonNullable<NonNullable<NonNullable<NonNullable<T>[P1]>[P2]>[P3]>[P4]>[P5] | undefined; | |
public static get<T, | |
P1 extends keyof NonNullable<T>, | |
P2 extends keyof NonNullable<NonNullable<T>[P1]>, | |
P3 extends keyof NonNullable<NonNullable<NonNullable<T>[P1]>[P2]>, | |
P4 extends keyof NonNullable<NonNullable<NonNullable<NonNullable<T>[P1]>[P2]>[P3]>, | |
P5 extends keyof NonNullable<NonNullable<NonNullable<NonNullable<NonNullable<T>[P1]>[P2]>[P3]>[P4]>, | |
P6 extends keyof NonNullable<NonNullable<NonNullable<NonNullable<NonNullable<NonNullable<T>[P1]>[P2]>[P3]>[P4]>[P5]>>(obj: T, prop1: P1, prop2: P2, prop3: P3, prop4: P4, prop5: P5, prop6: P6): NonNullable<NonNullable<NonNullable<NonNullable<NonNullable<NonNullable<T>[P1]>[P2]>[P3]>[P4]>[P5]>[P6] | undefined; | |
/* tslint:enable:max-line-length */ | |
// ...and so on... | |
// the actual function to extract the property | |
public static get(obj: any, ...props: string[]): any { | |
return obj && props.reduce( | |
(result, prop) => result == null ? undefined : result[prop], | |
obj | |
); | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment