skip to Main Content

I have this simple nested object which I need to flatten to be able to insert it into my database.

const input = {
  name: "Benny",
  department: {
    section: "Technical",
    branch: {
      timezone: "UTC",
    },
  },
  company: [
    {
      name: "SAP",
      customers: ["Ford-1", "Nestle-1"],
    },
    {
      name: "SAP",
      customers: ["Ford-2", "Nestle-2"],
    },
  ],
};

The desired result is like this, each value in the arrays results in a new sub-object stored in an array:

[
  {
    name: "Benny",
    "department.section": "Technical",
    "department.branch.timezone": "UTC",
    "company.name": "SAP",
    "company.customers": "Ford-1",
  },
  {
    name: "Benny",
    "department.section": "Technical",
    "department.branch.timezone": "UTC",
    "company.name": "SAP",
    "company.customers": "Nestle-1",
  },
  {
    name: "Benny",
    "department.section": "Technical",
    "department.branch.timezone": "UTC",
    "company.name": "SAP",
    "company.customers": "Ford-2",
  },
  {
    name: "Benny",
    "department.section": "Technical",
    "department.branch.timezone": "UTC",
    "company.name": "SAP",
    "company.customers": "Nestle-2",
  },
]

Instead of the result below which all fields stored in single object with indexes:

{
  name: 'Benny',
  'department.section': 'Technical',
  'department.branch.timezone': 'UTC',
  'company.0.name': 'SAP',
  'company.0.customers.0': 'Ford-1',
  'company.0.customers.1': 'Nestle-1',
  'company.1.name': 'SAP',
  'company.1.customers.0': 'Ford-2',
  'company.1.customers.1': 'Nestle-2'
}

My code looks like this:

function flatten(obj) {
  let keys = {};
  for (let i in obj) {
    if (!obj.hasOwnProperty(i)) continue;
    if (typeof obj[i] == "object") {
      let flatObj = flatten(obj[i]);
      for (let j in flatObj) {
        if (!flatObj.hasOwnProperty(j)) continue;
        keys[i + "." + j] = flatObj[j];
      }
    } else {
      keys[i] = obj[i];
    }
  }
  return keys;
}

Thanks in advance!

3

