Last active
February 27, 2018 12:17
-
-
Save RomkeVdMeulen/ee3397452be61f6b3e23a3ab506b7c0a to your computer and use it in GitHub Desktop.
@autocast / @autocastArray TypeScript decorators - http://romkevandermeulen.nl/2018/01/31/autocast-decorator.html
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
export function autocast<T>(type: {new (): T}) { | |
return function(target: any, key: string) { | |
makeAutocast(target, key, (newVal: any) => { | |
if (newVal === undefined) { | |
return undefined; | |
} | |
if (newVal instanceof type) { | |
return newVal; | |
} | |
return Object.assign(new type(), newVal); | |
}); | |
}; | |
} | |
export function autocastArray<T>(type: {new (): T}) { | |
return function(target: any, key: string) { | |
makeAutocast(target, key, (newVals: any[]) => { | |
return newVals.map(newVal => { | |
if (newVal === undefined) { | |
return undefined; | |
} | |
if (newVal instanceof type) { | |
return newVal; | |
} | |
return Object.assign(new type(), newVal); | |
}); | |
}); | |
}; | |
} | |
function makeAutocast<T>(prototype: any, key: string, mapper: (value: any) => T) { | |
const values = new Map<any, any>(); | |
Object.defineProperty(prototype, key, { | |
set(initialValue: any) { | |
Object.defineProperty(this, key, { | |
get() { | |
return values.get(this); | |
}, | |
set(value: any) { | |
values.set(this, mapper(value)); | |
}, | |
enumerable: true, | |
}); | |
this[key] = initialValue; | |
}, | |
enumerable: true, | |
configurable: true, | |
}); | |
} |
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
describe("@autocast", () => { | |
it("ensures values assigned to the decorated property are converted to the desired type if necessary", () => { | |
class Composed { | |
prop: string; | |
get extended() { | |
return this.prop + "-extended"; | |
} | |
constructor(values?: any) { | |
Object.assign(this, values); | |
} | |
} | |
class Composer { | |
@autocast(Composed) | |
a: Composed; | |
constructor(values?: any) { | |
Object.assign(this, values); | |
} | |
} | |
const manual = new Composer({a: new Composed({prop: "a"})}); | |
expect(manual.a).to.be.an.instanceOf(Composed).with.property("prop", "a"); | |
expect(manual.a.extended).to.equal("a-extended"); | |
expect(Object.keys(manual)).to.deep.equal(["a"]); | |
const empty = new Composer(); | |
expect(empty.a).to.be.undefined; | |
expect(Object.keys(empty)).to.be.empty; | |
const automatic = new Composer({a: {prop: "b"}}); | |
expect(automatic.a).to.be.an.instanceOf(Composed).with.property("prop", "b"); | |
expect(automatic.a.extended).to.equal("b-extended"); | |
expect(Object.keys(automatic)).to.deep.equal(["a"]); | |
const undefinedValue = new Composer({a: undefined}); | |
expect(undefinedValue.a).to.be.undefined; | |
const assignedLater = new Composer(); | |
assignedLater.a = <any> {prop: "c"}; | |
expect(assignedLater.a).to.be.an.instanceOf(Composed).with.property("prop", "c"); | |
expect(assignedLater.a.extended).to.equal("c-extended"); | |
}); | |
}); | |
describe("@autocastArray", () => { | |
it("ensures values in any array assigned to the decorated property are converted to the right type if necessary", () => { | |
class Composed { | |
prop: string; | |
get extended() { | |
return this.prop + "-extended"; | |
} | |
constructor(values?: any) { | |
Object.assign(this, values); | |
} | |
} | |
class Composer { | |
@autocastArray(Composed) | |
a: Composed[]; | |
constructor(values?: any) { | |
Object.assign(this, values); | |
} | |
} | |
const manual = new Composer({a: [new Composed({prop: "a"}), new Composed({prop: "b"})]}); | |
expect(manual.a).to.be.an("array").with.lengthOf(2); | |
expect(manual.a[0]).to.be.an.instanceOf(Composed).with.property("prop", "a"); | |
expect(manual.a[0].extended).to.equal("a-extended"); | |
expect(manual.a[1].extended).to.equal("b-extended"); | |
expect(Object.keys(manual)).to.deep.equal(["a"]); | |
const empty = new Composer(); | |
expect(empty.a).to.be.undefined; | |
expect(Object.keys(empty)).to.be.empty; | |
const automatic = new Composer({a: [{prop: "c"}, {prop: "d"}]}); | |
expect(automatic.a).to.be.an("array").with.lengthOf(2); | |
expect(automatic.a[0]).to.be.an.instanceOf(Composed).with.property("prop", "c"); | |
expect(automatic.a[0].extended).to.equal("c-extended"); | |
expect(automatic.a[1].extended).to.equal("d-extended"); | |
expect(manual.a[0]).to.be.an.instanceOf(Composed).with.property("prop", "a"); | |
expect(Object.keys(automatic)).to.deep.equal(["a"]); | |
const undefinedValue = new Composer({a: [{prop: "c"}, undefined]}); | |
expect(undefinedValue.a[0]).to.be.an.instanceOf(Composed).with.property("prop", "c"); | |
expect(undefinedValue.a[1]).to.be.undefined; | |
const assignedLater = new Composer(); | |
assignedLater.a = <any> [{prop: "e"}, {prop: "f"}]; | |
expect(assignedLater.a).to.be.an("array").with.lengthOf(2); | |
expect(assignedLater.a[0]).to.be.an.instanceOf(Composed).with.property("prop", "e"); | |
expect(assignedLater.a[0].extended).to.equal("e-extended"); | |
expect(assignedLater.a[1].extended).to.equal("f-extended"); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment