-1

I need to format this JavaScript object to the output format. Any help please?

let values = {
  'ban_Name': "test",
  'bank': "yes",
  'cont_1-counterName': "Cname1",
  'cont_1-obl': "obl1",
  'cont_1-orig': "orig 1",
  'cont_2-counterName': "Cname2",
  'cont_2-obl': "obl2",
  'cont_2-orig': "orig 2",
  'cont': "yes",
  'creditors': "no",
}

Output should be

{
  'ban_Name': "test",
  'bank': "yes",
  'cont': "yes",
  'conts' [{
    'counterName': "Cname1",
    'cobl': "obl1",
    'orig': "orig 1",
  }, {
    'counterName': "Cname2",
    'obl': "obl2",
    'orig': "orig 2",
  }],
  'creditors': "no",
}

Can some one suggest me a way to do this?

Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132
Samz
  • 76
  • 10

4 Answers4

1

You could try converting the values to a list of key value pairs that look closer to JavaScript nested paths:

Key Path Value
ban_Name test
bank yes
conts[0].counterName Cname1
conts[0].obl obl1
conts[0].orig orig 1
conts[1].counterName Cname2
conts[1].obl obl2
conts[1].orig orig 2
cont yes
creditors no

Note that the assignment of "yes" to cont will replace the array that you have defined above, so I altered the replacer to add an "s" before the array index bracket notation.

key.replace(/_(\d)-/g, (g0, g1) => `s[${g1 - 1}].`);

Simple solution

This solution is very simple, because it fails if you have more than two levels deep. If you need something that would work better, try to find a recursive way to convert key paths to a nested object.

let values = {
  'ban_Name': "test",
  'bank': "yes",
  'cont_1-counterName': "Cname1",
  'cont_1-obl': "obl1",
  'cont_1-orig': "orig 1",
  'cont_2-counterName': "Cname2",
  'cont_2-obl': "obl2",
  'cont_2-orig': "orig 2",
  'cont': "yes",
  'creditors': "no",
};

const pairs = Object.entries(values).map(([key, val]) =>
  [key.replace(/_(\d)-/g, (g0, g1) => `s[${g1 - 1}].`), val]);

console.log(pairs.map(([key, val]) => `${key} => ${val}`).join('\n'));

const toObject = (pairs, acc = {}) => {
  pairs.forEach(([key, val]) => {
    const [headKey, ...tailKeys] = key.split(/\./g);
    if (tailKeys.length === 0) {
      acc[headKey] = val;
    } else {
      const [otherKey] = tailKeys;
      const [match, arrKey, arrIndex] = headKey.match(/(\w+)\[(\d+)\]/);
      if (match) {
        const index = parseInt(arrIndex, 10);
        acc[arrKey] = (acc[arrKey] || []);
        acc[arrKey][index] = (acc[arrKey][index] || {});
        acc[arrKey][index][otherKey] = val;
      }
    }
  });
  return acc;
};

console.log(toObject(pairs));
.as-console-wrapper { top: 0; max-height: 100% !important; }

Dynamic alternative

The following will work for even deeper nesting.

// See: https://stackoverflow.com/a/19101235/1762224
Object.unflatten = function(data) {
  if (Object(data) !== data || Array.isArray(data)) return data;
  const regex = /\.?([^.\[\]]+)|\[(\d+)(\])?/g, resultholder = {};
  for (let p in data) {
    let scope = resultholder, prop = '', m;
    while (m = regex.exec(p)) {
      scope = scope[prop] || (scope[prop] = (m[2] ? [] : {}));
      prop = m[2] || m[1];
    }
    scope[prop] = data[p];
  }
  return resultholder[''] || resultholder;
};

const fixKeys = (obj, keyFn) => Object.entries(obj)
  .reduce((acc, [key, val]) => ({ ...acc, [keyFn(key)]: val }), {});

// =====================  Main  =====================

const values = {
  'ban_Name': "test",
  'bank': "yes",
  'cont_1-counterName': "Cname1",
  'cont_1-obl': "obl1",
  'cont_1-orig': "orig 1",
  'cont_2-counterName': "Cname2",
  'cont_2-obl': "obl2",
  'cont_2-orig': "orig 2",
  'cont': "yes",
  'creditors': "no",
};

const fixed = fixKeys(values, key =>
  key.replace(/_(\d)-/g, (g0, g1) => `s[${g1 - 1}].`));

console.log(Object.unflatten(fixed));
.as-console-wrapper { top: 0; max-height: 100% !important; }
Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132
1

let values = {
    'ban_Name': "test",
    'bank': "yes",
    'cont_1-counterName': "Cname1",
    'cont_1-obl': "obl1",
    'cont_1-orig': "orig 1",
    'cont_2-counterName': "Cname2",
    'cont_2-obl': "obl2",
    'cont_2-orig': "orig 2",
    'cont': "yes",
    'creditors': "no",
}

let output = {}
let contsArray = []

