0

Hi i need help in iterating through SubjectViewModel and check if the path of formfieldkeyPath matches the PropertyKey of each object in the array and return only true if the propery path value if null.

like for eg the formfieldkeyPath which is a string with the value department.subjectCost

export class SubjectViewModel {
    public id?: number;
    public SubjectInfo?: string;
    public studentId?: number;
    public department?:DepartmentViewModel;
    public studentBenefits?: studentBenefitViewModel[];
        

}

export class DepartmentViewModel{
    public id?: number;
    public departmentName?:string;
    public subjectCost?:number;
    public departmentBenefits?: departmentBenefitViewModel[];
        

}

SubjectViewModel =[
 {
   id:4,
   SubjectInfo:'xx',
   studentId:16,
   department:[
   {
     id:1,
     departmentName:'xxx',
     subjectCost:null,
     departmentBenefits:[{..},{..}]
   },
   {
     id:2,
     departmentName:'yyy',
     subjectCost:50,
     departmentBenefits:[{..},{..}]
   }],
   studentBenefits:[{..},{..}]
 }
]


i split the department.subjectCost into an array
const propertyPathArr = formfieldkeyPath .split('.'),


Selaka Nanayakkara
  • 3,296
  • 1
  • 22
  • 42
snoopy
  • 105
  • 2
  • 10
  • try using `Object.keys()` and comparing it against your paths. –  Apr 11 '21 at 19:28
  • can you make it clear which two properties you want to compare ? I assume it's SubjectCost vs something else in the list ! – Abiranjan Apr 11 '21 at 20:02
  • what is `PropertyKey` of each object and `formfieldkeyPath`? And why `SubjectViewModel` is an array ? Isn't it instance of `SubjectViewModel` class ? – Nur Apr 11 '21 at 23:44
  • Also Give us an example, What is your expected output ? And is `SubjectViewModel` your input ? – Nur Apr 11 '21 at 23:49

1 Answers1

0

I've had to do this a few times too. You're pretty close already with splitting by the period. From here, you'll just need to use bracket notation to iterate into each level of depth determined by that string, and then use map() to return the new array of matching property values.

The only complication I see between the data you are trying to retrieve and the formfieldkeyPath you want to use (department.subjectCost) is that department is the area you want to iterate through and subjectCost is the data we'll want to collect via map(). This will leave some ambiguity as to which property path "part" is the array to iterate through, and which is the data you want to collect when you loop through that array.

To resolve this, we will need to use a slightly different symbol to separate those two parts, the path to the array you want to loop through, and the path to the property you want to collect within that array. Essentially, we'll be constructing two nested arrays populated with the path names and then loop through those property path names to get the desired data.

For instance, we could transform obj.subobj.department->subjectCost into [['obj','subobj','department'],['subjectCost']] if your object was nested in two other objects obj and subobj. So to make this work for your simpler example, we'll just use department->subjectCost since we'll need to save the . character for objects with higher complexity.

** One other important note here is that because we'll be using the symbols . and -> to split our string, this will conflict with any objects keys that actually contain those exact strings in their property names, so be wary of that. If you do have properties with either of those symbols in their names, simply change the symbols used in my provided function below. Perhaps instead of . and ->, you can use .. and ... where two dots could represent a path (one level down) and the three dots would represent the split between the array's path and the path to the desired data you are collecting. In our case, it would be department...subjectCost.

Example 1: using your provided sample data

Here it is in action for department->subjectCost using the sample data you provided (. and ->):

Usage

iterPropPath(SubjectViewModel[0], 'department->subjectCost');
// -> [null, 50]

Full Example

const SubjectViewModel = [{
  id: 4,
  SubjectInfo: 'xx',
  studentId: 16,
  department: [{
      id: 1,
      departmentName: 'xxx',
      subjectCost: null,
      departmentBenefits: [{/**/}, {/**/}]
    }, {
      id: 2,
      departmentName: 'yyy',
      subjectCost: 50,
      departmentBenefits: [{/**/}, {/**/}]
  }],
  studentBenefits: [{/**/}, {/**/}]
}];

