3

I have the below object inputObj and I want to convert it to a simple object like outputObj.

var inputObj = {
    'a' : 1,
    'b' : true,
    'c' : 'string1',
    'd' : {
        'e' : 'string2',
        'f' : false,
        'g' : 5,
        'h' : {
            'i' : 7,
            'j' : 'string3',
            'k' : [
                {
                    'name' : 'l',
                    'value': 11
                },
                {
                    'name' : 'm',
                    'value': {
                        'n' : 13,
                        'o' : 'string4'
                    }
                }
            ]
        }
    },
    'p' : [
        {
            'name' : 'q',
            'value': 15
        },
        {
            'name' : 'r',
            'value': 'Awesome!'
        }
    ]
}

var outputObj = {
    'a' : 1,
    'b' : true,
    'c' : 'string1',
    'e' : 'string2',
    'f' : false,
    'g' : 5,
    'i' : 7,
    'j' : 'string3',
    'l' : 11,
    'n' : 13,
    'o' : 'string4',
    'q' : 15,
    'r' : 'Awesome!'
}

Please note that in case of array the final output object would be build from name and value properties as per above example.

I have tried to implement the same using below code. I know as I am calling the nested function, the final object is getting reset and the scope is also different there. May be basic closure concept can solve this but I am confused with this. If you can correct my code or altogether new code is also fine.

function convertToFirstLevelObject( object ) {
 var returnObj = {};

 if( IsPrimaryDataType( object )) {
  return object;
 }
 else {
  if( object instanceof Array ) {
         for ( var i = 0; i < object.length; i++ ) {
          if( typeof object[i] === 'object' && !(object[i] instanceof Array) ) {
           var key   = object[i]['name'],
            value = object[i]['value'];
           if( IsPrimaryDataType( value )) {
            returnObj[ key ] = value;
           }
           else {
            convertToFirstLevelObject( value );
           }
          }
          else{
           /* This condition is for Array of Array */
           if( object[i] instanceof Array ) {
            convertToFirstLevelObject( object[i] );
           } else {
            console.log('Wrong data passed, expected data id object or Array of objects');
            return;
           }
          }
         }
  }
  else {
   for ( var attr in object ) {
             if ( object.hasOwnProperty( attr ) ) {
              if( IsPrimaryDataType( object[ attr ] )) {
               returnObj[ attr ] = object[ attr ];
              }
              else {
               convertToFirstLevelObject( object[ attr ] )
              }
             } 
         }
  }
 }
 return returnObj;
}

function IsPrimaryDataType( input ) {
 var returnFlag = false;
 if( input === null || input === 'undefined' || typeof input !==  'object' ) {
  returnFlag = true;
 }
 return returnFlag;
}

Edit: Here is another inputObj just to show that nesting can be of any level, here I have increased the level of array nesting. In any level of array nesting it will just look for if there is any object which has name and value both the property then it will flatten that.

 var inputObj = {
    'a' : 1,
    'b' : true,
    'c' : 'string1',
    'd' : {
        'e' : 'string2',
        'f' : false,
        'g' : 5,
        'h' : {
            'i' : 7,
            'j' : 'string3',
            'k' : [
                {
                    'name' : 'l',
                    'value': 11
                },
                {
                    'name' : 'm',
                    'value': [{'n' : 13},{'o' : 'string4'}]
                }
            ]
        }
    },
    'p' : [
        {
            'name' : 'q',
            'value': 15
        },
        {
            'name' : 'r',
            'value': 'Awesome!'
        }
    ],
    's' : [
        [{
            'name' : 't',
            'value': 17
        },
        {
            'name' : 'u',
            'value': 'string5'
        }],
        [ 1, 2, 3],
        [ "string6", "string7", "string8"],
        [
            [1,3,5],
            [{'name' : 'v', 'value' : 19, 'anyOtherProp' : false}],
            [2,4,6],
            [{'name' : 'w', 'otherProp' : 31}]
        ]
    ]
}

OutObj should be like below

