'How to recursively check property types in an object

I'm writing a function that takes in an object (A), compares it with another object (B) and creates a new object (C). Both A and B objects are symmetrical (same amount of properties, same keys) but the type of their values can differ. Eg: A.amount can be '11' while B.amount is a number. The end result should be an object C that has the values from A with the types from B.

In the function I came up with, I'm looping through all properties in object A, checking the type of the properties in object B with a switch/case statement, applying type conversion (like .toString()) and adding the corrected property to object C.

I would like the code to be recursive but as soon as there are nested objects involved I do not know how to reach the properties in B nor create the equivalent in C.

Example of an A object:

const data = {
    a: "15",
    b: "foo",
    c: false,
    d: {
          da: 99,
          db: [{
                 dba: "1.2",
                 dbb: true
              }]
       }
    }

Example of a B object:

const targetType = {
    a: 27,
    b: "foofoo",
    c: true,
    d: {
          da: "bar",
          db: [{
                 dba: 4,
                 dbb: false
              }]
       }
    }

Example of a C object -values from A, types from B:

const finalObject = {
    a: 15,
    b: "foo",
    c: false,
    d: {
          da: "99",
          db: [{
                 dba: 1.2,
                 dbb: true
              }]
       }
    }

My function:

//data = object A
//targetType = object B
export function typeConversion(data: any, targetType: any) {
  const resultObj = {};
  Object.keys(data).forEach((key) => {
    switch (typeof targetType[key]) {
      case 'string':
        resultObj[key] = data[key].toString();
        break;
      case 'boolean':
        resultObj[key] = data[key] ? 'Yes' : 'No';
        break;
      case 'number':
        resultObj[key] = Number(data[key]);
        break;
      //here is where it gets tricky:
      case 'object':
        if (Array.isArray(data[key])) {
          const dataArray: any = [];
          data[key].forEach((o) => {
            //all objs in the array have the same shape, hence comparing to [0]
            dataArray.push(typeConversion(o, targetType[key][0]));
          });
          resultObj[key] = dataArray;
        } else {
          const dataObj = {};
          Object.keys(data[key]).forEach((subkey) => {
            dataObj[subkey] = typeConversion(subkey, targetType[key][subkey]);
          });
          resultObj[key] = dataObj;
        }
        break;
    }
  });
  return resultObj;
}

console.log(typeConversion(data, targetType))

The moment there is a nested object and typeConversion is called recursively, it will fail to find the path to the property in object B. I could add an optional parameter to the function for the 'parent property', but that would only work for one level of depth.

If there is a way to make this recursive, it can't be by coding the path to targetType[like][this].

TypeScript Playground example

All ideas welcome, perhaps I'm approaching the whole thing wrong.



Solution 1:[1]

I made some changes in your function to recursively create the final object. Although this is not Typescript specific problem, I wonder what is the use case for this :)

const transform = (data, targetType) => {
  let finalObject = {};
  const keys = Object.keys(data);
  
  for (const key of keys) {
    // If key is Array [], recursively add each element in Array
    if (data[key] instanceof Array) {
      finalObject[key] = [];
      for (let i = 0; i < data[key].length; i++) {
        const res = transform(data[key][i], targetType[key][i]);
        finalObject[key].push(res);
      }
    }
    // If key is Object {}, recursively add Object keys
    else if (data[key] instanceof Object) {
      finalObject[key] = transform(data[key], targetType[key]);
    }
    // If key is Primitive, we can directly add key
    else {
      switch (typeof targetType[key]) {
        case 'string':
          finalObject[key] = data[key].toString();
          break;
        case 'boolean':
          finalObject[key] = data[key] ? 'Yes' : 'No';
          break;
        case 'number':
          finalObject[key] = Number(data[key]);
          break;
      }
    }
  }

  return finalObject;
};

const data = {
  a: "15",
  b: "foo",
  c: false,
  d: {
    da: 99,
    db: [{
      dba: "1.2",
      dbb: true
    }]
  }
}

const targetType = {
  a: 27,
  b: "foofoo",
  c: true,
  d: {
    da: "bar",
    db: [{
      dba: 4,
      dbb: false
    }]
  }
}

const finalObject = transform(data, targetType);
console.log('Final Object : ', finalObject);

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1