Why class method in typescript "is not a function" when class is not initialized directly by "new" [duplicate] - reactjs

I read a JSON object from a remote REST server. This JSON object has all the properties of a typescript class (by design). How do I cast that received JSON object to a type var?
I don't want to populate a typescript var (ie have a constructor that takes this JSON object). It's large and copying everything across sub-object by sub-object & property by property would take a lot of time.
Update: You can however cast it to a typescript interface!

You can't simple cast a plain-old-JavaScript result from an Ajax request into a prototypical JavaScript/TypeScript class instance. There are a number of techniques for doing it, and generally involve copying data. Unless you create an instance of the class, it won't have any methods or properties. It will remain a simple JavaScript object.
While if you only were dealing with data, you could just do a cast to an interface (as it's purely a compile time structure), this would require that you use a TypeScript class which uses the data instance and performs operations with that data.
Some examples of copying the data:
Copying AJAX JSON object into existing Object
Parse JSON String into a Particular Object Prototype in JavaScript
In essence, you'd just :
var d = new MyRichObject();
d.copyInto(jsonResult);

I had the same issue and I have found a library that does the job : https://github.com/pleerock/class-transformer.
It works like this :
let jsonObject = response.json() as Object;
let fooInstance = plainToClass(Models.Foo, jsonObject);
return fooInstance;
It supports nested children but you have to decorate your class's member.

In TypeScript you can do a type assertion using an interface and generics like so:
var json = Utilities.JSONLoader.loadFromFile("../docs/location_map.json");
var locations: Array<ILocationMap> = JSON.parse(json).location;
Where ILocationMap describes the shape of your data. The advantage of this method is that your JSON could contain more properties but the shape satisfies the conditions of the interface.
However, this does NOT add class instance methods.

If you are using ES6, try this:
class Client{
name: string
displayName(){
console.log(this.name)
}
}
service.getClientFromAPI().then(clientData => {
// Here the client data from API only have the "name" field
// If we want to use the Client class methods on this data object we need to:
let clientWithType = Object.assign(new Client(), clientData)
clientWithType.displayName()
})
But this method will not work on nested objects, sadly.

I found a very interesting article on generic casting of JSON to a Typescript Class:
http://cloudmark.github.io/Json-Mapping/
You end up with following code:
let example = {
"name": "Mark",
"surname": "Galea",
"age": 30,
"address": {
"first-line": "Some where",
"second-line": "Over Here",
"city": "In This City"
}
};
MapUtils.deserialize(Person, example); // custom class

There is nothing yet to automatically check if the JSON object you received from the server has the expected (read is conform to the) typescript's interface properties. But you can use User-Defined Type Guards
Considering the following interface and a silly json object (it could have been any type):
interface MyInterface {
key: string;
}
const json: object = { "key": "value" }
Three possible ways:
A. Type Assertion or simple static cast placed after the variable
const myObject: MyInterface = json as MyInterface;
B. Simple static cast, before the variable and between diamonds
const myObject: MyInterface = <MyInterface>json;
C. Advanced dynamic cast, you check yourself the structure of the object
function isMyInterface(json: any): json is MyInterface {
// silly condition to consider json as conform for MyInterface
return typeof json.key === "string";
}
if (isMyInterface(json)) {
console.log(json.key)
}
else {
throw new Error(`Expected MyInterface, got '${json}'.`);
}
You can play with this example here
Note that the difficulty here is to write the isMyInterface function. I hope TS will add a decorator sooner or later to export complex typing to the runtime and let the runtime check the object's structure when needed. For now, you could either use a json schema validator which purpose is approximately the same OR this runtime type check function generator

TLDR: One liner
// This assumes your constructor method will assign properties from the arg.
.map((instanceData: MyClass) => new MyClass(instanceData));
The Detailed Answer
I would not recommend the Object.assign approach, as it can inappropriately litter your class instance with irrelevant properties (as well as defined closures) that were not declared within the class itself.
In the class you are trying to deserialize into, I would ensure any properties you want deserialized are defined (null, empty array, etc). By defining your properties with initial values you expose their visibility when trying to iterate class members to assign values to (see deserialize method below).
export class Person {
public name: string = null;
public favoriteSites: string[] = [];
private age: number = null;
private id: number = null;
private active: boolean;
constructor(instanceData?: Person) {
if (instanceData) {
this.deserialize(instanceData);
}
}
private deserialize(instanceData: Person) {
// Note this.active will not be listed in keys since it's declared, but not defined
const keys = Object.keys(this);
for (const key of keys) {
if (instanceData.hasOwnProperty(key)) {
this[key] = instanceData[key];
}
}
}
}
In the example above, I simply created a deserialize method. In a real world example, I would have it centralized in a reusable base class or service method.
Here is how to utilize this in something like an http resp...
this.http.get(ENDPOINT_URL)
.map(res => res.json())
.map((resp: Person) => new Person(resp) ) );
If tslint/ide complains about argument type being incompatible, just cast the argument into the same type using angular brackets <YourClassName>, example:
const person = new Person(<Person> { name: 'John', age: 35, id: 1 });
If you have class members that are of a specific type (aka: instance of another class), then you can have them casted into typed instances through getter/setter methods.
export class Person {
private _acct: UserAcct = null;
private _tasks: Task[] = [];
// ctor & deserialize methods...
public get acct(): UserAcct {
return this.acct;
}
public set acct(acctData: UserAcct) {
this._acct = new UserAcct(acctData);
}
public get tasks(): Task[] {
return this._tasks;
}
public set tasks(taskData: Task[]) {
this._tasks = taskData.map(task => new Task(task));
}
}
The above example will deserialize both acct and the list of tasks into their respective class instances.