var outputObj = {
    'a' : 1,
    'b' : true,
    'c' : 'string1',
    'e' : 'string2',
    'f' : false,
    'g' : 5,
    'i' : 7,
    'j' : 'string3',
    'l' : 11,
    'n' : 13,
    'o' : 'string4',
    'q' : 15,
    'r' : 'Awesome!',
    't' : 17,
    'u' : 'string5',
    'v' : 19
}
Debajit Majumder
  • 824
  • 1
  • 11
  • 22
  • https://gist.github.com/penguinboy/762197 – Rayon Sep 09 '16 at 11:50
  • @Rayon [flattening JS objects](http://stackoverflow.com/q/19098797/1048572) is a completely different question – Bergi Sep 09 '16 at 11:51
  • @Bergi – But input appears object, not `JSON` – Rayon Sep 09 '16 at 11:53
  • I don't see `outputObj`. Anyway, in what way does your code fail? Have you tried walking through the code in the debugger? –  Sep 09 '16 at 12:08
  • @Rayon - Your solution may help me to solve my problem. But currently it will not yield the exact output object which I want. – Debajit Majumder Sep 09 '16 at 18:24
  • @torazaburo - Sorry the representation is not so good, but there **scrollbar** where I have defined inputObj, you will find the outputObj at the bottom. – Debajit Majumder Sep 09 '16 at 18:25

2 Answers2

4

You could use an iterative and recursive approach for looping the object.

function flatObject(source, target) {
    Object.keys(source).forEach(k => {
        if (Array.isArray(source[k])) {
            source[k].forEach(function iter(a) {
                if (Array.isArray(a)) {
                    a.forEach(iter);
                    return;
                }
                if (a !== null && typeof a === 'object') {
                    if ('name' in a || 'value' in a) {
                        'name' in a && 'value' in a && flatObject({ [a.name]: a.value }, target);
                        return;
                    }
                    flatObject(a, target);
                }
            });
            return;
        }
        if (source[k] !== null && typeof source[k] === 'object') {
            flatObject(source[k], target);
            return;
        }
        target[k] = source[k];
    });
}

var inputObj = { 'a': 1, 'b': true, 'c': 'string1', 'd': { 'e': 'string2', 'f': false, 'g': 5, 'h': { 'i': 7, 'j': 'string3', 'k': [{ 'name': 'l', 'value': 11 }, { 'name': 'm', 'value': [{ 'n': 13 }, { 'o': 'string4' }] }] } }, 'p': [{ 'name': 'q', 'value': 15 }, { 'name': 'r', 'value': 'Awesome!' }], 's': [[{ 'name': 't', 'value': 17 }, { 'name': 'u', 'value': 'string5' }], [1, 2, 3], ["string6", "string7", "string8"], [[1, 3, 5], [{ 'name': 'v', 'value': 19, 'anyOtherProp': false }], [2, 4, 6], [{ 'name': 'w', 'otherProp': 31 }]]] },
    target = {};

flatObject(inputObj, target);
console.log(target);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • This is really great solution. But can you please give little generic solution for `Array` part i.e. if I change my input like this `'value': [{'n' : 13},{'o' : 'string4'}]` in the `inputObj` (only `n`, `o` written here), then also it should work. – Debajit Majumder Sep 10 '16 at 05:08
  • Sorry I think I couldn't convey my query properly, please see my **Edit** in the question. So `name` and `value` would be there, if any object inside array has these two property( i.e. `name` and `value`) then it will not flatten any other property. – Debajit Majumder Sep 10 '16 at 15:55
  • I have tried executing and It doesn't give the expected `outputObj` with the updated `inputObj`. I think in your first solution you have assumed that there will be objects containing `name` and `value` property just inside the array, but it is possible that there can be array of array (it can be of any level) and object inside inner array will have `name`, `value` property – Debajit Majumder Sep 10 '16 at 16:44
  • ok, i see the problem. whot do you want with just array items whop are not objects with the wanted name value pair? – Nina Scholz Sep 10 '16 at 16:52
  • Those will be ignored in the `outputObj`. Like if an item inside array is a simple array that will be ignored. If an item inside array is an object having properties other than `name` and `value` those will be ignored. – Debajit Majumder Sep 10 '16 at 17:08
  • This is just perfect which covers all the scenarios. Thank you sir. – Debajit Majumder Sep 12 '16 at 04:12
1

Another approach. Very clear in my opinion. It relies heavily on recursion.

function convert(obj, mem) {
    if (typeof obj != 'object' || obj === null) { // return primitive values and null directly
        return obj
    }
    else if (obj.hasOwnProperty("name")) { // convert {name,value} objects to regular ones and recurse
        if (!obj.value) return // value is required - if not available bail out
        var o = {}
        o[obj.name] = obj.value
        return convert(o, mem)
    }
    else if (Array.isArray(obj)) { // iterate over array items and recurse
        obj.forEach(function (item) {
            convert(item, mem)
        })
    }
    else {
        for (var key in obj) { // iterate object items
            var value = obj[key]
            if (typeof value === "object") { // convert nested objects, dispose key
                convert(value, mem)
            }
            else {
                mem[key] = value // keep everything else
            }
        }
    }
    return mem // return result
}
alexloehr
  • 1,773
  • 1
  • 20
  • 18