const regex = /cont_(\d+)-(\w+)/gm;
for (let key in values) {
    let m = '';
    if (key.startsWith('cont_')) {
        while ((m = regex.exec(key)) !== null) {
            // This is necessary to avoid infinite loops with zero-width matches
            if (m.index === regex.lastIndex) {
                regex.lastIndex++;
            }
            let contIndex = m[1]

            if (!contsArray[contIndex - 1]) {
                contsArray[contIndex - 1] = {}
            }
            contsArray[contIndex - 1][m[2]] = values[key]

        }
    } else {
        output[key] = values[key]
    }
}
output.conts = contsArray
console.log(output)
Pawan Sharma
  • 1,842
  • 1
  • 14
  • 18
0
function objChange(objTemp) {
   keyArr = Object.keys(objTemp);
   let obj = {};
   obj['conts'] = [];
   let consone = {};
   let constwo= {};
   for(let i of keyArr){   
      if(i.includes("cont_1-")) {
         const name = i.replace("cont_1-",'');
          consone[name] = values[i];
      } else if (i.includes("cont_2-")){ 
         const name = i.replace("cont_2-",'');
          constwo[name] = values[i];       
      } else {

        obj[i] = values[i]
      }
    }
    Object.keys(consone).length ? obj['conts'].push(consone) : '';
    Object.keys(constwo).length ? obj['conts'].push(constwo) : '';
    return obj;
}
let values = {
  'ban_Name': "test",
  'bank': "yes",
  'cont_1-counterName': "Cname1",
  'cont_1-obl': "obl1",
  'cont_1-orig': "orig 1",
  'cont_2-counterName': "Cname2",
  'cont_2-obl': "obl2",
  'cont_2-orig': "orig 2",
  'cont': "yes",
  'creditors': "no",
}
objChange(values)

P.s - I know this on Not fully optimize. but i am low in time show if you want to make more optimize then you can.Thanks you

Toxy
  • 696
  • 5
  • 9
  • 1
    the big problem with this is the hardcoding of `cont_1` and `cont_2`. I assumed that these were dynamic and we needed to handle `cont_42` or any other such nodes – Scott Sauyet Apr 09 '21 at 14:54
  • Yes they are dynamic, So hard coding will not work. Thanks for trying to help – Samz Apr 19 '21 at 03:40
0

This works by converting our object into a format like this:

[
  [["ban_Name"], "test"],
  [["bank"], "yes"],
  [["conts", 0, "counterName"], "Cname1"],
  [["conts", 0, "obl"], "obl1"],
  [["conts", 0, "orig"], "orig 1"], 
  [["conts", 1, "counterName"], "Cname2"],
  [["conts", 1, "obl"], "obl2"],
  [["conts", 1, "orig"], "orig 2"],
  [["cont"], "yes"],
  [["creditors"], "no"]
]

and then using a generic hydrate function that takes an array of path-value pairs like this and puts them together into an object:

// utility functions
const setPath = ([p, ...ps]) => (v) => (o) =>
  p == undefined ? v : Object .assign (
    Array .isArray (o) || Number.isInteger (p) ? [] : {},
    {...o, [p]: setPath (ps) (v) ((o || {}) [p])}
  )

const hydrate = (xs) =>
  xs .reduce ((a, [p, v]) => setPath (p) (v) (a), {})


// helper function
const makePath = (k, parts = k .match (/^cont_(\d+)\-(.+)$/)) => 
  parts ? ['conts', parts [1] - 1, parts [2]] : [k]


// main function
const convert = (input) =>
  hydrate (Object .entries (input) .map (([k, v]) => [makePath (k), v]))


// sample data
const values  = {'ban_Name': "test", 'bank': "yes", 'cont_1-counterName': "Cname1", 'cont_1-obl': "obl1", 'cont_1-orig': "orig 1", 'cont_2-counterName': "Cname2", 'cont_2-obl': "obl2", 'cont_2-orig': "orig 2", 'cont': "yes", 'creditors': "no"}


// demo
console .log (convert (values))
.as-console-wrapper {max-height: 100% !important; top: 0}

setPath is a generic utility function that takes a path like ['a', 'b', 'c'], a value like 42, and an object like {a: {b: {d: 1}}, e: 2} and adds the value along that path to (a copy of) that object, yielding something like {a: {b: {c: 42, d: 1}}, e: 2}.

hydrate is similar to Object .fromEntries except that it uses a path rather than a key, so can build nested objects from a flat list.

We have the helper function makePath which takes a plain key like 'bank' and returns ['bank'] or takes one of your formatted keys like 'cont_2-orig' and returns ['conts', 2, 'orig'].

Then our main function simply needs to map over the entries in the input object, converting the keys with makePath, then hydrate an object with the resulting array.

Note that hydrate and setPath are genuinely reusable functions that you might want in multiple parts of your application, and so probably belong in some sort of utility library. The only custom code here is makePath and convert, and those are quite simple.

yivi
  • 42,438
  • 18
  • 116
  • 138
Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103