Javascript nameof object property - javascript

In C# there is the posibility to get the name of an object property as a string value.
nameof(object.myProperty) --> "myProprty"
Can this be done in Javascript/Typescript?
Object.Keys() is the only thing i found, but it gives me all the keys.
Example what I want to achieve:
export interface myObject {
myProperty: string;
}
console.log(myObject["myProperty"]);
Let's say I change my interface for some reason to:
export interface myObject {
myPropertyOther: string;
}
console.log(myObject["myProperty"]); // This will not give an error on build
So I want to have something like this:
console.log(myObject[nameOf(myObject.myProperty)]
// This will give an error on build when the interface is changed

There is no nameof operator in Javascript/Typescript. You can creat a function that takes the key of another object and this is checked by the typescript compiler:
function keyOf<T>(o: T, k: keyof T) {
return k
}
let o = { a: 1 }
keyOf(o, 'a'); //ok
keyOf(o, 'b'); //err

I made a library that fetches the name of a property at runtime, even for types that don't exist at runtime (interfaces or types in TypeScript):
It can be found on NPM here: #fluffy-spoon/name-of
The source code is simple (just a few lines of code): https://github.com/ffMathy/FluffySpoon.JavaScript.NameOf
Usage
import { getPropertyName, getDeepPropertyName } from '#fluffy-spoon/name-of';
interface SomeType {
foo: boolean;
someNestedObject: {
bar: string;
}
}
console.log(getPropertyName<SomeType>(x => x.foo)); //prints "foo"
console.log(getPropertyName<SomeType>(x => x.someNestedObject)); //prints "someNestedObject"
console.log(getPropertyName<SomeType>(x => x.someNestedObject.bar)); //prints "bar"
console.log(getDeepPropertyName<SomeType>(x => x.foo)); //prints "foo"
console.log(getDeepPropertyName<SomeType>(x => x.someNestedObject)); //prints "someNestedObject"
console.log(getDeepPropertyName<SomeType>(x => x.someNestedObject.bar)); //prints "someNestedObject.bar"
Library source code
In case you don't want to install the NPM package.
function getPropertyNameInternal<T = unknown>(expression: (instance: T) => any, options: {
isDeep: boolean
}) {
let propertyThatWasAccessed = "";
var proxy: any = new Proxy({} as any, {
get: function(_: any, prop: any) {
if(options.isDeep) {
if(propertyThatWasAccessed)
propertyThatWasAccessed += ".";
propertyThatWasAccessed += prop;
} else {
propertyThatWasAccessed = prop;
}
return proxy;
}
});
expression(proxy);
return propertyThatWasAccessed;
}
export function getPropertyName<T = unknown>(expression: (instance: T) => any) {
return getPropertyNameInternal(expression, {
isDeep: false
});
}
export function getDeepPropertyName<T = unknown>(expression: (instance: T) => any) {
return getPropertyNameInternal(expression, {
isDeep: true
});
}

Yes, this can be done in Javascript/Typescript. There is a typescript module.
Shamelessly copied from the manual.
nameof(console);
nameof(console.log);
nameof(console["warn"]);
Transforms to:
"console";
"log";
"warn";
There are more nice examples in the manual.
The solution for yours question:
interface IMyObject {
myPropertyOther: string;
}
let myObject: IMyObject = {
myProperty: 'Hello world'
};
console.log(myObject[nameof<IMyObject>((o) => o.myProperty)]);

Related

How to write a constructor function in TypeScript that has method extension functionality

I want to write a constructor function that takes some predefined methods from an object (Methods) and injects it into every new object that is created with the constructor. It injects methods from another object because, I want the consumers of my module to be able to add new methods.
But the problem is: as all the injected methods are not defined in the constructor I can't seem to manage their type annotations properly.
It's hard for me to describe the problem so I created a simple example (with JavaScript to avoid all the type error) to demonstrate it.
// methods.js ---------------------------------------
const methods = {};
const addMethod = (name, value) => (methods[name] = value);
// these methods should be added by an external user of the programmer.js module
function code(...task) {
this.addInstruction({
cmd: "code",
args: task
});
return this;
}
function cry(...words) {
this.addInstruction({
cmd: "cry",
args: words
});
return this;
}
addMethod("code", code);
addMethod("cry", cry);
// programmer.js -------------------------------------
const retriveInstructionsMethod = "SECRET_METHOD_NAME";
const secretKey = "VERY_SECRET_KEY";
function Programmer() {
const instructions = [];
this.addInstruction = (value) => instructions.push(value);
Object.defineProperty(this, retriveInstructionsMethod, {
enumerable: false,
writable: false,
value(key) {
if (key === secretKey) return instructions;
},
});
for (const key of Object.keys(methods))
this[key] = (...args) => methods[key].apply(this, args);
}
// test.js -------------------------------------------
const desperateProgrammer = new Programmer();
const instructions = desperateProgrammer
.code("A library in typescript within 10 days")
.cry("Oh God! Why everything is so complicated :'( ? Plz Help!!!")
// the next two lines shouldn't work here (test.js) as the user
// shouln't have asscess to the "retriveInstructionsMethod" and "secretKey"
// keys. I'm just showing it to demonstrate what I want to achieve.
[retriveInstructionsMethod](secretKey);
console.log(instructions);
Here I want to hide all the instructions given to a Programmer object. If I hide it from the end user then I won't have to validate those instructions before executing them later.
And the user of programmer.js module should be able to add new methods to a programmer. For example:
// addMethods from "methods.js" module
addMethods("debug", (...bugs) => {...});
Now I know that, I can create a base class and just extend it every time I add a new method. But as it is expected that there will be lots of external methods so soon it will become very tedious for the user.
Below is what I've tried so far. But the type annotation clearly doesn't work with the following setup and I know it should not! Because the Methods interface's index signature([key: string]: Function) is very generic and I don't know all the method's name and signature that will be added later.
methods.ts
export interface Methods {
[key: string]: Function;
}
const methods: Methods = {};
export default function getMethods(): Methods {
return { ...methods };
}
export function addMethods<T extends Function>(methodName: string, method: T) {
methods[methodName] = method;
}
programmer.ts
import type { Methods } from "./methods";
import getMethods from "./methods";
export type I_Programmer = Methods & {
addInstruction: (arg: { cmd: string; args: unknown[] }) => void;
};
interface ProgrammerConstructor {
new (): I_Programmer;
(): void;
}
const retriveInstructionsMethod = "SECRET_METHOD_NAME";
const secretKey = "ACCESS_KEY";
const Programmer = function (this: I_Programmer) {
const instructions: object[] = [];
this.addInstruction = (value) => instructions.push(value);
Object.defineProperty(this, retriveInstructionsMethod, {
enumerable: false,
writable: false,
value(key: string) {
if (key === secretKey) return instructions;
},
});
const methods = getMethods();
for (const key of Object.keys(methods))
this[key] = (...args: unknown[]) => methods[key].apply(this, args);
} as ProgrammerConstructor;
// this function is just to demonstrate how I want to extract all the
// instructions. It should not be accessible to the end user.
export function getInstructionsFrom(programmer: I_Programmer): object[] {
// gives error
// #ts-expect-error
return programmer[retriveInstructionsMethod][secretKey]();
}
export default Programmer;
testUsages.ts
import { addMethods } from "./methods";
import type { I_Programmer } from "./programmer";
import Programmer, { getInstructionsFrom } from "./programmer";
function code(this: I_Programmer, task: string, deadline: string) {
this.addInstruction({ cmd: "code", args: [task, deadline] });
return this;
}
function cry(this: I_Programmer, words: string) {
this.addInstruction({ cmd: "cry", args: [words] });
return this;
}
addMethods("code", code);
addMethods("cry", cry);
const desperateProgrammer = new Programmer()
.code("a library with typescript", "10 days") // no type annotation of "code" method
.cry("Oh God! Why everything is so complicated :'( ? Plz Help!!!"); // same here
// Just for demonstration. Should not be accessible to the end user!!!
console.log(getInstructionsFrom(desperateProgrammer));
Kindly give me some hint how I can solve this problem. Thanks in advance.

TypeScript: Dynamically declared methods in class

I've some code like:
const methodsList = [
'foo',
'bar',
// ... 20 other items ...
]
export class Relayer {
constructor() {
for (const methodName of methodsList) {
this[methodName] = (...args) => {
// console.log('relaying call to', methodName, args)
// this is same for all methods
}
}
}
}
const relayer = new Relayer()
relayer.foo('asd') // TS error
relayer.bar('jkl', 123) // TS error
Now when I use the class instance, TypeScript complains when I call relayer.foo() or relayer.bar(). To make the code compile, I've to cast it as any or similar.
I've an interface that declares foo, bar and the other methods:
interface MyInterface {
foo: (a: string) => Promise<string>
bar: (b: string, c: number) => Promise<string>
// ... 20 other methods
}
How do I get TypeScript to learn the dynamically declared foo and bar class methods? Can the declare syntax be useful here?
First step is to create a type or interface where when indexed by a value in methodsList, the result will be a function:
// The cast to const changes the type from `string[]` to
// `['foo', 'bar']` (An array of literal string types)
const methodsList = [
'foo',
'bar'
] as const
type HasMethods = { [k in typeof methodsList[number]]: (...args: any[]) => any }
// Or
type MethodNames = typeof methodsList[number] // "foo" | "bar"
// k is either "foo" or "bar", and obj[k] is any function
type HasMethods = { [k in MethodNames]: (...args: any[]) => any }
Then, in the constructor, to be able to assign the keys of methodsList, you can add a type assertion that this is HasMethods:
// General purpose assert function
// If before this, value had type `U`,
// afterwards the type will be `U & T`
declare function assertIs<T>(value: unknown): asserts value is T
class Relayer {
constructor() {
assertIs<HasMethods>(this)
for (const methodName of methodsList) {
// `methodName` has type `"foo" | "bar"`, since
// it's the value of an array with literal type,
// so can index `this` in a type-safe way
this[methodName] = (...args) => {
// ...
}
}
}
}
Now after constructing, you have to cast the type still:
const relayer = new Relayer() as Relayer & HasMethods
relayer.foo('asd')
relayer.bar('jkl', 123)
You can also get rid of the casts when constructed using a factory function:
export class Relayer {
constructor() {
// As above
}
static construct(): Relayer & HasMethods {
return new Relayer() as Relayer & HasMethods
}
}
const relayer = Relayer.construct()
Another way around it is to create a new class and type-assert that new results in a HasMethods object:
class _Relayer {
constructor() {
assertIs<HasMethods>(this)
for (const methodName of methodsList) {
this[methodName] = (...args) => {
// ...
}
}
}
}
export const Relayer = _Relayer as _Relayer & { new (): _Relayer & HasMethods }
const relayer = new Relayer();
relayer.foo('asd')
relayer.bar('jkl', 123)
Or if you are only using new and then methods in methodsList, you can do:
export const Relayer = class Relayer {
constructor() {
assertIs<HasMethods>(this)
for (const methodName of methodsList) {
this[methodName] = (...args) => {
// ...
}
}
}
} as { new (): HasMethods };
You can also use your MyInterface interface instead of HasMethods, skipping the first step. This also gives type-safety in your calls.
Use the following syntax:
export class Relayer {
constructor() {}
public foo(){
// your foo method
this.executedOnEachFunction();
}
public bar(){
// your bar method
this.executedOnEachFunction();
}
executedOnEachFunction(){
// what you want to do everytime
}
}
https://repl.it/repls/LawfulSurprisedMineral
To me, this sounds like a need for an interface.
interface MyInterface {
foo(): void; // or whatever signature/return type you need
bar(): void;
// ... 20 other items ...
}
export class Relayer implements MyInterface {
constructor() {}
foo(): void {
// whatever you want foo to do
}
// ... the rest of your interface implementation
}
What it looks like you are doing is implementing some interface of sorts. In your constructor you are defining what the method implementations are instead of defining them in the class body. Might help to read Class Type Interfaces

Flow inheritance results in incompatible type at runtime

A.js:
// #flow
export interface A {
propA: string;
method(): void;
}
B.js:
// #flow
import { A } from "../interfaces/A.js";
export class B implements A {
propA: string;
// Additional properties here...
method() { //do stuff }
// Additional methods here...
};
main.js:
// #flow
import { A } from "../interfaces/A.js";
import { B } from "../classes/B.js";
export const processA = (w: string, obj: A): string => {
return processB(w, obj);
};
const processB = (_w: string, _obj: B): string => {
return _w;
};
Error: Cannot call 'processB' with 'obj' bound to '_obj' because 'A' [1] is incompatible with 'B' [2].
(Yes I know the A/B obj are not used in these functions, this is just a trimmed down example)
I understand why the error is being thrown, because in processB there is no guarantee that input _obj is of type B since it is of type A. But I'd like to have a method that takes in an obj: A and then passes to a submethod that requires obj to be of type B.
Is there a way to accomplish this? I'm kind of hacking around it by manually checking the constructor.name and using instanceof before calling processB, and changing declaration to const processB = (_w: string, _obj: A).
But it seems like there could be a better way. I want the initial method to take in any object that implements the interface, then have submethods that enforce the input obj to be a certain class that extends that interface.
I could only think of using instanceof since Flow needs some way of guaranteeing what type obj is. You don't need to change processB to accept an A if you're using instanceof, however. For example,
interface A {
propA: string;
method(): void;
};
class B implements A {
propA: string;
// Additional properties here...
propB: string;
method() {
// do stuff
}
// Additional methods here...
}
class C implements A {
propA: string;
// Additional properties here...
propC: string;
method() {
// do stuff
}
// Additional methods here...
}
function processA(w: string, obj: A): string {
if (obj instanceof B) {
return processB(w, obj);
} else if (obj instanceof C) {
return processC(w, obj);
}
throw new Error('Unsupported implementation of interface A');
// or just return a default string
};
function processB(w: string, obj: B): string {
return w + obj.propB;
};
function processC(w: string, obj: C): string {
return w + obj.propC;
}
Try Flow

Can a Descriptor change type in Typescript?

I'm trying to make descriptor which can change return type in typescript but I don't know how to do this.
Here is the code and what I tried:
function changeReturnType()
{
return <T extends unknown>(
target: {},
key: string | symbol,
descriptor: TypedPropertyDescriptor<T>
) => {
const oldValue = descriptor.value;
return {
...descriptor,
value(...argv)
{
// #ts-ignore
return String(oldValue.call(target, ...argv))
}
}
};
}
class Foo {
#changeReturnType()
square ()
{
return 1;
}
}
let val = new Foo().square(); // I hope ts know here is string
console.dir({
val,
type: typeof val,
});
This is currently not possible in TypeScript but you may look at this comment and modify hack described there to your needs (i.e. for method decorator usage).

How to implement Swift-style enum with associated values in Typescript? [duplicate]

Unfortunately, as of 0.9.5, TypeScript doesn't (yet) have algebraic data types (union types) and pattern matching (to destructure them). What's more, it doesn't even support instanceof on interfaces. Which pattern do you use to emulate these language features with maximal type safety and minimal boilerplate code?
I went with the following Visitor-like pattern, inspired by this and this (in the example, a Choice can be Foo or Bar):
interface Choice {
match<T>(cases: ChoiceCases<T>): T;
}
interface ChoiceCases<T> {
foo(foo: Foo): T;
bar(bar: Bar): T;
}
class Foo implements Choice {
match<T>(cases: ChoiceCases<T>): T {
return cases.foo(this);
}
}
class Bar implements Choice {
match<T>(cases: ChoiceCases<T>): T {
return cases.bar(this);
}
}
Usage:
function getName(choice: Choice): string {
return choice.match({
foo: foo => "Foo",
bar: bar => "Bar",
});
}
The matching itself is expressive and type-safe, but there's lot of boilerplate to write for the types.
Example to illustrate the accepted answer:
enum ActionType { AddItem, RemoveItem, UpdateItem }
type Action =
{type: ActionType.AddItem, content: string} |
{type: ActionType.RemoveItem, index: number} |
{type: ActionType.UpdateItem, index: number, content: string}
function dispatch(action: Action) {
switch(action.type) {
case ActionType.AddItem:
// now TypeScript knows that "action" has only "content" but not "index"
console.log(action.content);
break;
case ActionType.RemoveItem:
// now TypeScript knows that "action" has only "index" but not "content"
console.log(action.index);
break;
default:
}
}
TypeScript 1.4 adds union types and type guards.
Here's an alternative to the very good answer by #thSoft. On the plus side, this alternative
has potential interoperability with raw javascript objects on the form { type : string } & T, where the shape of T depends on the value of type,
has substantially less per-choice boilerplate;
on the negative side
does not enforce statically that you match all cases,
does not distinguish between different ADTs.
It looks like this:
// One-time boilerplate, used by all cases.
interface Maybe<T> { value : T }
interface Matcher<T> { (union : Union) : Maybe<T> }
interface Union { type : string }
class Case<T> {
name : string;
constructor(name: string) {
this.name = name;
}
_ = (data: T) => ( <Union>({ type : this.name, data : data }) )
$ =
<U>(f:(t:T) => U) => (union : Union) =>
union.type === this.name
? { value : f((<any>union).data) }
: null
}
function match<T>(union : Union, destructors : Matcher<T> [], t : T = null)
{
for (const destructor of destructors) {
const option = destructor(union);
if (option)
return option.value;
}
return t;
}
function any<T>(f:() => T) : Matcher<T> {
return x => ({ value : f() });
}
// Usage. Define cases.
const A = new Case<number>("A");
const B = new Case<string>("B");
// Construct values.
const a = A._(0);
const b = B._("foo");
// Destruct values.
function f(union : Union) {
match(union, [
A.$(x => console.log(`A : ${x}`))
, B.$(y => console.log(`B : ${y}`))
, any (() => console.log(`default case`))
])
}
f(a);
f(b);
f(<any>{});
To answer
it doesn't even support instanceof on interfaces.
Reason is type erasure. Interfaces are a compile type construct only and don't have any runtime implications. However you can use instanceof on classes e.g. :
class Foo{}
var x = new Foo();
console.log(x instanceof Foo); // true
This is an old question, but maybe this will still help someone:
Like #SorenDebois's answer, this one has half of the per-case boilerplate as #theSoft's. It is also more encapsulated than #Soren's. Additionally, this solution has type safety, switch-like behavior, and forces you to check all cases.
// If you want to be able to not check all cases, you can wrap this type in `Partial<...>`
type MapToFuncs<T> = { [K in keyof T]: (v: T[K]) => void }
// This is used to extract the enum value type associated with an enum.
type ValueOfEnum<_T extends Enum<U>, U = any> = EnumValue<U>
class EnumValue<T> {
constructor(
private readonly type: keyof T,
private readonly value?: T[keyof T]
) {}
switch(then: MapToFuncs<T>) {
const f = then[this.type] as (v: T[keyof T]) => void
f(this.value)
}
}
// tslint:disable-next-line: max-classes-per-file
class Enum<T> {
case<K extends keyof T>(k: K, v: T[K]) {
return new EnumValue(k, v)
}
}
Usage:
// Define the enum. We only need to mention the cases once!
const GameState = new Enum<{
NotStarted: {}
InProgress: { round: number }
Ended: {}
}>()
// Some function that checks the game state:
const doSomethingWithState = (state: ValueOfEnum<typeof GameState>) => {
state.switch({
Ended: () => { /* One thing */ },
InProgress: ({ round }) => { /* Two thing with round */ },
NotStarted: () => { /* Three thing */ },
})
}
// Calling the function
doSomethingWithState(GameState.case("Ended", {}))
The one aspect here that is really not ideal is the need for ValueOfEnum. In my application, that was enough for me to go with #theSoft's answer. If anyone knows how to compress this, drop a comment below!

Categories

Resources