0

So I have a api call that brings back information about a user:

data: {
  name: 'Some One',
  age: 100,
  gender: 'male'
  // etc
}

And I'm setting this to a class object.

class User {
  public data: any = {}
  constructor(data: any):void {
    this.updateData(data)
  }

  updateData(data) {
    for (const property in data) {
      if (Object.prototype.hasOwnProperty.call(data, property) &&
         typeof data[property] !== 'function') {
          this.data[property] = data[property]
       }
    }
  }
}

So when you do a const user = new User(userInfoFromApi) and console.log(user), it sends up looking like the same way it came in from the API.

What I want to do is reference user.name instead of user.data.name. Is that possible?

Something I tried was setting

const self = this

And then self[property] = data[property] but Typescript did NOT like that.

2 Answers 2

1
interface IUser {
  name: string;
  age: number;
  gender: "male" | "female";
}

class Base<T> {
  public readonly data: T;
  constructor(data: T) {
    this.data = data;
  }

  // i suppose updateData allow update partly
  public updateData(data: Partial<T>) {
    for (const property in data) {
      if (
        Object.prototype.hasOwnProperty.call(data, property) &&
        typeof data[property] !== "function"
      ) {
        this.data[property] = data[property]!;
      }
    }
  }
}

// first way use getter setter
class User extends Base<IUser> implements IUser {
  public get name() {
    return this.data.name;
  }
  public get age() {
    return this.data.age;
  }
  public get gender() {
    return this.data.gender;
  }
  public set name(name) {
    this.data.name = name;
  }
  public set age(age) {
    this.data.age = age;
  }
  public set gender(gender) {
    this.data.gender = gender;
  }
}

const user1 = new User({ name: "", gender: "female", age: 10 });
user1.updateData({ name: "part of value" });
user1.age = 2;
console.log(user1.data); // { name: 'part of value', gender: 'female', age: 2 }
console.log(user1.name); // part of value

// second way use Object.defineProperty
function create<T extends object>(data: T): Base<T> & T {
  const target = new Base(data);
  for (const property in data) {
    if (
      Object.prototype.hasOwnProperty.call(data, property) &&
      typeof data[property] !== "function"
    ) {
      Object.defineProperty(target, property, {
        get: function (this: Base<T>) {
          return this.data[property];
        },
        set: function (this: Base<T>, value) {
          this.data[property] = value;
        },
      });
    }
  }
  return target as never;
}



const user2 = create<IUser>({ name: "", gender: "female", age: 10 });
user2.updateData({ name: "part of value" });
user2.age = 2;
console.log(user2.data); // { name: 'part of value', gender: 'female', age: 2 }
console.log(user2.name); // part of value
Sign up to request clarification or add additional context in comments.

3 Comments

+1 just for the amount of effort but its too much boilerplate. The API calls have a ton of data coming back and sometimes its unknown what is sent.
second way use Object.defineProperty is the way to deal withconst data = create<any>(unkownDataValue);
I tried that, and for whatever reason, the extended classes don't pick up those properties (because they don't actually get added). You have to explicitly define the property and update it for it to show up in the extended classes.
0

You can do (this as any)['some_random_properties'] but this basically bypassed the type checking provided so you have to pretty careful about that.

I would suggest to define the properties exactly in class because that is actually what the type checking is for.

4 Comments

There is an extra layer to this I didn't mention, this is really the Base class. And there are many other classes that are extending it. So to define the property of each class would be time consuming (plus at times we don't know what the API is sending).
@RizaKhan Perhaps you could provide the error message to show what exactly is the problem. Also, if User is the base class, you could just define the properties in User and all extended classes will inherit the properties.
@JacySky There is no error. I just want to reference the properties as user.name instead of user.data.name (dynamically added, which after further investigating seems impossible). Not a big deal if it isn't possible, thought I'd ask.
@RizaKhan That would be (user as any)['name']. As long as you cast it to any it would be working like vanilla js.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.