Declare object type in class - javascript

I should create a js class respecting a json format
{
"rows": [{
"value": {
"comments": ${dInfo.description},
"Tags": [],
"metadataList": {
"names": [],
"metadata": {}
},
}]
}
I would like to know how to declare metadata.
I'm declaring like this actually.
export class Value {
comments: string;
Tags:string[];
metadataList:{
name:string[],
metadata: Object
}
}
Here metadataList is not typed which is not a best practice I think.
how could I declare metadataList with it's type ?

If metadata is a key/value store, you can make the type a little narrower than Object or {} by tightening up the value types:
class Value {
comments: string;
Tags:string[];
metadataList:{
name:string[],
metadata: { [key: string]: string }
}
}
const val = new Value();
val.metadataList = {
name: ['Example'],
metadata: {
key: 'value',
key2: 'value'
}
};
If you have more information about your metadata, I may be able to make it even narrower.

Related

Array.push TypeError: Cannot add property 0, object is not extensible

I have got a object called curNode like this
{
"name": "CAMPAIGN",
"attributes": {},
"children": []
}
I am trying to push to the object as shown below
curNode!.children!.push({
name: newNodeName,
children: [],
});
I get the below error
TypeError: Cannot add property 0, object is not extensible
at Array.push (<anonymous>)
If you copy curNode to the TS playground, and hover over the type, you'll see its type is:
type T = {
name: string;
attributes: {};
children: never[];
}
You can't add any elements to a never[] array.
In order to make the code work, you can declare a type and use it when you init curNode:
interface TNode {
name?: string
attributes?: object,
children?: TNode[]
}
const curNode : TNode = {
name: "CAMPAIGN",
attributes: {},
children: []
};
const newNodeName = "Foo"
curNode.children?.push({
name: newNodeName,
children: []
});
console.log(curNode);
// Output
//[LOG]: {
// "name": "CAMPAIGN",
// "attributes": {},
// "children": [
// {
// "name": "Foo",
// "children": []
// }
// ]
//}
link to TS Playground
Edit:
Another option is to use new Array<any> instead of [] when initializing the array:
const curNode = {
name: "CAMPAIGN",
attributes: {},
children: new Array<any>
};
const newNodeName = "Foo"
curNode.children?.push({
name: newNodeName,
children: []
});
console.log(curNode);
So in my case this post helped
Material-table TypeError: Cannot add property tableData, object is not extensible
As VLAZ mentioned, the object gets freezed, so I cloned the object using
const cloneData = structuredClone(object);
and everything started working.

Use name of type in other types on TS

I have types:
type TUser= {
id: number,
name: string,
}
type TFields = {
value: number,
name: string,
otherField: string,
}
type TTask = {
id: number,
title: string,
}
type TDataMethod = {
"TField": "fields",
"TTask": "tasks",
}
base on this types, how i can create type something like that (the part of the Type below is pseudocode):
type TResponse<T> = {
data: T extends TUser ? TUser[] : {[TDataMethod[T]]: T}
time: string,
}
for objects
const userResponse: TResponse<TUser> = {
data: [
id: 1,
name: "John",
],
time: "13 august 2022"
}
const taskResponse: TResponse<TTask> = {
data: {
tasks: {
id: 1,
title: "Some task",
}
},
time: "14 august 2022"
}
or i have one way - use extends declaration?
It is possible with some Typescript "programming".
For example, I have these interfaces.
interface User {
name: string;
age: number;
}
interface Bot {
name: string;
author: string;
}
The Metadata should be an array so we could iterate from it.
type Metadata = [
{
name: 'users'; // here's the property
type: User; // here's the type
},
{
name: 'bots';
type: Bot;
}
];
We don't actually could iterate from it. So, create an helper named ArrayUnshift which will unshift (remove first item) from the generic type. If the generic type (Array) is [first, ...rest], then return the rest so the first item is removed.
type ArrayUnshift<Array extends any[]> =
Array extends [infer First, ...infer Rest] ?
Rest : never;
Then we could itearate the Metadata. If the first Metadata.type is equal to generic type, then return the Metadata.name, if not recursive to itself but unshift the Metadata.
type MetadataProperty<T extends any, Data extends any[] = Metadata> =
Data[0]['type'] extends T ?
Data[0]['name'] : MetadataProperty<T, ArrayUnshift<Data>>;
Last, create ResponseData with MetadataProperty<T> as its property.
interface ResponseData<T extends object> {
time: string;
data: MetadataProperty<T> extends string ? {
[Key in MetadataProperty<T>]: T;
} : {
string: T; // fallback to avoid error
}
}
There's a repo that related to this topic, take a look to Type Challenges.
EDIT: Or you could simply use Extract utility as being said by #caTS.
You don't need to "iterate" over them; just get the elements as a union and use Extract: Extract<Metadata[number], { type: T }>["name"].

Proper way to declare json object (typescript)

I have the following json file with data:
[{
"sectionKey": "key1",
"sectionName": "name",
"content": []
},
{
"sectionKey": "key2",
"sectionName": "name",
"content": []
}
]
My question is: how to declare the content of the array in typescript? like "Type"[].
Please do not suggest using any[] since it removes all type checking provided by TypeScript.
interface CoolObject {
sectionKey: string;
sectionName: string;
content: Array<any>;
}
const greatArray: CoolObject[] = [
{
sectionKey: 'key1',
sectionName: 'name',
content: [],
},
{
sectionKey: 'key2',
sectionName: 'name',
content: [],
foobar: 'something' // not assignable to type 'CoolObject'
},
];
If it's the 'content' poperty you're trying to typehint you can use Array<YourType> or YourType[] too.

Typing a "complex" object with TypeScript

I'm trying to see if I can get VS Code to assist me when typing an object with predefined types.
A dish object can look like this:
{
"id": "dish01",
"title": "SALMON CRUNCH",
"price": 120,
"ingredients": [
"MARINERAD LAX",
"PICKLADE GRÖNSAKER",
"KRISPIG VITLÖK",
"JORDÄRTSKOCKSCHIPS",
"CHILIMAJONNÄS"
],
"options": [
{
"option": "BAS",
"choices": ["RIS", "GRÖNKÅL", "MIX"],
"defaultOption": "MIX"
}
]
}
My current attempt looks like this
enum DishOptionBaseChoices {
Rice = 'RIS',
Kale = 'GRÖNKÅL',
Mix = 'MIX',
}
type DishOption<T> = {
option: string
choices: T[]
defaultOption: T
}
type DishOptions = {
options: DishOption<DishOptionBaseChoices>[]
}
type Dish = {
id: string
title: string
price: number
ingredients: string[]
options: DishOptions
}
(not sure if some of them should be interface instead of type)
the plan is to make enums for all "types" of options. The auto-suggestion works fine with the id but not when writing the options.
Working solution
type ChoiceForBase = 'RIS' | 'GRÖNKÅL' | 'MIX'
type DishOption<OptionType, ChoiceType> = {
option: OptionType
choices: ChoiceType[]
defaultOption: ChoiceType
}
type Dish = {
id: string
title: string
price: number
ingredients: string[]
options: DishOption<'BAS', ChoiceForBase>[]
}
On type Dish you defined options: DishOptions, but then on type DishOptions you again defined a property options. So with you current types definition your options property looks like this:
const dish: Dish = {
options: {
options: [{
choices: ...
}]
}
}
If you want options to be an array of DishOption change your Dish definition:
type Dish = {
id: string
title: string
price: number
ingredients: string[]
options: DishOption<DishOptionBaseChoices>[]
}

Stringify typescript object to JSON with custom order

How can I serialize a typescript object to JSON string so that all properties are serialised in a specific order (not sorted) ?
The order can be interface/class order or if possible using experimental decorators !
Any idea ?
Example :
interface Root {
id: string;
name: string;
description: string;
childs: Child[];
}
interface Child {
id: string;
name: string;
description: string
}
const root = ...
JSON.stringify(root, null, 2);
Result :
{
"id": "1000",
"name": "root name",
"description": "root description",
"childs": [{
"id": "9000",
"name": "child name 1",
"description": "child description 1"
},{
"id": "9001",
"name": "child name 2",
"description": "child description 2"
}]
}
You could achieve this using experimental decorators, e.g. with the ReflectionCapabilities and Decorators classes from Angular.
First, declare a property decorator, like this:
export class Serialized {
}
export interface SerializedDecorator {
(obj: Serialized): TypeDecorator;
new(obj: Serialized): Serialized;
}
export const Serialized: SerializedDecorator =
makePropDecorator('Serialized', (args) => args, Serialized);
Your data model would look something like this:
class Root {
#Serialized({})
id: string;
#Serialized({})
name: string;
#Serialized({})
description: string;
#Serialized({})
childs: Child[];
}
class Child {
#Serialized({})
id: string;
#Serialized({})
name: string;
#Serialized({})
description: string
}
Now, to get property information and put together the string, do something like this:
export function stringifyOrdered(obj: any): string {
if(!obj || !obj.__proto__ || !obj.__proto__.constructor)
return "";
let serialized = "{";
const reflect = new ReflectionCapabilities();
// this gives you the ordered list of properties:
for (let propMetadata in reflect.propMetadata(obj.__proto__.constructor)) {
/* put together the stringified object,
recursively call the serialize function if the value type is object */
if(obj[propMetadata] instanceof Array){
serialized += `"${propMetadata}": [`;
for(let item of <Array<any>>obj[propMetadata]){
if(['string', 'number', 'boolean'].includes(typeof item)){
serialized += `"${item}", `
} else if (typeof item == 'object'){
serialized += `${stringifyOrdered(item)}, `
}
}
serialized = serialized.substr(0, serialized.length - 2);
serialized += "], ";
}else{
if(["string", "number", "boolean"].includes(typeof obj[propMetadata])){
serialized += `"${propMetadata}": "${obj[propMetadata]}", `
} else if (typeof obj[propMetadata] == 'object'){
serialized += `"${propMetadata}": ${stringifyOrdered(obj[propMetadata])}, `
}
}
}
serialized = serialized.substr(0, serialized.length - 2);
return serialized + "}";
}
I use ReflectionCapabilities and Decorators in my own project. You can get it from GitHub: https://github.com/angular/angular/blob/master/packages/core/src/reflection/reflection_capabilities.ts and https://github.com/angular/angular/blob/master/packages/core/src/util/decorators.ts
Update: I have tested above code and it works for me. Feel free to use it wherever you want.
A bit of background information: Property declarations get stripped away when transpiling from TypeScript to JavaScript. One way to preserve this information is to annotate all properties. This is only possible in classes, not in interfaces.

Categories

Resources