A strange mismatch not noticed by Typescript. How is this possible? - node.js

Apparently, Typescript doesn't seem to recognize the difference between an object like {} and a generic array [] by accepting last one as input of a function that requires an object with {}'s structure.
To resume my problem, this is a simplified example to replicate it:
type test = { [key: string]: any };
let x: test = ["x", "y", "z"];
Actually, Typescript seems to accept this. How is this possible?
Note: The situation I ran into is more similar to this:
type fooType = { [key: string]: any };
const fooFunction<T extends fooType>(input: T) => // code...
fooFunction([]); // No red underline
But you can consider the first example. It's the same.
The main idea is to create a function that accepts only objects with a key (type string) and a value of any type.
Thank you in advance for the answers!

Differentiating between plain objects and other things (like arrays, or even functions) can be frustrating in JavaScript (and therefore Typescript).
Since an array is an object, you need a type that excludes arrays. For completeness, you may also want to exclude other non-plain objects, like functions, dates, regexes, etc, but I'll just focus on arrays.
Using your example, here are some approaches:
1. Exclude objects with numeric indexes
function fooFunction<T extends {
[key: string]: any,
[index: number]: never
}>(input: T) { }
fooFunction(['']); // Will have red underline!
fooFunction([]); // This will NOT have an underline!
In the above case, we're saying that T cannot have any numeric indexes. There is an edge case, though: an empty array has type never[], which also has no numeric indexes!
2. Exclude array-specific fields
Another approach is to identify some property common to arrays that won't be in any of the objects you plan to pass through your function:
function fooFunction<T extends {
map?: never,
}>(input: T) { }
fooFunction(['']); // Will have red underline!
fooFunction([]); // So will this!
3. Narrow the parameter type
The cleanest approach is to narrow your generic at the parameter to exclude arrays. The following example uses a utility type that returns never for lots of non-plain-object inputs (but not all of them):
type FancyObject = any[]|Function|Date|RegExp|Error
type PlainObject<T> = T extends FancyObject
? never
: T extends { [key: string]: any }
? T
: never;
function fooFunction<T>(input: PlainObject<T>) {}
fooFunction(['']); // Will have red underline!
fooFunction([]); // So will this!
fooFunction({ hello: 'world' }) // This is fine!

Related

populate object properties using lambda expression in typescript

