skip to Main Content

I am working on an edit form for my company and someone on my team decided to change the formatting of our API payload return without consulting the rest of the team and now we are playing catchup.

Since the API called we have to post the value back to the DB are not set up for nested objects, I need to flatten it so I can manipulate the values and submit it.

Here is the new nested structure

    {
       "status": 1, 
       "data" : {
            "name": "Tank",
            "dimensions": "2.0 x 2.5 x 4.0"
       }
    }

and I need to convert it back to something like this:

    {
       "status": 1, 
       "name": "Tank",
       "dimensions": "2.0 x 22.5 x 4.0"
    }

I found multiple solutions online, but all of them are giving me the same TypeScript error

"for (… in …) statements must be filtered with an if statement"

I am also seeing the a lot of them are embedding the name of for first element in from of its nested element, like this:

    {
       "status": 1, 
       "data.name": "Tank",
       "data.dimensions": "2.0 x 22.5 x 4.0"
    }

Can someone show me how to rewrite this flatten function where it eliminates the TS error and doesn’t append the first level element’s name to its nested elements, leaving me with simple single level object?

Here is my flatten function:

const obj = values;

const flattenObj = (ob) => {
  // The object which contains the final result
  const result = {};
  // loop through the object "ob"
  for (const i in ob) {
      // We check the type of the i using typeof() function and recursively call the function again
      if ((typeof ob[i]) === 'object' && !Array.isArray(ob[i])) {
          const temp = flattenObj(ob[i]);
          for (const j in temp) {
              // Store temp in result
              result[i + '.' + j] = temp[j];
          }
      }
      // Else store ob[i] in result directly
      else {
          result[i] = ob[i];
      }
  }
  return result;
};

console.log(flattenObj(ob));

4

Answers


  1. Try this:

      "status": 1, 
      "data" : {
        "name": "Tank",
        "dimensions": "2.0 x 2.5 x 4.0"
      }
    };
    
    const flattenObj = (ob: Record<string, unknown>) => {
      const result: Record<string, unknown> = {};
      for (const i in ob) {
          if (ob.hasOwnProperty(i)) {
            const val = ob[i];
            if (val && typeof val === 'object' && !Array.isArray(val)) {
              const temp = flattenObj(val as Record<string, unknown>);
              for (const j in temp) {
                  result[i + '.' + j] = temp[j];
              }
            }
            else {
              result[i] = ob[i];
            }
          }
      }
      return result;
    };
    
    console.log(flattenObj(obj));
    

    Playground

    Some changes from your version:

    1. Has .hasOwnProperty conditional check to satisfy the lint rule
    2. Checks that ob[i] is truthy, note that typeof null === 'object'!
    3. Types the argument and return value as Record<string, unknown>. For truly dynamic objects (like the response from an API call) you won’t know the type. If you narrow the type of the data to a specific schema you could probably use template literal types and generics to make a type-safe more specific return value. I’m still trying to figure that out.
    Login or Signup to reply.
  2. Here is the working example:

    let jsonObj = {
      status: 1,
      data: {
        name: "someStatus",
        dimensions: "2.0 x 2.5 x 4.0",
        some: {
          city: "somecity",
          dimensions: "somedimensions",
          next: {
            type: "nextType",
            value: "nextValue",
          },
        },
      },
    };
     
    
    function builder(obj : Record<string,any>, temp:Record<string,string | number | boolean>) {
      for (let keys in obj) {
        if (typeof obj[keys] == "object") {
          builder(obj[keys], temp);
        } else {
          temp[keys] = obj[keys];
        }
      }
    
      return temp;
    }
    
    let result = builder(jsonObj,{});
    
    console.log(result);
    Login or Signup to reply.
  3. You could change

    for (const i in ob) { .. }
    

    to

    for (const [i, obi] of Object.entries(ob)) { // use obi instead of ob[i] }
    

    that way you’re not touching the prototype stuff at all.

    Login or Signup to reply.
  4. This can be done by using a recursive function.

    In case the obj[key] is a simple value like obj['status'], it is added directly to the result object.

    Otherwise, if the obj[key] is an object like the value of obj['data'], the flattenJSON function is called using obj['data'] as a parameter. Once done, the result is concatenated with the previous result.

    This function handles multiple nesting as well.

    Note: The parentKey is not wanted. Consequently, some keys might be overridden.

    type JsonObject = Record<string, unknown>;
    
    function flattenJSON(jsonObj: JsonObject): Record<string, unknown> {
      let result: Record<string, unknown> = {};
    
      for (const key in jsonObj) {
        if (typeof jsonObj[key] === 'object' &&!Array.isArray(jsonObj[key])) {
          const flattened = flattenJSON(jsonObj[key] as JsonObject);
          result = { ...result, ...flattened };
        } else {
          result[key] = jsonObj[key];
        }
      }
    
      return result;
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search