Assuming the json has the same properties as your typescript class, you don't have to copy your Json properties to your typescript object. You will just have to construct your Typescript object passing the json data in the constructor.
In your ajax callback, you receive a company:
onReceiveCompany( jsonCompany : any )
{
let newCompany = new Company( jsonCompany );
// call the methods on your newCompany object ...
}
In in order to to make that work:
1) Add a constructor in your Typescript class that takes the json data as parameter. In that constructor you extend your json object with jQuery, like this: $.extend( this, jsonData). $.extend allows keeping the javascript prototypes while adding the json object's properties.
2) Note you will have to do the same for linked objects. In the case of Employees in the example, you also create a constructor taking the portion of the json data for employees. You call $.map to translate json employees to typescript Employee objects.
export class Company
{
Employees : Employee[];
constructor( jsonData: any )
{
$.extend( this, jsonData);
if ( jsonData.Employees )
this.Employees = $.map( jsonData.Employees , (emp) => {
return new Employee ( emp ); });
}
}
export class Employee
{
name: string;
salary: number;
constructor( jsonData: any )
{
$.extend( this, jsonData);
}
}
This is the best solution I found when dealing with Typescript classes and json objects.

In my case it works. I used functions
Object.assign (target, sources ...).
First, the creation of the correct object, then copies the data from json object to the target.Example :
let u:User = new User();
Object.assign(u , jsonUsers);
And a more advanced example of use. An example using the array.
this.someService.getUsers().then((users: User[]) => {
this.users = [];
for (let i in users) {
let u:User = new User();
Object.assign(u , users[i]);
this.users[i] = u;
console.log("user:" + this.users[i].id);
console.log("user id from function(test it work) :" + this.users[i].getId());
}
});
export class User {
id:number;
name:string;
fullname:string;
email:string;
public getId(){
return this.id;
}
}

While it is not casting per se; I have found https://github.com/JohnWhiteTB/TypedJSON to be a useful alternative.
#JsonObject
class Person {
#JsonMember
firstName: string;
#JsonMember
lastName: string;
public getFullname() {
return this.firstName + " " + this.lastName;
}
}
var person = TypedJSON.parse('{ "firstName": "John", "lastName": "Doe" }', Person);
person instanceof Person; // true
person.getFullname(); // "John Doe"