Newbie Alert! I feel silly asking this question but I need someone to teach me the correct syntax.
I have code that looks like this:
let thing: INewThing;
thing.personId = another.personId;
thing.address = another.work.address;
thing.greades = another.subjectInfo.grades;
thing.isCurrent = another.student.isCurrent;
I know it can be written cleaner. I want to use a lamda expression, something like this:
let thing: INewThing => {
personId = another.personId,
address = another.work.address,
grades = another.subjectInfo.grades,
isCurrent = another.student.isCurrent
} as IThingUpdate;
I have looked and looked for an example. I have yet to find one that works for me. It's just syntax but no matter what I try it doesn't work.
You're just looking to create a new object, which is a pretty different thing from a "lambda" (function). Just declare the object. You don't need a function.
const thing = {
personId: another.personId,
address: another.work.address,
// use the correct spelling below - no 'greades'
grades: another.subjectInfo.grades,
isCurrent: another.student.isCurrent,
};
If the another is typed properly, that should be sufficient.
If the another object had more properties using the same path, another option would be to destructure those properties out, then declare the object with shorthand, eg:
const originalObj = { prop: 'val', nested: { foo: 'foo', bar: 'bar', unwanted: 'unwanted' } };
const { foo, bar } = originalObj.nested;
const thing = { foo, bar };
Destructuring like this, without putting the values into differently-named properties, helps reduce typos - if a property starts out, for example, as someLongPropertyName, putting it into a standalone identifier someLongPropertyName and then constructing an object with shorthand syntax ensures that the new object also has the exact property name someLongPropertyName (and not, for example, someLongPRopertyName - which isn't that uncommon of a mistake when using the more traditional object declaration format).
But since all the paths in your another object are different, this approach wouldn't work well in this particular situation.

pass only the fields on the interface typescript [duplicate]

When using typescript a declared interface could look like this:
interface MyInterface {
test: string;
}
And an implementation with extra property could be like this:
class MyTest implements MyInterface {
test: string;
newTest: string;
}
Example (here the variable 'reduced' still contain the property 'newTest'):
var test: MyTest = {test: "hello", newTest: "world"}
var reduced: MyInterface = test; // something clever is needed
Question
In a general way, how can you make the 'reduced' variable to only contain the properties declared in the 'MyInterface' interface.
Why
The problem occur when trying to use the 'reduced' variable with angular.toJson before sending it to a rest service - the toJson method transforms the newTest variable, even if it's not accessible on the instance during compile, and this makes the rest service not accept the json since it has properties that shouldn't be there.
It is not possible to do this. The reason being interface is a Typescript construct and the transpiled JS code is empty
//this code transpiles to empty!
interface MyInterface {
test: string;
}
Thus at runtime there is nothing to 'work with' - no properties exist to interrogate.
The answer by #jamesmoey explains a workaround to achieve the desired outcome.
A similar solution I use is simply to define the 'interface' as a class -
class MyInterface {
test: string = undefined;
}
Then you can use lodash to pick the properties from the 'interface' to inject into you object:
import _ from 'lodash'; //npm i lodash
const before = { test: "hello", newTest: "world"};
let reduced = new MyInterface();
_.assign(reduced , _.pick(before, _.keys(reduced)));
console.log('reduced', reduced)//contains only 'test' property
see JSFiddle
This is a pragmatic solution that has served me well without getting bogged down on semantics about whether it actually is an interface and/or naming conventions (e.g. IMyInterface or MyInterface) and allows you to mock and unit test
TS 2.1 has Object Spread and Rest, so it is possible now:
var my: MyTest = {test: "hello", newTest: "world"}
var { test, ...reduced } = my;
After that reduced will contain all properties except of "test".
Another possible approach:
As other answers have mentioned, you can't avoid doing something at runtime; TypeScript compiles to JavaScript, mostly by simply removing interface/type definitions, annotations, and assertions. The type system is erased, and your MyInterface is nowhere to be found in the runtime code that needs it.
So, you will need something like an array of keys you want to keep in your reduced object:
const myTestKeys = ["test"] as const;
By itself this is fragile, since if MyInterface is modified, your code might not notice. One possible way to make your code notice is to set up some type alias definitions that will cause a compiler error if myTestKeys doesn't match up with keyof MyInterface:
// the following line will error if myTestKeys has entries not in keyof MyInterface:
type ExtraTestKeysWarning<T extends never =
Exclude<typeof myTestKeys[number], keyof MyInterface>> = void;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Type 'UNION_OF_EXTRA_KEY_NAMES_HERE' does not satisfy the constraint 'never'
// the following line will error if myTestKeys is missing entries from keyof MyInterface:
type MissingTestKeysWarning<T extends never =
Exclude<keyof MyInterface, typeof myTestKeys[number]>> = void;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Type 'UNION_OF_MISSING_KEY_NAMES_HERE' does not satisfy the constraint 'never'
That's not very pretty, but if you change MyInterface, one or both of the above lines will give an error that hopefully is expressive enough that the developer can modify myTestKeys.
There are ways to make this more general, or possibly less intrusive, but almost no matter what you do, the best you can reasonably expect from TypeScript is that your code will give compiler warnings in the face of unexpected changes to an interface; not that your code will actually do different things at runtime.
Once you have the keys you care about you can write a pick() function that pulls just those properties out of an object:
function pick<T, K extends keyof T>(obj: T, ...keys: K[]): Pick<T, K> {
return keys.reduce((o, k) => (o[k] = obj[k], o), {} as Pick<T, K>);
}
And them we can use it on your test object to get reduced:
var test: MyTest = { test: "hello", newTest: "world" }
const reduced: MyInterface = pick(test, ...myTestKeys);
console.log(JSON.stringify(reduced)); // {"test": "hello"}
That works!
Playground link to code
Are you trying to only set/assign properties listed on the interface only? Functionality like that is not available in TypeScript but it is very simple to write a function to perform the behaviour you looking for.
interface IPerson {
name: string;
}
class Person implements IPerson {
name: string = '';
}
class Staff implements IPerson {
name: string = '';
position: string = '';
}
var jimStaff: Staff = {
name: 'Jim',
position: 'Programmer'
};
var jim: Person = new Person();
limitedAssign(jimStaff, jim);
console.log(jim);
function limitedAssign<T,S>(source: T, destination: S): void {
for (var prop in destination) {
if (source[prop] && destination.hasOwnProperty(prop)) {
destination[prop] = source[prop];
}
}
}
In your example newTest property won't be accessible thru the reduced variable, so that's the goal of using types. The typescript brings type checking, but it doesn't manipulates the object properties.
In a general way, how can you make the 'reduced' variable to only contain the properties declared in the 'MyInterface' interface.
Since TypeScript is structural this means that anything that contains the relevant information is Type Compatible and therefore assignable.
That said, TypeScript 1.6 will get a concept called freshness. This will make it easier to catch clear typos (note freshness only applies to object literals):
// ERROR : `newText` does not exist on `MyInterface`
var reduced: MyInterface = {test: "hello", newTest: "world"};
Easy example:
let all_animals = { cat: 'bob', dog: 'puka', fish: 'blup' };
const { cat, ...another_animals } = all_animals;
console.log(cat); // bob
One solution could be to use a class instead of an interface and use a factory method (a public static member function that returns a new object of it's type). The model is the only place where you know the allowed properties and it's the place where you don't forget to update them accidentaly on model changes.
class MyClass {
test: string;
public static from(myClass: MyClass): MyClass {
return {test: myClass.test};
}
}
Example:
class MyTest extends MyClass {
test: string;
newTest: string;
}
const myTest: MyTest = {test: 'foo', newTest: 'bar'};
const myClass: MyClass = MyClass.from(myTest);

TypeScript shared configuration object with typehinting

I am developing an API library and I am curious about how should endpoint configuration problem should be approached in Node.js with TypeScript. I want all endpoint configuration to be contained within one entity.
I currently have this approach in place:
ApiConstants.ts
------------------------
const BASE_DOMAIN = 'https://api.example.com';
export default Object.freeze({
BASE_DOMAIN: {
V1: `${BASE_DOMAIN}/v0.1`,
V2: `${BASE_DOMAIN}/v0.2`,
V3: `${BASE_DOMAIN}/v0.3`,
},
PATH: {
CATS: '/animals/cats',
},
});
It does the job, I can use it in any class by importing it and accessing the values. The problem is that I want to restrict functions to only accept values declared within this object. When request constructing function should display invalid type intellisense when value is passed which is not a part of this object.
Desired type would look something like this. Path must be declared within ApiConstants.PATH object.
function makeRequest(path: ApiConstants.PATH) {
...
}
How can such behavior be achieved?
function makeRequest(path: keyof typeof ApiConstants.PATH) {
// ...
}
See in Playground
In general, you probably want to do something like
const ApiConstants = Object.freeze({
BASE_DOMAIN: {
V1: `${BASE_DOMAIN}/v0.1`,
V2: `${BASE_DOMAIN}/v0.2`,
V3: `${BASE_DOMAIN}/v0.3`,
},
PATH: {
CATS: '/animals/cats',
},
});
type ApiConstants = typeof ApiConstants;
export default ApiConstants;
to avoid having to use typeof ApiConstants all the time. Note that this means ApiConstants is both a value and a type—Typescript is OK with this, and will know from context what you’re doing, but some programmers find it confusing. A common naming convention is to use an initial lowercase letter for the value, and an initial uppercase for the type, as in const apiConstants = /*…*/; and type ApiConstants = typeof apiConstants;.
On the other hand, it’s kind of convenient that your default export is both the value and the type.
You might also want to add
export type ApiPaths = keyof ApiConstants['PATH'];
We use ['PATH'] because we’re using the type ApiConstants here, not the value. We could still use the value but we’d have to add typeof to it again, as in keyof typeof ApiConstants.PATH.

Enum attribute in lit/lit-element

We are trying to build a component with a property variant that should only be set to "primary" or "secondary" (enum). Currently, we are just declaring the attribute as a String, but we were wondering if there is a better way for handling enums? For example, should we validate somehow that the current value is part of the enum? Should we throw an error if not?
I asked this question on Slack and the answers I got lean towards declaring the property as String and use hasChanged() to display a warning in the console if the property value is invalid.
Standard HTML elements accept any string as attribute values and don't throw exceptions, so web components should probably behave the same way.
This all sounds reasonable to me.
If you're using TypeScript I'd recommend just using strings. You can use export type MyEnum = 'primary' | 'secondary' to declare it and then use #property() fooBar: MyEnum to get build time checking. You can use #ts-check to do this in plain JS with #type MyEnum too.
This works well if the enums are for component options or that map to server-side enums that will get validated again.
However, if you want to validate user input into enums or loop through them a lot this is less good. As the JS runs it has no visibility of the type. You need an object dictionary, something like:
const MyEnum = Object.freeze({
primary: 'primary',
secondary: 'secondary'
});
// Enforce type in TS
const value: keyof MyEnum;
// Validate
const validated = MyEnum[input.toLower()];
// Loop
for(const enumVal of Object.keys(MyEnum)) ...
// Or Convert to a different value type
const MyEnum = Object.freeze({
primary: 1,
secondary: 2
});
These are somewhat idiosyncratic. Again, if you're using TypeScript it has an enum keyword that compiles to something like this and I'd use that rather than rolling your own. Strings are the better option unless you need to validate, loop or convert the values.

TS: Cannot invoke an expression whose type lacks a call signature when defined dynamically, but it works

I'm still quite new to typescript, so please be gentle with me if I'm doing something with no sense for this technology!
The problem that I'm trying to solve is having a dynamic way to define how my application errors should be structured, but leaving to the users the faculty to enrich the messages.
So I tried to create this logic in a module that could be extended easily from the application, but I'm currently facing the problem:
Error:(35, 18) TS2349: Cannot invoke an expression whose type lacks a call signature. Type 'ErrorMessage' has no compatible call signatures.
What I thought it was a good idea (but please tell me if I'm wrong), was to use a register and a map to have the possibility to extend this mapping every time I want. So I created my ErrorMessage interface to be like the following:
export interface ErrorMessage {
actionMessage: string;
actionSubject: string;
originalErrorMessage?: string;
toString: () => string;
}
and a register for these, called ErrorResponseRegister, as it follows:
export enum defaultErrors {
ExceptionA = 'ExceptionA',
ExceptionB = 'ExceptionB',
}
export class ErrorResponseRegister {
private mapping: Map<string, ErrorMessage>;
constructor() {
this.mapping = new Map()
.set(defaultErrors.ExceptionA, exceptionAErrorMessage)
.set(defaultErrors.ExceptionB, exceptionBErrorMessage);
}
}
So at the end, every ErrorMessage function should look like:
export function exceptionAErrorMessage(originalErrorMessage?: string): ErrorMessage {
return {
enrichment1: "Something happened",
enrichment2: "in the application core",
originalErrorMessage: originalErrorMessage,
toString(): string {
return `${this.enrichment1} ${this.enrichment2}. Original error message: ${originalErrorMessage}`;
},
};
}
Please note I haven't used classes for this ones, as it doesn't really need to be instantiated
and I can have a bunch of them where the toString() method can vary. I just want to enforce the errors should have an enrichment1 and enrichment2 that highlight the problem in a better way for not-technical people.
So, now, back to code. When I'm trying to use the exceptionAErrorMessage statically, I can't see any problem:
console.log(exceptionAErrorMessage(originalErrorMessage).toString())
But when I try dynamically, using the map defined in the ErrorResponseRegister, something weird happens:
// In ErrorResponseRegister
public buildFor(errorType: string, originalErrorMessage?: string): Error {
const errorMessageBuilder = this.mapping.get(errorType);
if (errorMessageBuilder) {
return errorMessageBuilder(originalErrorMessage).toString();
}
return "undefined - do something else";
}
The code works as expected, the error returned is in the right format, so the toString function is executed correctly.
BUT, the following error appears in the IDE:
Error:(32, 18) TS2349: Cannot invoke an expression whose type lacks a call signature. Type 'ErrorMessage' has no compatible call signatures.
The line that causes the problem is
errorMessageBuilder(originalPosErrorMessage).toString()
Can someone help me to understand what I'm doing wrong?
It looks like your problem is you've mistyped mapping... it doesn't hold ErrorMessage values; it holds (x?: string)=>ErrorMessage values:
private mapping: Map<string, (x?: string) => ErrorMessage>;
What's unfortunate is that you initialize this variable via new Map().set(...) instead of the using an iterable constructor argument.
The former returns a Map<any, any> which is trivially assignable to mapping despite the mistyping. That is, you ran smack into this known issue where the standard library's typings for the no-argument Map constructor signature produces Map<any, any> which suppresses all kinds of otherwise useful error messages. Perhaps that will be fixed one day, but for now I'd suggest instead that you use the iterable constructor argument, whose type signature declaration will infer reasonable types for the keys/values:
constructor() {
this.mapping = new Map([
[defaultErrors.ExceptionA, exceptionAErrorMessage],
[defaultErrors.ExceptionB, exceptionBErrorMessage]
]); // inferred as Map<defaultErrors, (orig?: string)=>ErrorMessage>
}
If you had done so, it would have flagged the assignment as an error with your original typing for mapping (e.g., Type 'Map<defaultErrors, (originalErrorMessage?: string | undefined) => ErrorMessage>' is not assignable to type 'Map<string, ErrorMessage>'.) Oh well!
Once you make those changes, things should behave more reasonably for you. Hope that helps; good luck!
Link to code

Resources