Assert schema of javascript object on function callback handler - javascript

I have an object with structure like this: {prop_1: function_1, prop_n: function_n}. I use jest to unit-test my code, which you may check here: https://jestjs.io.
Functions provided by prop_${i} have some requirement object {prop_1:requirementCallback_1, prop_n: requirementCallback_n} needed such that requirementCallback_${i}, for example, that given function returns a defined value or it asserts some expected number of input arguments. Therefore, it is advisable from my point of view that we run requirement values against function_{i} to verify such conditions.
I am not familiar to Typescript, but its name suggests that the language may have some out-of-the-box solution to check such conditions. What do you think about this scenario?

Does the object have dynamic properties? then try:
//change void to your appropriate return type
type MyObjectType = Record<string, (someStringArg: string, someNumberArg: number) => void >
If you are trying to have defined properties with same funtion types then try:
type SameFunctionType = (string_arg1: string, number_arg2: number)=>void//or any return type for the function
interface MyObjectType{
prop_1: SameFunctionType;
prop_n: SameFunctionType;
}
If you are trying to have defined properties with different funtion types then try:
interface MyObjectType{
prop_1: ()=>number;
prop_2: (arg1: Date)=>number;
}

Related

Cast object type to another type

I have class where I set type of range to IntervalRange
export class Test {range: IntervalRange;}
then in parent class I initialize the value:
export class TestInitializer {
Create(){
return <Test>{
range: IntervalRange.initialize(Database.GetByName('MONTH').id};
}
InitializeElement() {
let test = <Test>JSON.parse(JSON.stringify(configuration));
return test;
}
then in my other component I use this as:
#Input() range: IntervalRange;
However on function range.getRange();
I get: ERROR TypeError: this.range.getRange is not a function
Why is that? it says range is an Object, though it should be IntervalRange.
I tried writing as IntervalRange, <IntervalRange> range
nothing worked. How to fix that?
Update: let type = typeof(this.range); prints "object"
method:
ngOnChanges() {
if (this.range) {
let type = typeof(this.range);
let ddd = this.range.getRange(); //<----- this is where I get error
}
A typecast only casts the type. Typescript doesn't exist at runtime. Therefore if JSON.parse doesn't return a proper Test instance (which it won't since methods won't get serialized), it will fail at runtime. Instead of typecasting you probably want to build up a Test instance, and load the serialized data into it.
The TypeScript "cast" (<A>tmp or tmp as A) :
makes structural type compatibility checking → it is unavailable if the source data has type any.
has no runtime counterpart → TypeScript types can be compatible but runtime types can differ, for instance an object literal vs a real class instance.
When the data comes from an unsafe/external source, e.g. JSON.parse() or a WebAPI call, it's a DTO (data transfer object), with the any type. The TypeScript "cast" is unsafe too.
To ensure the cast operation, you can use a mapping function from the DTO to the "domain model" class. The key point is to return a real class instance.
Object.assign(new IntervalRange(), this.range) (that you mentioned in a comment) satisfies this point.
We can be even more stricter with a "field by field mapping": this.a = range.a; this.b = range.b; ...).
Both options are better encaspulated in a function: the class constructor, a static factory method in the class, an external function in the same module.

Defining Object Literals with Flow

I am attempting to define this object:
let apiFormatted: {"barcode": number, "id": number} = {"barcode": this_barcode.barcode, "id": this.props.currentJob}
The error I am getting from Flow is:
Flow: object literal. This type is incompatible with object type
I have attempted {[string]: number, [string]: number} as well with no success. I am new to Flow and any help would be appreciated.
First, some words about flow:
flowtype is a typechecker. That means it checks if you use the variables the way you have declared them. This has two advantages:
1) you can find errors while you write your code, and it does not occur when you execute it, which makes your code more bulletproof
2) it makes the code more readable, therefore its easier for others to understand the code and you can work in a team.
The only disadvantage is that you need to type much more. However flow infers much of the types, that means that it checks for the types even if you don't tell it to. Therefore
let obj = {barcode : this_barcode.barcode, id : this.props};
actually infers the type:
{barcode: number, id: number}
That means that the following won't work:
obj.barcode = "fails"
as flow infers a number, but I tried to assign a string.
When you want to pass it to a function and do:
function open(barcode : {}){
console.log(barcode.id);
}
This is dangerous, beause you can do
open({});
And it won't tell you that the code wont work as wanted. So there it makes sense to define id as aproperty:
function open(barcode : { id : number }){
If your object has many propertes that are extensively used, it's probably a good idea to define an Interface once:
interface Barcode {
barcode : number;
id : number
}
let obj : Barcode = {barcode:1, id:2};

Function variable assigned to a Class using Typescript

Say you have a Typescript class like this:
class CompExt extends Comp {
public static sum(a: number, b: number): number {return a+b};
};
The function sum will be sligtly different from the original class and it must be static.
Your actual code is
let ff: Function = CompExt;
console.log('the summ works fine',ff.sum(1,2));
Both the code editor and compilation will warn me this:
bas.ts(47,16): error TS2339: Property 'sum' does not exist on type 'Function'.
Is there a way to avoid compilation errors? If I use any rather than Function everthing is fine but I was wondering if Typescript supports such style.
Just don't specify the type to be a Function because it's not a generic function, it's CompExt.
If you really want to specify the type, you can use: let ff: typeof CompExt = CompExt.
A possible solution to your situation can be provided using decorators. Check this answer Static property on a class

Return Object (with specific key, value types) from function in TypeScript

Suppose I have a function that needs to return something of type StringMap<string, boolean>. An example return that is valid is: {"required": true}.
Now, I've read in a tutorial (it's not important which tutorial) you can create a function that has return type of { [s: string]: boolean } and this is the same return type as the StringMap above.
I don't understand how are these two the same return type? And how the second version is even valid?
All the return types I have seen in TypeScript have only included the type in the past i.e. boolean, number, any. For example function (): number {}. In our second version we use s: string which means we give the variable a name, and specify it's type, how are we suddenly allowed to give the variable the name s?
On top of that we put this string inside an array [s: string] as the key in the second version (therefore the key is now an array). While a StringMap has a string as the key.
The syntax is a bit different than you think. It's a unique syntax for defining dictionaries\maps.
{ [s: string]: boolean } means: a map, which has a key with type string, and it's values are boolean. The s means nothing at all, it could have been anything you want.
(Then why give it a name in the first place? my guess is to make the code more clear, when mapping more complex types. Sometimes you'll want to call the index id, sometimes address, etc..)
More info here, indexed types is what you want.
The Typescript handbook online isn't the most friendly documentation ever, but I think it's good enough and I recommend everyone who uses typescript to at least skim through it. Especially since in 2.0+ they added a bunch of crazy\awesome type features like mapped types.
The type { [s: string]: boolean } defines an indexable type interface.
What you see as an array is just the syntax decided to define the index of the interface.
The name of the key, as far as I know, is ignored and only the type is what matters.
This code { [s: string]: boolean } is defining an indexable interface where the indices are strings and the values are booleans.
I assume that the definition of StringMap is as follows:
export interface StringMap<T, U> = { [s: T]: U };
Which is kind of redundant if you ask me (as the name says that it should be a string map, so the keys should be strings). I would have declared the IStringMap interface as:
export interface IStringMap<T> = { [key: string]: T };
Interfaces in TypeScript just define the "shape" of the object. The previous three definitions have equivalent shapes, so this is perfectly valid:
function fn() : IStringMap<boolean> {
let myMap : StringMap<string, bool> = { };
myMap["foo"] = true;
myMap["bar"] = false;
myMap["baz"] = true;
return myMap;
}
let foo: { [bazzinga: string]: boolean } = fn();

Is there a way I can check a function parameter has an allowed value before runtime with Typescript?

I have an AngularJS service coded in Typescript. I call the functions of the service with a parameter like this setFalse('retrieve'):
class StateService implements IStateService {
state = [];
get = (property: string) => {
return this.state[property];
}
setFalse = (property: string) => {
this.state[property] = false;
}
setNull = (property: string) => {
this.state[property] = null;
}
setTrue = (property: string) => {
this.state[property] = true;
}
// more code here
}
Is there some way that I could remove the need for the quoted string 'retrieve', use a constant or check the values being used before run time?
1. Is there some way that I could remove the need for the quoted string 'retrieve'?
You could desugar the overload, and provide lots of different versions of the function.
//just to give the idea
function setFalse_retrieve(){ return setFalse('retrieve') }
The upside of doing this is that its really typesafe and there is no way to ever call setFalse with a bad parameter. The downside is that there is lots of boilerplate and you can't pass around a property value if you want to.
2. Use a constant?
Typescript has an enum feature for this:
enum Properties { retrieve, frobnicate };
You can now use Properties.retrieve instead of "retrieve" in your code and it will catch any typos in the enum name.
Properties.retriev; // Error: The property 'retriev' does not exist on value of type 'typeof Properties'.
Just be aware that Typescript makes the enum values be integers so you will need to convert them to strings when calling the Angular functions:
var enumProperty = Properties.retrieve; // 0
var strProperty = Properties[enumProperty]; // "retrieve"
The downside of the enum approach is that you can pass any number where an enum value is expected and the error will not be detected at runtime (do don't do that):
var x:Property = 10; // :(
3. Use a constant or check the values being used before run time
Typescript has function overloading on function constants but AFAIK, you can only use it to specialize return types depending on inputs, not to restrict your valid inputs to a set of constants. That is, you would still need to have a generic case that accepts any string, which is not what you want.

Categories