Personally I find it appalling that typescript does not allow an endpoint definition to specify
the type of the object being received. As it appears that this is indeed the case,
I would do what I have done with other languages, and that is that I would separate the JSON object from the class definition,
and have the class definition use the JSON object as its only data member.
I despise boilerplate code, so for me it is usually a matter of getting to the desired result with the least amount of code while preserving type.
Consider the following JSON object structure definitions - these would be what you would receive at an endpoint, they are structure definitions only, no methods.
interface IAddress {
street: string;
city: string;
state: string;
zip: string;
}
interface IPerson {
name: string;
address: IAddress;
}
If we think of the above in object oriented terms, the above interfaces are not classes because they only define a data structure.
A class in OO terms defines data and the code that operates on it.
So we now define a class that specifies data and the code that operates on it...
class Person {
person: IPerson;
constructor(person: IPerson) {
this.person = person;
}
// accessors
getName(): string {
return person.name;
}
getAddress(): IAddress {
return person.address;
}
// You could write a generic getter for any value in person,
// no matter how deep, by accepting a variable number of string params
// methods
distanceFrom(address: IAddress): float {
// Calculate distance from the passed address to this persons IAddress
return 0.0;
}
}
And now we can simply pass in any object conforming to the IPerson structure and be on our way...
Person person = new Person({
name: "persons name",
address: {
street: "A street address",
city: "a city",
state: "a state",
zip: "A zipcode"
}
});
In the same fashion we can now process the object received at your endpoint with something along the lines of...
Person person = new Person(req.body); // As in an object received via a POST call
person.distanceFrom({ street: "Some street address", etc.});
This is much more performant and uses half the memory of copying the data, while significantly reducing the amount of boilerplate code you must write for each entity type.
It simply relies on the type safety provided by TypeScript.

Use a class extended from an interface.
Then:
Object.assign(
new ToWhat(),
what
)
And best:
Object.assign(
new ToWhat(),
<IDataInterface>what
)
ToWhat becomes a controller of DataInterface

If you need to cast your json object to a typescript class and have its instance methods available in the resulting object you need to use Object.setPrototypeOf, like I did in the code snippet bellow:
Object.setPrototypeOf(jsonObject, YourTypescriptClass.prototype)

Use 'as' declaration:
const data = JSON.parse(response.data) as MyClass;

An old question with mostly correct, but not very efficient answers. This what I propose:
Create a base class that contains init() method and static cast methods (for a single object and an array). The static methods could be anywhere; the version with the base class and init() allows easy extensions afterwards.
export class ContentItem {
// parameters: doc - plain JS object, proto - class we want to cast to (subclass of ContentItem)
static castAs<T extends ContentItem>(doc: T, proto: typeof ContentItem): T {
// if we already have the correct class skip the cast
if (doc instanceof proto) { return doc; }
// create a new object (create), and copy over all properties (assign)
const d: T = Object.create(proto.prototype);
Object.assign(d, doc);
// reason to extend the base class - we want to be able to call init() after cast
d.init();
return d;
}
// another method casts an array
static castAllAs<T extends ContentItem>(docs: T[], proto: typeof ContentItem): T[] {
return docs.map(d => ContentItem.castAs(d, proto));
}
init() { }
}
Similar mechanics (with assign()) have been mentioned in #Adam111p post. Just another (more complete) way to do it. #Timothy Perez is critical of assign(), but imho it is fully appropriate here.
Implement a derived (the real) class:
import { ContentItem } from './content-item';
export class SubjectArea extends ContentItem {
id: number;
title: string;
areas: SubjectArea[]; // contains embedded objects
depth: number;
// method will be unavailable unless we use cast
lead(): string {
return '. '.repeat(this.depth);
}
// in case we have embedded objects, call cast on them here
init() {
if (this.areas) {
this.areas = ContentItem.castAllAs(this.areas, SubjectArea);
}
}
}
Now we can cast an object retrieved from service:
const area = ContentItem.castAs<SubjectArea>(docFromREST, SubjectArea);
All hierarchy of SubjectArea objects will have correct class.
A use case/example; create an Angular service (abstract base class again):
export abstract class BaseService<T extends ContentItem> {
BASE_URL = 'http://host:port/';
protected abstract http: Http;
abstract path: string;
abstract subClass: typeof ContentItem;
cast(source: T): T {
return ContentItem.castAs(source, this.subClass);
}
castAll(source: T[]): T[] {
return ContentItem.castAllAs(source, this.subClass);
}
constructor() { }
get(): Promise<T[]> {
const value = this.http.get(`${this.BASE_URL}${this.path}`)
.toPromise()
.then(response => {
const items: T[] = this.castAll(response.json());
return items;
});
return value;
}
}
The usage becomes very simple; create an Area service:
#Injectable()
export class SubjectAreaService extends BaseService<SubjectArea> {
path = 'area';
subClass = SubjectArea;
constructor(protected http: Http) { super(); }
}
get() method of the service will return a Promise of an array already cast as SubjectArea objects (whole hierarchy)
Now say, we have another class:
export class OtherItem extends ContentItem {...}
Creating a service that retrieves data and casts to the correct class is as simple as:
#Injectable()
export class OtherItemService extends BaseService<OtherItem> {
path = 'other';
subClass = OtherItem;
constructor(protected http: Http) { super(); }
}