const iterPropPath = (obj, path) => {
  path = path.replace(/\[(\w+)\]/g, '.$1').replace(/^\./, '');
  const pathArray = path.split('->').map(e => e.split('.').filter(f => f)).filter(e => e && e.length);
  if (pathArray.length !== 2) {
    console.error('iterPropPath requires a valid object path to a nested array and a valid path within that array to the desired property value. Property path levels should be delimited by `.` and the array path/property path should be delimited by `->`.');
    return false;
  }
  const [arrPath, propPath] = pathArray;
  let arr = obj;
  let arrPathValid = true;
  arrPath.slice().forEach(step => arr.hasOwnProperty(step) ? (arr = arr[arrPath.shift(1)], !arrPath.length && (!Array.isArray(arr) || !arr.length) && (arrPathValid = false)) : (arrPathValid = false));
  if (!arrPathValid) {
    console.error(`Array path ${arrPath.join('.')} is invalid.`);
    return false;
  }
  return arr.map((e,iPropPath) => (iPropPath = propPath.slice(), propPath.forEach(step => e && e.hasOwnProperty(step) && (e = e[iPropPath.shift(1)])), e));
};

const formfieldkeyPath = 'department->subjectCost';
const subjectCosts = iterPropPath(SubjectViewModel[0], formfieldkeyPath);

console.log(subjectCosts);

This follows the path of the provided object SubjectViewModel[0] down the specified path to the array and then iterates through the array, returning a newly mapped array of the desired values, in this case [null, 50] (the subjectCost values).

Example 2: more complex example

Below is a deeper example of this function in action, which makes use bracket notation. In order to support bracket notation, I added a couple of replace statements inspired by @Alnitak's answer to a related question back in 2011:

Usage

iterPropPath(deepArray, 'entries.data->a[1].names[2]');
// -> ["Ron", "Eli", "Bre"]

Full Example

const deepArray = {
  id: 0,
  entries: {
    randomData: true,
    data: [
      { a: ['test1-1', { names: [ 'Tom', 'Kim', 'Ron' ] }], b: [['test1-2'], ['test1-3']]},
      { a: ['test2-1', { names: [ 'Jim', 'Lia', 'Eli' ] }], b: [['test2-2'], ['test2-3']]},
      { a: ['test3-1', { names: [ 'Tia', 'Jon', 'Bre' ] }], b: [['test3-2'], ['test3-3']]}
    ]
  }
};

const iterPropPath = (obj, path) => {
  path = path.replace(/\[(\w+)\]/g, '.$1').replace(/^\./, '');
  const pathArray = path.split('->').map(e => e.split('.').filter(f => f)).filter(e => e && e.length);
  if (pathArray.length !== 2) {
    console.error('iterPropPath requires a valid object path to a nested array and a valid path within that array to the desired property value. Property path levels should be delimited by `.` and the array path/property path should be delimited by `->`.');
    return false;
  }
  const [arrPath, propPath] = pathArray;
  let arr = obj;
  let arrPathValid = true;
  arrPath.slice().forEach(step => arr.hasOwnProperty(step) ? (arr = arr[arrPath.shift(1)], !arrPath.length && (!Array.isArray(arr) || !arr.length) && (arrPathValid = false)) : (arrPathValid = false));
  if (!arrPathValid) {
    console.error(`Array path ${arrPath.join('.')} is invalid.`);
    return false;
  }
  return arr.map((e,iPropPath) => (iPropPath = propPath.slice(), propPath.forEach(step => e && e.hasOwnProperty(step) && (e = e[iPropPath.shift(1)])), e));
};

const path = 'entries.data->a[1].names[2]';
const namesFromDeepArray = iterPropPath(deepArray, path);

console.log(namesFromDeepArray);
Brandon McConnell
  • 5,776
  • 1
  • 20
  • 36