Answers


  1. Edit

    In the code below, I left your flatten functionality the same. I added a fix method that converts your original output into your desired output.

    Note: I changed the name value of second company to FOO.

    const flatten = (obj) => {
      let keys = {};
      for (let i in obj) {
        if (!obj.hasOwnProperty(i)) continue;
        if (typeof obj[i] == 'object') {
          let flatObj = flatten(obj[i]);
          for (let j in flatObj) {
            if (!flatObj.hasOwnProperty(j)) continue;
            keys[i + '.' + j] = flatObj[j];
          }
        } else {
          keys[i] = obj[i];
        }
      }
      return keys;
    };
    
    const parseKey = (key) => [...key.matchAll(/(w+).(d)(?=.?)/g)]
      .map(([match, key, index]) => ({ key, index }));
    
    const fix = (obj) => {
      const results = [];
      Object.keys(obj).forEach((key) => {
        const pairs = parseKey(key);
        if (pairs.length > 1) {
          const result = {};
          Object.keys(obj).forEach((subKey) => {
            const subPairs = parseKey(subKey);
            let replacerKey;
            if (subPairs.length < 1) {
              replacerKey = subKey;
            } else {
              if (
                subPairs.length === 1 &&
                subPairs[0].index === pairs[0].index
              ) {
                replacerKey = subKey
                  .replace(`.${subPairs[0].index}`, '');
              }
              if (
                subPairs.length === 2 &&
                subPairs[0].index === pairs[0].index &&
                subPairs[1].index === pairs[1].index
              ) {
                 replacerKey = subKey
                  .replace(`.${subPairs[0].index}`, '')
                  .replace(`.${subPairs[1].index}`, '');
                 result[replacerKey] = obj[subKey];
              }
            }
            if (replacerKey) {
              result[replacerKey] = obj[subKey];
            }
          });
          results.push(result);
        }
      });
      return results;
    };
    
    
    const input = {
      name: "Benny",
      department: { section: "Technical", branch: { timezone: "UTC" } },
      company: [
        { name: "SAP", customers: ["Ford-1", "Nestle-1"] },
        { name: "FOO", customers: ["Ford-2", "Nestle-2"] },
      ]
    };
    
    const flat = flatten(input);
    console.log(JSON.stringify(fix(flat), null, 2));
    .as-console-wrapper { top: 0; max-height: 100% !important; }

    Original response

    The closest (legitimate) I could get to your desired result is:

    [
      {
        "name": "Benny",
        "department.section": "Technical",
        "department.branch.timezone": "UTC",
        "company.name": "SAP",
        "company.customers.0": "Ford-1",
        "company.customers.1": "Nestle-1"
      },
      {
        "name": "Benny",
        "department.section": "Technical",
        "department.branch.timezone": "UTC",
        "company.name": "SAP",
        "company.customers.0": "Ford-2",
        "company.customers.1": "Nestle-2"
      }
    ]
    

    I had to create a wrapper function called flattenBy that handles mapping the data by a particular key e.g. company and passes it down to your flatten function (along with the current index).

    const flatten = (obj, key, index) => {
      let keys = {};
      for (let i in obj) {
        if (!obj.hasOwnProperty(i)) continue;
        let ref = i !== key ? obj[i] : obj[i][index];
        if (typeof ref == 'object') {
          let flatObj = flatten(ref, key);
          for (let j in flatObj) {
            if (!flatObj.hasOwnProperty(j)) continue;
            keys[i + '.' + j] = flatObj[j];
          }
        } else { keys[i] = obj[i]; }
      }
      return keys;
    }
    
    const flattenBy = (obj, key) =>
      obj[key].map((item, index) => flatten(obj, key, index));
    
    
    const input = {
      name: "Benny",
      department: { section: "Technical", branch: { timezone: "UTC" } },
      company: [
        { name: "SAP", customers: ["Ford-1", "Nestle-1"] },
        { name: "SAP", customers: ["Ford-2", "Nestle-2"] },
      ]
    };
    
    console.log(JSON.stringify(flattenBy(input, 'company'), null, 2));
    .as-console-wrapper { top: 0; max-height: 100% !important; }
    Login or Signup to reply.
  2. You could take the array’s values as part of a cartesian product and get finally flat objects.

    const
        getArray = v => Array.isArray(v) ? v : [v],
        isObject = v => v && typeof v === 'object',
        getCartesian = object => Object.entries(object).reduce((r, [k, v]) => r.flatMap(s =>
            getArray(v).flatMap(w =>
                (isObject(w) ? getCartesian(w) : [w]).map(x => ({ ...s, [k]: x }))
            )
        ), [{}]),
        getFlat = o => Object.entries(o).flatMap(([k, v]) => isObject(v)
            ? getFlat(v).map(([l, v]) => [`${k}.${l}`, v])
            : [[k, v]]
        ),
        input = { name: "Benny", department: { section: "Technical", branch: { timezone: "UTC" } }, company: [{ name: "SAP", customers: ["Ford-1", "Nestle-1"] }, { name: "SAP", customers: ["Ford-2", "Nestle-2"] }] },
        result = getCartesian(input).map(o => Object.fromEntries(getFlat(o)));
    
    console.log(result);
    .as-console-wrapper { max-height: 100% !important; top: 0; }
    Login or Signup to reply.
  3. This code locates all forks (places where arrays are located which indicate multiple possible versions of the input), and constructs a tree of permutations of the input for each fork. Finally, it runs all permutations through a flattener to get the desired dot-delimited result.

    Note: h is a value holder, where h.s is set to 1 as soon as the first fork is found. This acts like a kind of global variable across all invocations of getFork on a particular initial object, and forces only one fork to be considered at a time when building up a tree of forks.

    const input = {"name":"Benny","department":{"section":"Technical","branch":{"timezone":"UTC"}},"company":[{"name":"SAP","customers":["Ford-1","Nestle-1"]},{"name":"SAP","customers":["Ford-2","Nestle-2"]},{"name":"BAZ","customers":["Maserati","x"],"Somekey":["2","3"]}]}
    
    const flatten = (o, prefix='') => Object.entries(o).flatMap(([k,v])=>v instanceof Object ? flatten(v, `${prefix}${k}.`) : [[`${prefix}${k}`,v]])
    const findFork = o => Array.isArray(o) ? o.length : o instanceof Object && Object.values(o).map(findFork).find(i=>i)
    const getFork = (o,i,h={s:0}) => o instanceof Object ? (Array.isArray(o) ? h.s ? o : (h.s=1) && o[i] : Object.fromEntries(Object.entries(o).map(([k,v])=>[k, getFork(v, i, h)]))) : o
    const recurse = (o,n) => (n = findFork(o)) ? Array(n).fill(0).map((_,i)=>getFork(o, i)).flatMap(recurse) : o
    const process = o => recurse(o).map(i=>Object.fromEntries(flatten(i)))
    
    const result = process(input)
    console.log(result)
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search