You can create an interface of your type (SomeType) and cast the object in that.
const typedObject: SomeType = <SomeType> responseObject;

FOR JAVA LOVERS
Make POJO class
export default class TransactionDTO{
constructor() {
}
}
create empty object by class
let dto = new TransactionDto() // ts object
let json = {name:"Kamal",age:40} // js object
let transaction: TransactionDto = Object.assign(dto,JSON.parse(JSON.stringify(json)));//conversion

https://jvilk.com/MakeTypes/
you can use this site to generate a proxy for you. it generates a class and can parse and validate your input JSON object.

I used this library here: https://github.com/pleerock/class-transformer
<script lang="ts">
import { plainToClass } from 'class-transformer';
</script>
Implementation:
private async getClassTypeValue() {
const value = await plainToClass(ProductNewsItem, JSON.parse(response.data));
}
Sometimes you will have to parse the JSON values for plainToClass to understand that it is a JSON formatted data

In the lates TS you can do like this:
const isMyInterface = (val: any): val is MyInterface => {
if (!val) { return false; }
if (!val.myProp) { return false; }
return true;
};
And than user like this:
if (isMyInterface(data)) {
// now data will be type of MyInterface
}

I ran into a similar need.
I wanted something that will give me easy transformation from/to JSON
that is coming from a REST api call to/from specific class definition.
The solutions that I've found were insufficient or meant to rewrite my
classes' code and adding annotations or similars.
I wanted something like GSON is used in Java to serialize/deserialize classes to/from JSON objects.
Combined with a later need, that the converter will function in JS as well, I ended writing my own package.
It has though, a little bit of overhead. But when started it is very convenient in adding and editing.
You initialize the module with :
conversion schema - allowing to map between fields and determine
how the conversion will be done
Classes map array
Conversion functions map - for special conversions.
Then in your code, you use the initialized module like :
const convertedNewClassesArray : MyClass[] = this.converter.convert<MyClass>(jsonObjArray, 'MyClass');
const convertedNewClass : MyClass = this.converter.convertOneObject<MyClass>(jsonObj, 'MyClass');
or , to JSON :
const jsonObject = this.converter.convertToJson(myClassInstance);
Use this link to the npm package and also a detailed explanation to how to work with the module: json-class-converter
Also wrapped it for
Angular use in :
angular-json-class-converter

Pass the object as is to the class constructor; No conventions or checks
interface iPerson {
name: string;
age: number;
}
class Person {
constructor(private person: iPerson) { }
toString(): string {
return this.person.name + ' is ' + this.person.age;
}
}
// runs this as //
const object1 = { name: 'Watson1', age: 64 };
const object2 = { name: 'Watson2' }; // age is missing
const person1 = new Person(object1);
const person2 = new Person(object2 as iPerson); // now matches constructor
console.log(person1.toString()) // Watson1 is 64
console.log(person2.toString()) // Watson2 is undefined

You can use this npm package. https://www.npmjs.com/package/class-converter
It is easy to use, for example:
class UserModel {
#property('i')
id: number;
#property('n')
name: string;
}
const userRaw = {
i: 1234,
n: 'name',
};
// use toClass to convert plain object to class
const userModel = toClass(userRaw, UserModel);
// you will get a class, just like below one
// const userModel = {
// id: 1234,
// name: 'name',
// }

You can with a single tapi.js!
It's a lightweight automapper that works in both ways.
npm i -D tapi.js
Then you can simply do
let typedObject = new YourClass().fromJSON(jsonData)
or with promises
axios.get(...).as(YourClass).then(typedObject => { ... })
You can read more about it on the docs.

