Skip to content

Instantly share code, notes, and snippets.

@StefanKern
Last active August 14, 2019 13:43
Show Gist options
  • Save StefanKern/56ba63d0549fade8974333830b1092e0 to your computer and use it in GitHub Desktop.
Save StefanKern/56ba63d0549fade8974333830b1092e0 to your computer and use it in GitHub Desktop.
Deep Property Access with Typescript
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');
});
});
// 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