I am using TypeScript and Axios. I have the following classes:
class Item {
private a: string = '';
private b: string = '';
public constructor(options: any = {}) {
Object.assign(this, options);
}
}
class Items {
private items: Array<Item> = [];
public constructor(options: any = {}) {
Object.assign(this, options);
}
public getItems(): Array<Item> {
return this.items;
}
}
My backend responses are similar to:
{
"items": [
{"a": "a1", "b": "b1", "c": "c1"},
{"a": "a2", "b": "b2", "c": "c2"}
]
}
As a result I want to get an Items class that contains all Items with the fields a and b (c should be ignored).
I tried the following:
Axios.get<Items>('path-to-backend-api').then((response) => {
const items: Items = (new Items(response.data));
});
However, the resulting items object's inner array is empty. I also wanted to invoke JS's map on the response.data.items array, but couldn't get the type system to work. const items: response.data.getItems() failed as well. I tried about a dozen other less typed approaches, but couldn't find a solution.
How could I construct a Array<Item object based on the above JSON response?
Okay, multiple things to unpack here.
The generic type passed to Axios.get<T>() should represent the shape of incoming deserialised data i.e. response.data. You are passing Items which is a class you are creating a new instance of using the incoming data. They are separate and should be declared separately.
Whilst you can use a class as an interface, it doesn't really suit your implementation to do so.
response.data.getItems() is impossible with your current implementation. If you really want to be able to do this (you probably don't), you could add a transformResponse function to your Axios request config that creates a new class instance from the data before delivering it in the response of Axios.get().
With minimal changes to your original code, here's what I'd suggest doing to type correctly and make your code more flexible:
interface ItemData {
a: string;
b: string;
}
interface ItemsData {
items: ItemData[];
}
class Item implements ItemData {
a: string = '';
b: string = '';
constructor(options: ItemData) {
Object.assign(this, options);
}
}
class Items {
items: Item[] = [];
constructor(options: ItemsData) {
Object.assign(this.items, options.items.map(item => new Item(item));
}
getItems(): Item[] {
return this.items;
}
}
Axios.get<ItemsData>('path-to-backend-api').then((response) => {
const items: Items = new Items(response.data);
// now you can use the class methods
const list = items.getItems();
});
Related
My project has lots of serializable classes where I need to convert some attributes before using the class. For example, this is the Json result of a Rest-Call:
{
"class": "ProductGroup",
"name": "RootGroup",
"children": [
{
"class": "ProductGroup",
"name": "RootGroup",
"children": [
// .... and so on... you get the idea.
]
}
]
}
I've created a serializable.ts class which basically makes sure, that I habe an object of type ProductGroup instead of Object:
class ProductGroup {
class:string
name:string
constructor(json?: any) {
Object.assign(this, json)
}
}
var myObject = new ProductGroup(jsonResult)
but to make sure that the children are also of type ProductGroup, I have to convert them as well. This is, how I currently do it:
class ProductGroup {
class:string
name:string
constructor(json?: any) {
Object.assign(this, json)
}
private _children:ProductGroup[]
get children():ProductGroup[] { return this._children }
set children(_:any[]) { this._children = _.map(x => new ProductGroup(x) }
}
var myObject = new ProductGroup(jsonResult)
My problem is: I need those three lines of code for every kind of child object of the Json-Response, to make sure, it's converted to the correct class. So I was wondering if there might be some kind of define that helps me re-use this three-line-code-block.... something like this (pseudocode)
const typesafe = <T>(name:string, ctor:Type<T>) => {
private ${'_'+name}:T[]
get ${name}():T[] { return this.${'_'+name} }
set ${name}(_:any[]) { this.${'_'+name} = _.map(x => new ctor(x) }
}
class ProductGroup {
class:string
name:string
constructor(json?: any) {
Object.assign(this, json)
}
typesafe('children', ProductGroup)
typesafe('products', Product)
// ... I think you get the point :)
}
var myObject = new ProductGroup(jsonResult)
is there any way to achieve this?
see code above
google / stackoverflow search for existing answers
I have an enum in typescript like below:
export enum XMPPElementName {
state = "state",
presence = "presence",
iq = "iq",
unreadCount = "uc",
otherUserUnreadCount = "ouc",
sequenceID = "si",
lastSequenceID = "lsi",
timeStamp = "t",
body = "body",
message = "message"
}
And wants to de-structure its value, How can we do this in Typescript?
const { uc, ouc, msg, lsi, si, t, body } = XMPPElementName;
update
As #amadan mentioned, we can use Assigning to new variable names as in Mozilla doc say Destructuring_assignment, like below:
Assigning to new variable names
A property can be unpacked from an object and assigned to a variable with a different name than the object property.
const o = {p: 42, q: true};
const {p: foo, q: bar} = o;
console.log(foo); // 42
console.log(bar); // true
And the method is very good to solve this problem, but if you need to access all items without the need to explicitly define them, you can either on of these two mentiond tag1 tag2
const { uc, ouc, msg, lsi, si, t, body } = XMPPElementName;
This doesn't work because XMPPElementName doesn't have an element named uc (and equivalently for others). If you explicitly name your keys, it will work:
const {
unreadCount: uc,
otherUserUnreadCount: ouc,
message: msg,
lastSequenceID: lsi,
sequenceID: si,
timeStamp: t,
body: body,
} = XMPPElementName;
it will work. Alternately, you can just use variables with names that are equal to the keys, not the values:
const {
unreadCount,
otherUserUnreadCount,
message,
lastSequenceID,
sequenceID,
timeStamp,
body,
} = XMPPElementName;
You want an enum value-to-value map. Like you've said enum in JS is just a POJO. You can create a utility type to help generate the correct type.
type EnumValueMap<T extends { [k: string]: string }> = { [K in T[keyof T]]: K }
function convertEnumValuesToObject<T extends { [k: string]: string }>(enumerable: T): EnumValueMap<T> {
return (Object as any).fromEntries(Object.values(enumerable).map(v => [v, v]))
}
Playground Link
As we know, in typescript an enum is like a plain old javascript object(at-least what the playground js-output is showing or the log showing):
one way is using a function which generates a new object with {value:value} structure like below:
export function convertEnumValuesToObject<T>(enumObj: T): { [index: string]: T[keyof T] } {
const enum_values = Object.values(enumObj);
return Object.assign({}, ...enum_values.map(_ => ({ [_]: _ })));
}
const { uc, ouc, msg, lsi, si, t, body } = convertEnumValuesToObject(
XMPPElementName
);
It would be great to see answers in typescript?
This may be helpful for anyone looking for a quick and easy answer - yes you can (at least as of now). This works for enums with and without assigned values as far as I can tell.
enum MyEnum {
One,
Two,
Three
}
const { One, Two, Three } = myEnum;
console.log({ One, Two, Three }) // {One: 0, Two: 1, Three: 2}
enum Status {
None = '',
Created = 'CREATED',
Completed = 'COMPLETED',
Failed = 'FAILED',
}
const { None, Created, Completed, Failed } = Status;
console.log(None, Created, Completed, Failed) // '', 'CREATED', 'COMPLETED, 'FAILED'
Please write me back if I'm wrong or you found any weirdness when testing yourself.
I am trying to read from a JSON file, but it is formatted this way:
[
{
"INV#" : "123"
},
{
"INV#" : "456"
}
]
That variable name does not meet JavaScript or TypeScript naming. So I have the following object:
export interface Invoice {
invoiceNumber: number;
}
class myclass {
private inv: Invoice[];
}
How can I read from that json file into this object. Making changes on the database variable is not an option. Changing the name of my interface variable is not possible in JavaScript. Somehow they have to match, but there must be a way to make the object property into mine.
// let's assume that you've read the json into some variable
let response = [
{
"INV#" : "123"
},
{
"INV#" : "456"
}
];
export interface Invoice {
invoiceNumber: number;
}
class myclass {
private inv: Invoice[];
constructor(_response: any[]) {
// do not forget to check _response for null/undefined, if it is an array or not, etc.
this.inv = _response.map(obj => <Invoice>{
invoiceNumber: parseInt(obj['INV#'])
});
}
}
// usage:
let some_var = new myclass(response);
// now some_var contains array of invoices internally.
I'm pretty new with these languages and I'm sure it's not so complicated but could not find how to walk through class instance variables dynamically.
I'm trying to display instance's variables in a table using Angular. I'd like to write a single function that will work with any class.
Let's say I have a workbook class:
export class workbookEntity {
public name: string;
public creationDate: string;
constructor() {
this.name = 'my work book name';
this.creationDate = '2018/01/26';
}
}
And let's say I want to get the names and values of the variables of an instance of this class in another class' function:
export class showClassComponent {
// some code here
whenSubmitedInHtmlForm(className: string): void {
// do something to walk through instance
// with className parameter = 'workbookEntity'
}
// more code there
}
How would you walk through the instance to get each variable's name and value to get something like this?
[
{
name: 'name',
value: 'my work book name'
},
{
name: 'creationDate',
value: '2018/01/26'
}
]
There's no concept of reflection, so much in Typescript, so you can't neccessarily do a type lookup. But you might be able to do something along the lines of...
export class showClassComponent {
var classes = [ { name: 'workBookEntity', value: new WorkBookEntity() }]
whenSubmitedInHtmlForm(className: string): void {
let c = classes.filter(class => class.name == className)[0];
if(c) {
let ret = [];
for (var key in c) {
if (c.hasOwnProperty(key)) {
ret.push({ name: key, value: c[key] });
}
}
return ret;
}
}
// more code there
}
If you want to be able to retain accurate type information, you can create a pick utility function and then just import it into your class module wherever you need to use it.
let workBook = new WorkbookEntity();
export function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
let newObj = {} as Pick<T, K>;
for (const key of keys) {
newObj[key] = obj[key];
}
return newObj;
}
console.log(pick(workBook, ['name', 'creationDate']));
// log: { name: 'my work book name', creationDate: '2018/01/26' }
Then you can import the first function into another utility function if you to be able to handle multiple objects.
export function pickObjects<T extends object, K extends keyof T>
(objects: T[], keys: K[]): Pick<T, K>[] {
return objects.reduce((result, obj) => [...result, pick(obj, keys)], []);
}
let workbooks = pickObjects([workBook, workBook2], ['name']);
workbooks[0].name // this exists
workbooks[0].age // Typescript error.
The editor will show that age doesn't exist since we didn't pick it
I'm writing a typescript class for using with immutable map
class NavigableObject<T> {
constructor(private obj: T, private path: string[] = []) { }
To<R>(p: (x: T) => R): NavigableObject<R> {
return new NavigableObject<R>(p(this.obj),
this.path.concat(this.getPropName(p(this.obj))));
}
getPath() {
return this.path;
}
private getPropName(value) {
for (var item in this.obj) {
if (this.obj[item] === value) {
return item;
}
}
}
}
let test = {
a: {
a1: 1,
a2: 1
},
b: {
b1: 1,
b2: 2
}
}
let navigableTest = new NavigableObject(test);
navigableTest.To(m => m.b).To(m => m.b2).getPath(); // = ["b", "b2"]
navigableTest.To(m => m.a).To(m => m.a2).getPath(); // = ["a", "a1"] <-- I selected a2, tho
There is a problem with getPropName method. When obj has two properties with same value, only the first property will be matched.
Does anyone know how to work around this?
You could use this way of getting the property name:
class NavigableObject<T> {
constructor(private obj: T, private path: string[] = []) { }
To<R>(p: (x: T) => R): NavigableObject<R> {
return new NavigableObject<R>(p(this.obj),
this.path.concat(this.getPropName(p)));
}
getPath() {
return this.path;
}
private static propertyRegEx = /\.([^\.;]+);?\s*\}$/;
private getPropName(propertyFunction: Function) {
return NavigableObject.propertyRegEx.exec(propertyFunction.toString())[1];
}
}
let test = {
a: {
a1: 1,
a2: 1
},
b: {
b1: 1,
b2: 2
}
}
let navigableTest = new NavigableObject(test);
navigableTest.To(m => m.b).To(m => m.b2).getPath(); // = ["b", "b2"]
navigableTest.To(m => m.a).To(m => m.a2).getPath(); // = ["a", "a2"]
The future is here. As per Tim Perry's link, TypeScript has now added keyof, which is a great feature for getting the available properties of a class.
Usage, as per the TypeScript documentation:
interface Person {
name: string;
age: number;
location: string;
}
type K1 = keyof Person; // "name" | "age" | "location"
type K2 = keyof Person[]; // "length" | "push" | "pop" | "concat" | ...
type K3 = keyof { [x: string]: Person }; // string
There's nothing easy you can do here with your current querying approach. It's ambiguous which property you're selecting, so there's going to be no easy way to get the right path. It's not that your code is 'wrong' now as such, it's just that there's two possible correct answers, and it's picked one arbitrarily.
You can change the rules on which of the possible keys it picks, but there are no sensible rules that will reliably get you the right single answer. Alternatively you could return all the possible answers, and have an ambiguous path, but it doesn't seem like that does what you're looking for.
There might be one option by doing crazy things like parsing the function provided with Esprima or even regular expressions to work out which properties are being grabbed, but it's generally going to be a bad idea. That's likely to be complex and fragile, unreliable unless you can guarantee the exact shape of the code you'll be provided in To(), and run pretty slowly too.
If you do want to be able to select properties like this and know the path used for certain, you'll have to give your To function the key of the property, not just a function to get the value it holds. It's a less elegant API, but it's the only sensible way you're going to get the behaviour you're looking for:
class NavigableObject<T> {
constructor(private obj: T, private path: string[] = []) { }
To<R>(p: string): NavigableObject<R> {
return new NavigableObject<R>(this.obj[p],
this.path.concat(p));
}
getPath() {
return this.path;
}
}
let test = {
a: {
a1: 1,
a2: 1
},
b: {
b1: 1,
b2: 2
}
}
let navigableTest = new NavigableObject(test);
navigableTest.To('b').To('b2').getPath();
navigableTest.To('a').To('a2').getPath();
In future it might be possible to do this with type safety in TypeScript, but not right now. This PR is looking into exactly these issues, but it's still under some discussion yet, so there'll be a while until it's implemented. Note that it's still proposing the string-based approach from the example above, it's just the type system will check that string constants are valid property names for the types used.
Don't terminate the for on the first value found, and return an array of names with matching values. I leave to you the propblem of how to handle multiple names.
private getPropName (value) {
var items = [];
for (var item in this.obj) {
if (this.obj[item] === value)
items.push (item);
return items;
}