There are several ways to do it, lets examine a some options:
class Person {
id: number | undefined;
firstName: string | undefined;
//? mark for note not required attribute.
lastName?: string;
}
// Option 1: Fill any attribute and it would be accepted.
const person1= { firstName: 'Cassio' } as Person ;
console.log(person1);
// Option 2. All attributes must assign data.
const person2: Person = { id: 1, firstName: 'Cassio', lastName:'Seffrin' };
console.log(person2);
// Option 3. Use partial interface if all attribute not required.
const person3: Partial<Person> = { firstName: 'Cassio' };
console.log(person3);
// Option 4. As lastName is optional it will work
const person4: Person = { id:2, firstName: 'Cassio' };
console.log(person4);
// Option 5. Fill any attribute and it would be accepted.
const person5 = <Person> {firstName: 'Cassio'};
console.log(person5 );
Result:
[LOG]: {
"firstName": "Cassio"
}
[LOG]: {
"id": 1,
"firstName": "Cassio",
"lastName": "Seffrin"
}
[LOG]: {
"firstName": "Cassio"
}
[LOG]: {
"id": 2,
"firstName": "Cassio"
}
[LOG]: {
"firstName": "Cassio"
}
It will also work if you have an interface instead a Typescript class.
interface PersonInterface {
id: number;
firstName: string;
lastName?: string;
}
Play this code

I think that json2typescript is a good alternative
https://www.npmjs.com/package/json2typescript
You can convert json to Class model with a simple model class with annotations
Used in project

You can cast json to property like this
class Jobs {
constructor(JSONdata) {
this.HEAT = JSONdata.HEAT;
this.HEAT_EAF = JSONdata.HEAT_EAF;
}
}
var job = new Jobs({HEAT:'123',HEAT_EAF:'456'});

This is a simple and a really good option
let person = "{"name":"Sam","Age":"30"}";
const jsonParse: ((key: string, value: any) => any) | undefined = undefined;
let objectConverted = JSON.parse(textValue, jsonParse);
And then you'll have
objectConverted.name

Related

Typescript generic function which gives component properties based on other variables

This one has been puzzling me for a while, trying to create a type-safe email service.
I have an enum of possible template names:
enum TemplateName {
EXAMPLE_TEMPLATE = "EXAMPLE TEMPLATE"
...
}
I have an object of default settings per template:
type EmailConfig<X = React.ComponentType> = {
html: X
subject: string
...
}
type EmailMapping: EmailConfig = {
[key in TemplateName]: EmailConfig
}
const Emails = {
[TemplateName.EXAMPLE_TEMPLATE]: {
html: TestTemplate, // THIS IS A REACT FUNCTIONAL COMPONENT
subject: "This is a test",
...rest
}
...
}
My Templates look like so:
export interface TestTemplateProps {
title?: string
firstName?: string
preview?: string
headline?: string
site?: string
...
}
export const TestTemplate: React.FC<TestTemplateProps> = ({
title = 'Test Email',
site = 'My Website',
preview = 'Important Information from My Site',
firstName = 'there',
headline,
children,
}) => {
return (
...
)
}
I have a generic function I want to be able to pass in an enum value and all the props of the Component that relates to that enum value.
FOR EXAMPLE
sendEmail(TemplateName.EXAMPLE_TEMPLATE, { ... })
Where { ... } is typed to TestTemplateProps interface
My current attempt at sendEmail looks like this:
async sendEmail<X extends keyof EmailMapping>(
template: X,
opts: React.ComponentProps<typeof Emails[X]['html']>
) {
...
}
I've tried just playing around (honestly I am just guessing at what to change at this point) and this is the closest I have come so far.
When I call sendEmail with above code, it forced me to pass one of the Enums, but then in the opts the only "typing" that appears is "children?" and none of the other properties in the TestTemplateProps so I think I'm close!
TIA
Yes. but the first thing that I wanna know is ... are you making a class for data loading and passing data in your interfaces with static function or static constructor for async data loading... if so ... I want to know the
the exact problem you're facing...

Typescript : convert post request body to map

