简体   繁体   中英

Turn array to a deep level of object

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', 'cde'] 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,

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

 const keys = ['a', 'b.c', 'cde'] 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', 'cde'] 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; }

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', 'cde', 'efgh']; 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; }

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', 'cde'] 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)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM