How to assign an index signature to a object's value? - typescript-typings

Giving the following code:
export function objToStr(object: object): string {
let str = [];
for (let p in object) {
if (object.hasOwnProperty(p)) {
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(object[p]))
}
}
return str.join("&")
}
I get the error from object[p]:
Element implicitly has an 'any' type because type '{}' has no index signature. [7017]
I tried with
encodeURIComponent((<any>object[p]))
encodeURIComponent(object[p]: any)
But I still get the error. I find typing everything quite confusing, there is so much types out there.
If someone can tell me what Typescript wants from me there, it would help a lot, it's the last error message and then I'm done switching my code form JS to TS.
EDIT
I had to add "noImplicitAny": true to test the setting as I wasn't sure what is was doing and how the code would react to it.
Turning it to false I now get:
Argument of type 'string' is not assignable to parameter of type 'never'. [2345] for the part insite str.push

Error appears because you have compilerOptions.noImplicitAny = true at tsconfig.json. As error suggests, add index signature to object variable:
let object: { [index: string]: any } = {};
let str: string[] = [];
i.e. any will be specified explicit.

Related

Fix for Typescript warnings for type ‘any’ with Cloud Functions for Firebase

I’m getting a number of warnings all relating to the use of ‘any’ as a return type for functions in my Typscript code. I am trying to write a node.js backend with Cloud Functions for Firebase, to manage Google Play Billing purchases and subscriptions.
I am following the examples given in the Classy Taxi Server example here:
https://github.com/android/play-billing-samples/tree/main/ClassyTaxiServer
For example, the following:
function purchaseToFirestoreObject(purchase: Purchase, skuType: SkuType): any {
const fObj: any = {};
Object.assign(fObj, purchase);
fObj.formOfPayment = GOOGLE_PLAY_FORM_OF_PAYMENT;
fObj.skuType = skuType;
return fObj;
}
Gives the warning
Unexpected any. Specify a different type. #typescript-eslint/no-explicit-any)
I have tried to change ‘any’ to ‘unknown’, but then I get an error
Property 'formOfPayment' does not exist on type 'unknown'.ts(2339)
and
Property 'skuType' does not exist on type 'unknown'.ts(2339)
In another function
export function mergePurchaseWithFirestorePurchaseRecord(purchase: Purchase, firestoreObject: any) {
// Copy all keys that exist in Firestore but not in Purchase object, to the Purchase object (ex. userID)
Object.keys(firestoreObject).map(key => {
// Skip the internal key-value pairs assigned by convertToFirestorePurchaseRecord()
if ((purchase[key] === undefined) && (FIRESTORE_OBJECT_INTERNAL_KEYS.indexOf(key) === -1)) {
purchase[key] = firestoreObject[key];
}
});
}
I get the following warnings
Missing return type on function. #typescript-eslint/explicit-module-boundary-types
Argument 'firestoreObject' should be typed with a non-any type. #typescript-eslint/explicit-module-boundary-types
Unexpected any. Specify a different type. #typescript-eslint/no-explicit-any
In this function, if I change ‘any’ to ‘unknown’, I still get a warning for
Missing return type on function.
In another example I get an error for the use of ‘any’ in this constructor:
export default class PurchaseManager {
constructor(private purchasesDbRef: CollectionReference, private playDeveloperApiClient: any) { }
And again the warning is
Argument 'playDeveloperApiClient' should be typed with a non-any type. #typescript-eslint/explicit-module-boundary-types
In this case, if I follow the suggestion to use ‘unknown’ instead of ‘any’, then I get an error for ‘purchases’ in the following function:
const apiResponse = await new Promise((resolve, reject) => {
this.playDeveloperApiClient.purchases.products.get({
packageName: packageName,
productId: sku,
token: purchaseToken,
}, (err, result) => {
if (err) {
reject(this.convertPlayAPIErrorToLibraryError(err));
} else {
resolve(result.data);
}
})
});
The error created by changing ‘any’ to ‘unknown’ in the constructor is:
Property 'purchases' does not exist on type 'unknown'.ts(2339)
If I understand correctly, I could prevent all of these (and other similar) warnings without creating errors, by disabling explicit-module-boundary-types, and/or no-explicit-any for the entire file, but I am not sure if this is bad practice?
Is there another (better) way to specify the return types to avoid using ‘any’?
Or is it fine to just go ahead and disable explicit-module-boundary-types or no-explicit-any?
If you do not want to use any, you will have to declare an interface for your type and set that as the return type of the function.
You can do something like:
interface MyType {
formOfPayment: string;
skyType: SkyType;
foo: //<any other pre-existing type>;
}
Then you can use this as the return type of any other function that requires you to return an object with above properties. e.g.
function purchaseToFirestoreObject(purchase: Purchase, skuType: SkuType): MyType {
const fObj: any = {};
Object.assign(fObj, purchase);
fObj.formOfPayment = GOOGLE_PLAY_FORM_OF_PAYMENT;
fObj.skuType = skuType;
return fObj;
}

How to implement a type safe, phantom types based builder in typescript?

The idea is to allow a call to the .build() method only upon having all the mandatory parameters filled. So the constructor should be taught to do some validation.
If I understand you correctly, you have some kind of builder class, which by default doesn't have all the required parameters. And that class has a method, which updates its state. Only when all required parameters are set, only then build method should be available.
So first of all, we have T type which partial (all properties are optional).
On each update, we should return a new instance with type T & Record<K, T[K]> - it means optional T + one non-optional property.
Finally, we can use conditional types to allow build only when T extends Required<T>.
So the final solution:
function createBuilder<T>(initialData: T) {
return {
update: <K extends keyof T>(key: K, value: T[K]) => {
return createBuilder<T & Record<K, T[K]>>({
...initialData,
[key]: value
})
},
build: (() => {
//
}) as T extends Required<T> ? () => boolean : undefined
}
}
const builder1 = createBuilder<Partial<{
key1: string,
key2: number
}>>({})
builder1.build()
// Cannot invoke an object which is possibly 'undefined'
const builder2 = builder1.update('key1', 'abc')
builder2.build()
// Cannot invoke an object which is possibly 'undefined'
const builder3 = builder2.update('key2', 10)
builder3.build()
// No error
Hovewer, there is no point having this logic. If you can statically update the object, you probably can set all properties in the constructor.

Inconsistent error while storing a darray into a Shape

I have a shape like this
const type TFileInfo = shape(
'displayName' => string,
'givenName' => string,
'jobTitle' => string,
'businessPhones' => vec<string>
);
private Person::TFileInfo $person;
Now my constructor of the class looks like so
public function __construct(string $apiresponse) { // instance method
$json = \json_decode($response, /* associative = */ true);
TypeAssert\matches<self::TFileInfo>($json);
$this->person = $json; //OFFENDING LINE
$this->person['businessPhones1'] = "";
}
Now strangely the above code does not throw any error .
If I remove the offending line , then the last line throws a compile time error Expected nothing because the field 'businessPhones1' is not defined in this shape type, and this shape type does not allow unknown fields
What am I missing here ? Is there a better way to assign an API response to a typed variable ?
TypeAssert\matches doesn't prove that its argument is the type you specified, in contrast to the behavior of some other built-ins like is_null which are special-cased in the typechecker. Instead, it coerces the argument and returns it, so you need to move your standalone call to the assignment, i.e. $this->person = TypeAssert\matches<self::TFileInfo>($json);.
You might have expected a type error from the $this->person = $json assignment then, but in fact json_decode and some other unsafe built-in PHP functions are special-cased by the typechecker to be bottom types (convertible to anything) so they could be usable at all before type-assert. It remains this way today: see its type definition in the HHVM source, probably for compatibility.
One other interesting point about this case is that $this->person = $json coerces $this->person to a bottom type as well downstream of the binding. To my understanding, this is a specific behavior of the Hack typechecker to do this for a single level of property nesting, yet it preserves the types for properties of properties (the second example has_error):
<?hh // strict
class Box<T> { public function __construct(public T $v) {} }
function no_error<T>(Box<int> $arg): T {
$arg->v = json_decode('');
return $arg->v;
}
function has_error<T>(Box<Box<int>> $arg): T {
$arg->v->v = json_decode('');
return $arg->v->v;
}

Typescript Array.filter empty return

Problem statement
I've got problem with an object array I would like to get a sub object array from based on a object property. But via the Array.filter(lambda{}) all I get is an empty list.
The object is like:
export interface objectType1 {
someName: number;
someOtherName: string;
}
export interface ObjectType2 {
name: string;
other: string;
ObjectType1: [];
}
The method to get the subArray is:
private getSubArray(toDivied: ObjectType2[], propertyValue: string){
let list: ObjectType2[] = toDivied.filter((row:ObjectType2) => {
row.name === propertyValue
});
return list;
}
Analys
Namely two things been done ensure filter comparing works and that the data is "as expected".
Brekepoints in visual studio code
Via break points in the return and filter compareison I've inspected that the property value exists (by conditions on the break point) and that the "list" which is returned is empty.
I would like to point out that I use a Typescript linter which usally gives warning for the wrong types and undefined variable calls and such so I am quite sure it shouldn't be an syntax problem.
Tested via javascript if it works in chrome console
remove braces inside callback function
private getSubArray(toDivied: ObjectType2[], propertyValue: string){
let list: ObjectType2[] = toDivied.filter((row:ObjectType2) =>
row.name === propertyValue
);
return list;
}

TypeScript warning => TS7017: Index signature of object type implicitly has any type

I am getting the following TypeScript warning -
Index signature of object type implicitly has any type
Here is the code that cases the warning:
Object.keys(events).forEach(function (k: string) {
const ev: ISumanEvent = events[k]; // warning is for this line!!
const toStr = String(ev);
assert(ev.explanation.length > 20, ' => (collapsed).');
if (toStr !== k) {
throw new Error(' => (collapsed).');
}
});
can anyone determine from this code block why the warning shows up? I cannot figure it out.
If it helps this is the definition for ISumanEvent:
interface ISumanEvent extends String {
explanation: string,
toString: TSumanToString
}
You could add an indexer property to your interface definition:
interface ISumanEvent extends String {
explanation: string,
toString: TSumanToString,
[key: string]: string|TSumanToString|ISumanEvent;
}
which will allow you to access it by index as you do: events[k];. Also with union indexer it's better to let the compiler infer the type instead of explicitly defining it:
const ev = events[k];

Resources