I'm programming an rest api with node js and typescript and for create user, my api recieve a json post :
import {Request, Response, Router} from "express";
import {User} from '../../../models/user.model';
import {createUser} from '../../../factories/user.factory';
export default [
{
path: "/api/v1/user/create",
method: "post",
handler: [
async (req: Request, res: Response) => {
createUser(new User(req.body.user));
res.status(200).send(req.body);
}
]
}
];
For exemple, I send that :
{
"user": {
"email": "test#gmail.com",
"password": "12345678",
"firstName": "Jérémy"
}
}
I would like create an object "User" with the object req.body.user :
import {Timestamp} from './timestamp.model';
export class User {
id: bigint | undefined;
email: string | undefined;
password: string | undefined;
firstName: string | undefined;
lastName: string | undefined;
pseudo: string | undefined;
birthDate: Timestamp | undefined;
lastEditDate: Timestamp | undefined;
creationDate: Timestamp | undefined;
googleAuthToken: string | undefined;
language: string | undefined;
profileAvatarUrl: string | undefined;
profileBannerUrl: string | undefined;
primaryLightColor: string | undefined;
secondaryLightColor: string | undefined;
primaryDarkColor: string | undefined;
secondaryDarkColor: string | undefined;
constructor(array: object) {
console.log(array);
// #ts-ignore
console.log(array.gg);
// #ts-ignore
this.id = array.id;
// #ts-ignore
this.email = array.email;
// #ts-ignore
this.password = array.password;
// #ts-ignore
this.firstName = array.firstName;
// #ts-ignore
this.lastName = array.lastName;
// #ts-ignore
this.pseudo = array.pseudo;
// #ts-ignore
this.birthDate = array.birthDate;
// #ts-ignore
this.lastEditDate = array.lastEditDate;
// #ts-ignore
this.creationDate = array.creationDate;
// #ts-ignore
this.googleAuthToken = array.googleAuthToken;
// #ts-ignore
this.language = array.language;
// #ts-ignore
this.profileAvatarUrl = array.profileAvatarUrl;
// #ts-ignore
this.profileBannerUrl = array.profileBannerUrl;
// #ts-ignore
this.primaryLightColor = array.primaryLightColor;
// #ts-ignore
this.secondaryLightColor = array.secondaryLightColor;
// #ts-ignore
this.primaryDarkColor = array.primaryDarkColor;
// #ts-ignore
this.secondaryDarkColor = array.secondaryDarkColor;
// #ts-ignore
}
toMap() {
return {
"id": this.id,
"email": this.email,
"firstName": this.firstName,
"lastName": this.lastName,
"pseudo": this.pseudo,
"profileAvatarUrl": this.profileAvatarUrl,
"birthDate": this.birthDate,
"lastEditDate": this.lastEditDate,
"creationDate": this.creationDate,
"language": this.language,
"googleAuthToken": this.googleAuthToken,
"profileBannerUrl": this.profileBannerUrl,
"primaryLightColor": this.primaryLightColor,
"secondaryLightColor": this.secondaryLightColor,
"primaryDarkColor": this.primaryDarkColor,
"secondaryDarkColor": this.secondaryDarkColor,
}
}
}
I have put all this "// #ts-ignore" because if not , I've this error :
src/models/user.model.ts(27,25): error TS2339: Property 'id' does not
exist on type 'object'. src/models/user.model.ts(28,32): error TS2339:
Property 'email' does not exist on type 'object'.
src/models/user.model.ts(29,35): error TS2339: Property 'password'
does not exist on type 'object'. src/models/user.model.ts(30,36):
error TS2339: Property 'firstName' does not exist on type 'object'.
src/models/user.model.ts(31,35): error TS2339: Property 'lastName'
does not exist on type 'object'.
My question is : How correctly make my class user for not have to put all this "// #ts-ignore" ?
Thank's in advance.
Jérémy.
I have a different suggestion which I find nice in typescript and started using intensively. Instead of creating a class for your user you can define it as an interface.
export interface User {
email: string,
password: string,
firstName: string,
lastName: string,
// etc
}
and then simply do:
const user = req.body.user as User;
It's faster and cleaner to type as long as you use these just for creating domain model objects with no business logic.
EDIT:
IF you need to stick with class then try using any
type.
export class user {
constructor(userDto: any) {
// your logic
}
}
new User(req.body.user);
I'm particularly likes Dan's solution, is clean and also is fast. But if he ToMap function is need it, you can consider use https://lodash.com/ is a very handy library, helps with arrays, object mappings, deep cloning .
Regards
PD: you also can use indexer secondaryDarkColor =array.['secondaryDarkColor']
There are a few things that might be worth clearing up that are clouding the solution to this one.
First thing is that your toMap method is somewhat unnecessary, as an ES6 class is a template for creating an object, so creating an instance of your class with the new keyword gives you back an object, and the toMap method doesn't actually return a User object even though it conforms to the type. If you want to return a User, you'd need to modify your signature to read:
toMap(): User { ... }
This notifies the compiler that you are intending to return a User, otherwise, the compiler just sees the object and infers it to be a concrete object with the properties you've defined.
Additionally, your constructor has the following signature:
constructor(array: object) { ... }
For one, calling your variable array is bound to cause confusion down the road, as to someone reading your code, they'll assume that they're working with an array, but you've typed it as object.
As far as the type errors go, you're getting them because TypeScript's object is because it is represents the non-primitive type, and actually has no properties at all so when you attempt to access one, the compiler can't access it.
With your current code, you could replace object with any and it would most likely compile, but I don't think you'd want that.
There is a much more concise method to accomplish what you're after, you can actually define your attributes and their types by setting them in the constructor, like so:
class User {
constructor(
public id?: string, // or private
public email?: string
) {}
}
const user = new User("abc123", "this#example.com")
console.log(user)
# >>> User: { "id": "abc123", "email": "this#example.com" }
Also note that you can use a question mark to indicate that an input is optional.
If you want to accept a json object as an input instead of putting everything in your constructor, you can always define a static method on the class that accepts an any as an input and returns an instance of the class.
See fiddle for example.

