2
\$\begingroup\$

I have a list of raw data like following mockData and need to generate to a object[] format, some key-value pair are required from raw data like first, last, state. but some key-value pair are option like nickname. Eventually, I need to build a object array with some same key-value like raw data, and some key's value depends on another key's value like fullname, address. If there is a better way for me to do it and make it more dynamically? because I will have more than 20 different key-value set like fullname, address that need to be built by some function related on raw data

export const mockData: Human[] = [
  {
    first: 'name1',
    last: 'v1',
    state: 'ny',
  },
  {
    first: 'name2',
    last: 'v2',
    state: 'pa',
    nickname: 'rocketman',
  }]

export interface Human {
  first: string;
  last: string;
  state: string;
  nickname?: string;
}

export interface Student extends Human{
  fullname: string;
  address: string;
}

function setFullName(first: string, last: string): string {
    return `${first} ${last}`
}

function setAddress(first: string, last: string, state: string): string {
    return `${first} ${last} live a ${state}`
}

function buildStudentData(jsons: Human[]): Student[] {
  return jsons.map((json) =>  <Student> {
      first: json.first,
      last: json.last,
      nickname:  json.hasOwnProperty('nickname') ? json.nickname : json.first,
      fullname: setFullName(json.first, json.last),
      address: setAddress(json.first, json.last, json.state)})
}


console.log(buildStudentData(mockData));
\$\endgroup\$
0
\$\begingroup\$

You have kind of "logic" in your mapping (nickname, fullname and adress). And I assume that this will be the case also for the other data sets you mentioned. That makes generalizing hard. Especially when there is not much in common.

You COULD write a generator were you can configurate for each attribute the mapping strategy, but this would take more time then write the mapping of those 20-30 values directly.

I personally use in quite some cases classes instead of interfaces and then apply a public static "fromJSON(...)" or in your case "fromHuman(...)" method onto it. Its MORE code to write, but it makes everything explicit. And you are able to ensure that a object (a student) ALWAYS has a proper name. Such things make refactoring later easier. But as i said, the negative part is that you implement more code to gain this safety.

For example in the following code there is only ONE way to create a student. And if the primary identifier of this object (the firstname / lastname combination) is missing then it errors out.

export class Student implements Human {
  readonly first: string;
  readonly last: string;
  state: string;
  nickname?: string;
  fullname: string;
  address: string;

  private constructor(firstname: string, lastname: string{
    if(firstname && lastname){
      throw Error('Students must have a first and a lastname!');
    }
    this.first = firstname;
    this.last = lastname;
  }

  public static fromHuman(human: Human): Student {
    const student: Student = new Student(human.first, human.last);
    student.state= human.state;
    student.nickname = human.nickname | human.first
    // if you are using TSD 3.7 or above you should use the ?? operator
    // ...
  }
}

If some of the values may change, then i would think about NOT storing calculated values (like the adress) but use a GET method. The reason is, IF one of the values used for calculation (e.g. the state) changes, then a stored "adress" would have to get updated manually.

export class Student ...{
   // ...
   public get adress(): string{
      return `${this.first} ${this.last} live a ${this.state}`
    }
 }
}

PRO: Values get updated automaticly CON: Values have to get recalculated each time. This may be a performance issue, depending on the usage.

At the end (as always) it depends what you want to achieve. If you need a quick way to generate data, then i would personally stick to your way.
If you are using the objects heavily in your application, then i would think about how they are used from the business perspective (which values may not change, which may change often ,...) and treat them as Business Objects. And then guard the objects against misuse. Even if only you are using that code, this may guard you against mistakes and help you understand your code when you have to fix a bug or add a feature a few months later.

\$\endgroup\$
2
  • \$\begingroup\$ Those are great suggestion and explanation. However I was turned down by other developer about using class. Because he feels like it is not transparent enough which I do not really know why. But like you say. I will need to return lots of different combination of json data by calling different format of setAddress or setFullname function. Which bring me to only different between each buildstudentdata is fullname: setFullName(json.first, json.last), address: setAddress(json.first, json.last, json.state)}) } \$\endgroup\$ Feb 12 '20 at 4:02
  • \$\begingroup\$ Classes for these kind of things are quite "heavy". Therefor a lot of developers do not like to invest that much effort if they can achieve the result with an interface much easier. In my experience, as soon as the application gets a certain size, and if multiple developers are working on it, then the class approach will return the invested effort in no time. Also with business data models instead of DTOs the class explains the business usage of this data. But as always, the solution depends on the problem and the context around it. \$\endgroup\$
    – JanRecker
    Feb 12 '20 at 10:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.