I'm relatively new to the Typescript world, and I'm just working on a test app to get my self used to it. So I have this weird (?) issue with type restriction 'not working'.
I have an array defined in a class like member field this:
listings: Array<ICryptListingItem> = [];
And the interface is:
export interface ICryptListingItem {
name: string;
something: number;
}
Why is the compiler fine with doing:
this.listings = listings.data.map((listing) => {
return {
name: listing.name
}
});
The objects returned from listings.data.map is not implementing the interface the array has as it's type? What am I not getting here?
Thanks in advance.
TypeScript does handle this automatically; your code sample is missing some information. For example:
export interface ICryptListingItem {
name: string;
something: number;
}
class MyThing {
listings: Array<ICryptListingItem> = [];
doSomething() {
const listings = {
data: [
{ name: "the keeper" },
{ name: "the seeker" }
]
};
// Error here, as expected
this.listings = listings.data.map((listing) => {
return {
name: listing.name
}
});
}
}
Probably the type of either listings or listings.data is any, so the result of the map call is also any; any is then always an allowed type to assign to this.listings.
Related
I'm trying to figure out a way of typehinting dynamically in Typescript. Depending on the object I receive, I want to create an object of a certain interface type (based on some property in the received object). Here's a very basic example:
interface ITest {
location: string;
record: Record<string,unknown>[];
}
const test = {
location: "Victoria",
record: [
{
name: "matt",
age: 29,
is_single: false,
}
]
};
if(test.location === "Victoria") {
const interfaceName = 'ITest';
newMessage: interfaceName = test;
}
With this example in mind I'm wondering how to correctly type hint ITest using the string value interfaceName.
I do know that simply passing the string value of the interface name of the interface is incorrect, but that's sort of the desired effect. I'm not an expert at Typescript and would appreciate some help!
type ITest = {
location: 'Victoria';
record: Record<string, SomeType>[];
} | {
location: 'Georgia';
record: Record<string, OtherType>[];
}
if (test.location === "Victoria") {
// `test.record` is of type `Record<string, SomeType>[]` in this block
} else {
/ `test.record` is of type `Record<string, OtherType>[]` in this block
}
// inital types
interface Source {
id: string;
version: number; // discard
masterVariant: MasterVariant;
}
interface MasterVariant {
id: number; // discard
sku?: string;
}
// desired "lighter" types
interface Target {
id: string;
masterVariant: MasterVariantLight;
}
interface MasterVariantLight {
sku?: string;
}
to remove version property we can use the following
export class Convert {
public static toTarget(source: Source): Target {
const { version, ...result } = source;
return result;
}
}
can you see a way to discard masterVariant.id using above transformation?
export class Convert {
public static toTarget(source: Source): Target {
const { version, masterVariant: { id, ...rest }, ...result } = source;
return { id: result.id, masterVariant: { ...rest } };
}
}
Playground
While pure destructuring is possible here, I think it'd be clearer to use one more line to construct the required masterVariant format.
Having a class that never gets instantiated doesn't make much sense either - a plain function would be more suitable for the task (which is fine, unlike in Java).
const toTarget = (source: Source) => {
const { masterVariant, ...origSource } = source;
const newMasterVariant = { sku: masterVariant.sku };
return { ...origSource, masterVariant: newMasterVariant };
};
I have this sample of code experimenting with mixins in TypeScript. However, it is not returning what I am expecting.
It should give me: User ({"id":3,"name":"Lorenzo Delaurentis"}).
Instead, I am getting: Function ({"id":3,"name":"Lorenzo Delaurentis"}).
The line let Name = Class.constructor.name should give me User, but it is not. Am I missing something obvious here?
type ClassConstructor<T> = new(...args: any[]) => T
function withDebug<C extends ClassConstructor<{
getDebugValue(): object
}>>(Class: C) {
return class extends Class {
constructor(...args: any[]) {
super(...args)
}
debug() {
let Name = Class.constructor.name
let value = this.getDebugValue()
return `${Name} (${JSON.stringify(value)})`
}
}
}
class DebugUser {
constructor(
private id: number,
private firstName: string,
private lastName: string
) {}
getDebugValue() {
return {
id: this.id,
name: `${this.firstName} ${this.lastName}`
}
}
}
let User = withDebug(DebugUser)
let user = new User(3, 'Lorenzo', "Delaurentis")
console.log(user.debug())
P.S. I compiled with tsc mixins --target ES6. Otherwise, I get an error: error TS2339: Property 'name' does not exist on type 'Function'.
You want just Class.name. The Class.constructor is Function.
Recently I tried final version of Notion API. Using typescript I made a retrieve block children request:
(async () => {
const blockId = process.env.BLOCK_ID;
const response = await notion.blocks.children.list({
block_id: blockId,
page_size: 50,
});
console.log(response.results[1].to_do);
})();
Error message
For some reason typescript tells me that to_do doesn't exist in type (PartialBlockObjectResponse | BlockObjectResponse). I looked at type definition and... it was there:
declare type BlockObjectResponse = {
// ...
{
type: "to_do";
to_do: {
rich_text: Array<RichTextItemResponse>;
color: ApiColor;
checked: boolean;
};
object: "block";
id: string;
created_time: string;
created_by: {
id: IdRequest;
object: "user";
};
last_edited_time: string;
last_edited_by: {
id: IdRequest;
object: "user";
};
has_children: boolean;
archived: boolean;
}
// ...
}
I tried making type guard
function isToDo(value: PartialBlockObjectResponse | BlockObjectResponse): value is BlockObjectResponse {
return "to_do" in value;
} /* Error: TS2304: Cannot find name 'PartialBlockObjectResponse'. */
and importing type from package
import type {PartialBlockObjectResponse} from "#notionhq/client/build/src/api-endpoints";
// Error: Module '"#notionhq/client/build/src/api-endpoints"' declares 'PartialBlockObjectResponse' locally, but it is not exported.
Neither helped.
(Frustratingly) That package does not export its types. There's an open issue about it in the repo.
But you can work around this using a generic with your predicate function:
TS Playground
import {Client} from '#notionhq/client';
declare const notion: Client;
declare const blockId: string;
function isTodo <T extends Record<string, unknown>>(obj: T): obj is T & { type: 'to_do' } {
return 'type' in obj && obj.type === 'to_do';
}
(async () => {
const response = await notion.blocks.children.list({block_id: blockId, page_size: 50});
for (const result of response.results) {
if (isTodo(result)) {
result.to_do; /*
^^^^^
is now this type:
{
rich_text: RichTextItemResponse[];
color: ApiColor;
checked: boolean;
}
*/
}
}
})()
My answer assumes that you cloned this notion-sdk-typescript-starter repo.
The way I solved this issue was to go to the api-endpoints.d.ts file and replace PartialBlockObjectResponse (inside the ListBlockChildrenResponse type) with a new type that matches the desired response structure you are looking for. Here is what that looks like:
export declare type ListBlockChildrenResponse = {
type: "block";
block: EmptyObject;
object: "list";
next_cursor: string | null;
has_more: boolean;
results: Array<DesiredBlockObjectResponse | BlockObjectResponse>;
// ^^^ here is where PartialBlockObjectResponse is deleted
// and replaced
The new type that I implemented looks like this:
export declare type DesiredBlockObjectResponse = {
type: "block";
block: {
type:string,
object: string,
id: string,
parent: { type: string, page_id: string },
// ... more info if desired
}
object: "block"
};
I hope this helps!
I am trying to select an object array of nested object inside an array of o
export interface IDetails{
_id?: string;
order?: number;
detaildesc?: string;
}
resl:any[];
var data={
locations: string;
id: string;
ordermain: number;
value: string;
details :[IDetails]
}
I am trying to get list of all "order" under details:
resl = this.data.forEach(a=> { a.details.map(x=>x.order});
I'm getting the following error:
type void is not assignable to type any[] for resl .
When I just make var resl, i get undefined error.
Please let me know how to fix that so I get array of details.order
I checked around on the stackoverflow for possible solution couldnt find one that solves the issue.
Thanks
You can extract all orders with
var ordersArr = data.map(x => {
return x.details.map(y => {
return y.order;
});
});
ordersArr would be an array of array. To flatten it youcan write
var result = [].concat.apply([], ordersArr);
result would be array of all order values
You can also do the same thing with forEach instead of using map
this.data.forEach(x => {
x.details.forEach(y => {
if (y.order) {
this.resl.push(y.order);
}
});
});
Your code seems to be not correct, so I assumed this is what you expected
export interface IDetails {
_id?: string;
order?: number;
detaildesc?: string;
}
export class Something {
resl: any[];
data: {
locations: string;
id: string;
ordermain: number;
value: string;
details: IDetails[];
}[];
aFunction() {
this.resl = this.data.map(a => { a.details.map(x => x.order});
}
}
and map, not forEach will returned a transformed array