How to write mobx action methods in separate files and import them to the actual mobx class having observable variables?

I have a mobx class which has an observable variable named dataObject. This observable variable is an object and its structure is:
{
name:string;
dataType:string;
value: string;
description:string;
.
.
.
.
.
..... total around 45 such properties
}
Each object property has an action method to update it. For example
#action updateName = (name:string) =>{
this.dataObject.name = name;
}
It is difficult to maintain all such 45 action methods in same class. Is there any way to write these methods in a separate ts file and import them to the current file?
EDIT: Adding a part of dataObject interface along with few of its action methods
interface IDataObject{
name:string;
dataType:string;
value:any;
valueType:VALUE_TYPE|any;
hasCustomValueType:boolean;
customValueType:string|undefined;
description:string;
isRegistered:boolean;
associatedVariableDetails:IVariableDetails[];
hasMultipleValues:boolean;
associatedService:SERVICE;
}
enum VALUE_TYPE{
MAJOR = 'major',
MED = 'med',
MINOR = 'minor'
}
enum SERVICE{
PUSH_IN = 'pushin',
PULL_OUT = 'pullout'
}
interface IVariableDetails{
variableName:string;
varirbleDataType:string;
variableValue:any;
hasArray:boolean;
isDeletable:boolean;
}
//////////////
method to update dataType
#action updateDataType = (dataType:string) =>{
this.dataObject.dataType = dataType;
if(dataType === 'string'){
this.dataObject.value = ''
}
else if(dataType === 'boolean'){
this.dataObject.value = false
}
else if(dataType === 'integer'){
this.dataObject.value = 0
}
}
methods to modify associatedVariableDetails
#action addVariableDetails = (variableDetails:IVariableDetails) =>{
this.dataObject.associatedVariableDetails.push(variableDetails);
}
#action updateMultipleValueState = (hasMultipleValues:boolean) =>{
this.dataObject.hasMultipleValues = hasMultipleValues;
if(!hasMultipleValues){
this.dataObject.associatedVariableDetails = this.dataObject.associatedVariableDetails[0];
}
}
Create an interface for the object that you want as the observable and add an index signature.
This looks something like this:
interface foo {
[key: string]: type;
bar: string;
}
Then pass the key to your update action and update the value like object[key] = newValue.
Example:
#action update = (key: string, value: type) => {
object[key] = value;
}
This will allow you to use one single function to patch any value into your observable object.
This is a good article that explains index signatures in depth: https://basarat.gitbooks.io/typescript/docs/types/index-signatures.html
To answer your original question though. You could create 45 functions and export all of them in an object and import them into this file, but that solution may be overkill and tedious to maintain if types or fields change. I can show an example of that if this solution does not work. The solution I have explained above is the more efficient way of handling object patches.
EDIT:
I apologize for the delay in editing my answer. I have made edits to your code above showing the approach I would be taking with this kind of object you have. Here is the edit:
interface IDataObject{
[key: string]: string | boolean | VALUE_TYPE | SERVICE | IVariableDetails[];
name: string;
dataType: string;
value: any; // THIS SHOULD BE STRICTLY DEFINED
valueType: VALUE_TYPE|any; // THIS SHOULD ALSO BE STRICTLY DEFINED
hasCustomValueType: boolean;
customValueType?: string;
description: string;
isRegistered:boolean;
associatedVariableDetails: IVariableDetails[];
hasMultipleValues: boolean;
associatedService: SERVICE;
}
enum VALUE_TYPE{
MAJOR = 'major',
MED = 'med',
MINOR = 'minor'
}
enum SERVICE{
PUSH_IN = 'pushin',
PULL_OUT = 'pullout'
}
interface IVariableDetails{
variableName:string;
varirbleDataType:string;
variableValue:any;
hasArray:boolean;
isDeletable:boolean;
}
#action updateField = (key: string, value: string | boolean | VALUE_TYPE | SERVICE) => {
this.dataObject[key] = value;
}
#action addVariableDetails = (variableDetails: IVariableDetails) => {
this.dataObject.associatedVariableDetails.push(variableDetails);
}
#action updateMultipleValueState = (hasMultipleValues: boolean) => {
this.dataObject.hasMultipleValues = hasMultipleValues;
if(!hasMultipleValues){
this.dataObject.associatedVariableDetails = this.dataObject.associatedVariableDetails[0];
}
}
What I have done here is exposed a single method to manipulate the semi primitive values that do not need extra work after, they just want new values. Then I would create more complex #actions as needed based on the type of work you need to do on your object. As shown the updateField action does not allow you to use it with a VariableDetails type of parameter which will make it so typescript complains if you try to send a variableDetails parameter to that action.
I have also noted that the any attributes should definitely be some type if the rest of the object has strict typings. If you are going to have any types in your object you might as well make an interface like interface IAny { [key: string]: any; } and use that everywhere.
I hope this can be of help, and again I apologize for the gap in responses.

