1

I have an array and a sample object like this:

const array = ['a', 'b.c', 'c.d.e']

Sample

const sample = {
  'aa': 'test',
  'a': 'value',
  'b': {
    'c': 'value'
  },
  'c': {
    'd': {
      'e': 'value'
    }
  },
  'e': {
    'f': {
      'g': {
        'h' : 'value'
      }
    }
  }
}

Then I want to make a new object basing on the sample. The result should look like this:

const newSample = {
  'aa': 'test',
  'a': 'oldvalue' + 1,
  'b': {
    'c': 'oldvalue' + 1
  },
  'c': {
    'd': {
      'e': 'oldvalue' + 1
    }
  },
  'e': {
    'f': {
      'g': {
        'h' : 'oldvalue'
      }
    }
  }
}

I'm thinking of loop through the array and count the length of each element. However, it's not as efficient as the level of the array and sample increase. Are there any algorithms that can be done better?

const array = ['a', 'b.c', 'c.d.e']

const sample = {
  'aa': test,
  'a': 'value',
  'b': {
    'c': 'value'
  },
  'c': {
    'd': {
      'e': 'value'
    }
  },
  'e': {
    'f': {
      'g': {
        'h' : 'value'
      }
    }
  }
}

const newSample = {}

const transformer = (array) => {
  array.forEach(item => {
    const itemArr = item.split('.')
    if (itemArr.length === 1) {
      console.log(sample[itemArr[0]])
      newSample[itemArr[0]] = sample[itemArr[0]] + 1
    }
    // the condition goes on...
  })
}
transformer(array)
console.log(newSample)

Thanks,

Dale Nguyen
  • 1,930
  • 3
  • 24
  • 37

3 Answers3

2

You could reduce the splitted keys and save the last key for the assignment with the last key.

const 
    keys = ['a', 'b.c', 'c.d.e']
    object = { a: 'value', b: { c: 'value' }, c: { d: { e: 'value' } }, e: { f: { g: { h : 'value' } } } },
    transformer = (objec, keys) => keys.reduce((r, s) => {
        var path = s.split('.'),
            last = path.pop(),
            final = path.reduce((o, k) => o[k] = o[k] || {}, r);

        final[last] = (final[last] || '') + 1; // or whatever you need change
        return r;
    }, object); 

console.log(transformer(object, keys));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Nonmutating approach by returning a deep copy with changed value.

function transformer(object, keys) {
    return Object.entries(object).reduce((r, [k, v]) => {
        r[k] = v && typeof v === 'object'
            ? transformer(v, keys.filter(s => s.startsWith(k + '.')).map(s => s.split('.').slice(1).join('.')))
            : keys.includes(k) ? (v || '') + 1: v;
        return r;
    }, {});
}


const 
    keys = ['a', 'b.c', 'c.d.e']
    object = { a: 'value', b: { c: 'value' }, c: { d: { e: 'value' } }, e: { f: { g: { h : 'value' } } } }; 

console.log(transformer(object, keys));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
1

It looks like you just want to apply a new value to a set of scoped-fields.

You could just loop over your desired scoped-fields and set a new value for them. The answer to the following question: Convert JavaScript string in dot notation into an object reference works very well for this problem set.

Warnings

If you need to deep-copy or clone the object, you can serialize-deserialize it:

function cloneObject(obj) {
  return JSON.parse(JSON.stringify(obj));
}

Normal assignment does NOT handle deep-copies:

function cloneObject(obj) {
  return Object.assign({}, obj);
}

const array = ['a', 'b.c', 'c.d.e', 'e.f.g.h'];

const sample = {
  'a': 'value',
  'b': {
    'c': 'value'
  },
  'c': {
    'd': {
      'e': 'value'
    }
  },
  'e': {
    'f': {
      'g': {
        'h': 'value'
      }
    }
  }
};

const newSample = setValueForKeys(cloneObject(sample), array, 'someValue');

console.log('Updated:', newSample);
console.log('Original:', sample);

function setValueForKeys(source, keys, value) {
  keys.forEach((key) => index(source, key, value));
  return source;
}

function cloneObject(obj) {
  return JSON.parse(JSON.stringify(obj));
}

// See: https://stackoverflow.com/a/6394168/1762224
function index(obj, is, value) {
  if (typeof is == 'string') return index(obj, is.split('.'), value);
  else if (is.length == 1 && value !== undefined) return obj[is[0]] = value;
  else if (is.length == 0) return obj;
  else return index(obj[is[0]], is.slice(1), value);
}
function multiIndex(obj, is) {
  return is.length ? multiIndex(obj[is[0]], is.slice(1)) : obj;
}
function pathIndex(obj, is) {
  return multiIndex(obj, is.split('.'))
}
.as-console-wrapper { top: 0; max-height: 100% !important; }
Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132
1

You can do this with forEach loop to loop each element in the array and then split each path to array and then use reduce method on that path array to update each nested property.

const array = ['a', 'b.c', 'c.d.e']
const sample = {"a":"value","b":{"c":"value"},"c":{"d":{"e":"value"}},"e":{"f":{"g":{"h":"value"}}}}


function update(array, sample) {
  array.forEach(c => {
    c.split('.').reduce((r, e, i, a) => {
      if (!a[i + 1]) r[e] = r[e] + 1
      else return r[e] || {}
    }, sample)
  })
}

update(array, sample);
console.log(sample)
Nenad Vracar
  • 118,580
  • 15
  • 151
  • 176