How to fetch value from array which came from json in Angular 5 app?

I have the following JSON definitions:
export class Company {
name: string;
trips : Trip[] = [];
}
export class Trip{
id: number;
name: string;
}
I am able to see the trips in the console using:
console.log(this.company);
In the component I have the following method:
if(this.company) {
Object.keys(this.company.trips).forEach((data) => {
console.log(data);
});
}
What I am getting in the console is the trip's properties names which is "id" and "number".
I would like to know how to access the value.
According to your data structure, you should not even try to do Object.keys what you should be doing as the Trip is an object, is something like following in which you treat the iteration object as a real Trip object
if(this.company && this.company.trips){
this.company.trips.forEach((trip:Trip) => {
console.log(trip.id + '\n' + trip.name);
});
}
if you have any issue with this piece of cod then make sure you are correctly doing your deserialization process and objects are getting cast properly.

TypeScript + React fetch array with different types

I have three types
class Media {
private _id: number;
//getters and setters
}
class Serial extends Media {
private _watched_series: number;
//getters and setters
}
class Movie extends Media {
private _watched: boolean;
//getters and setters
}
And backend have the same structure. Backend return Array<Media>, but contains child types.
I'm trying this:
ListsApi.mediasFromList(list.id).then((response: Response) => {
response.json().then((value: Array<Movie | Serial>) => {
this.setState({medias: value});
});
});
But when I try use typeof I always get object.
What am I doing wrong? I'm trying write function in media fromJson to create object from raw json object, but then I do not understand how to determine the type of a raw object.
Thanks in advance for the answer.
When trying to determine the type of a raw JSON object, you have to manually check the properties of the object to test if it matches a specific type. The following function can be used to determine if your media is of the Movie type:
isMovie(input: Movie | Serial): input is Movie {
return input.hasOwnProperty('_watched');
}
And then when you are testing if it is Movie or Serial type:
if (this.isMovie(media)) {
// media will now be treated as type `Movie`
}

Resources