How to unflatten a JavaScript object in a daisy-chain/dot notation into an object with nested objects and arrays?

冷暖自知 提交于 2019-12-10 15:17:28

问题


I want to unflatten an object like this...

var obj2 = {
    "firstName": "John",
    "lastName": "Green",
    "car.make": "Honda",
    "car.model": "Civic",
    "car.revisions.0.miles": 10150,
    "car.revisions.0.code": "REV01",
    "car.revisions.0.changes": "",
    "car.revisions.1.miles": 20021,
    "car.revisions.1.code": "REV02",
    "car.revisions.1.changes.0.type": "asthetic",
    "car.revisions.1.changes.0.desc": "Left tire cap",
    "car.revisions.1.changes.1.type": "mechanic",
    "car.revisions.1.changes.1.desc": "Engine pressure regulator",
    "visits.0.date": "2015-01-01",
    "visits.0.dealer": "DEAL-001",
    "visits.1.date": "2015-03-01",
    "visits.1.dealer": "DEAL-002"
};

... into an object with nested objects and arrays like the following:

{
  firstName: 'John',
  lastName: 'Green',
  car: {
    make: 'Honda',
    model: 'Civic',
    revisions: [
      { miles: 10150, code: 'REV01', changes: ''},
      { miles: 20021, code: 'REV02', changes: [
        { type: 'asthetic', desc: 'Left tire cap' },
        { type: 'mechanic', desc: 'Engine pressure regulator' }
      ] }
    ]
  },
  visits: [
    { date: '2015-01-01', dealer: 'DEAL-001' },
    { date: '2015-03-01', dealer: 'DEAL-002' }
  ]
}

Here's my (failed) attempt:

function unflatten(obj) {
    var result = {};

    for (var property in obj) {
        if (property.indexOf('.') > -1) {
            var substrings = property.split('.');

            console.log(substrings[0], substrings[1]);


        } else {
            result[property] = obj[property];
        }
    }

    return result;
};

I quickly started repeating code unnecessarily in order to do the nesting of objects and arrays. This is definitely something that needs recursion. Any ideas?

EDIT: I've also asked the opposite, flatten, in another question.


回答1:


You can first use for...in loop to loop object properties, then split each key at . then use reduce to build nested properties.

var obj2 = {"firstName":"John","lastName":"Green","car.make":"Honda","car.model":"Civic","car.revisions.0.miles":10150,"car.revisions.0.code":"REV01","car.revisions.0.changes":"","car.revisions.1.miles":20021,"car.revisions.1.code":"REV02","car.revisions.1.changes.0.type":"asthetic","car.revisions.1.changes.0.desc":"Left tire cap","car.revisions.1.changes.1.type":"mechanic","car.revisions.1.changes.1.desc":"Engine pressure regulator","visits.0.date":"2015-01-01","visits.0.dealer":"DEAL-001","visits.1.date":"2015-03-01","visits.1.dealer":"DEAL-002"}

function unflatten(data) {
  var result = {}
  for (var i in data) {
    var keys = i.split('.')
    keys.reduce(function(r, e, j) {
      return r[e] || (r[e] = isNaN(Number(keys[j + 1])) ? (keys.length - 1 == j ? data[i] : {}) : [])
    }, result)
  }
  return result
}

console.log(unflatten(obj2))



回答2:


Try breaking the problem down into two distinct challenges:

  1. Setting a value by path
  2. Looping over an object and unflattening the keys one by one

You might start with a setIn function that would look something like this:

function setIn(path, object, value) {
  let [key, ...keys] = path; 

  if (keys.length === 0) {
    object[key] = value;
  } else {
    let nextKey = keys[0];
    object[key] = object[key] || isNaN(nextKey) ? {} : [];
    setIn(keys, object[key], value);
  }

  return object;
}

Then combine it with an unflatten function which loops over an object running setIn for each key.

function unflatten(flattened) {
  let object = {};

  for (let key in flattened) {
    let path = key.split('.');
    setIn(path, object, flattened[key]);
  }

  return object;
}

Of course, there's already an npm package for doing this, and it'd also it'd be easy to implement your own using functions like _.set from lodash.

It's unlikely that you'd ever run into a long enough path that you'd end up running out of stack frames, but of course it's possible to implement setIn without recursion, using loops or trampolines.

And finally, if immutable data is your thing and you want to work with a version of setIn that doesn't modify your data structures, then you could take a look at the implementation in Zaphod—a JavaScript library for treating the native data structures as though they were immutable.




回答3:


You could walk the splitted substrings and a temporary object with a check if the key exists and build a new property with a check for the next property if it is a finite number, then assign an array, otherwise an object. At last assign with the last substring the value to the temp object.

function unflatten(obj) {
    var result = {}, temp, substrings, property, i;
    for (property in obj) {
        substrings = property.split('.');
        temp = result;
        for (i = 0; i < substrings.length - 1; i++) {
            if (!(substrings[i] in temp)) {
                if (isFinite(substrings[i + 1])) { // check if the next key is
                    temp[substrings[i]] = [];      // an index of an array
                } else {
                    temp[substrings[i]] = {};      // or a key of an object
                }
            }
            temp = temp[substrings[i]];
        }
        temp[substrings[substrings.length - 1]] = obj[property];
    }
    return result;
};

var obj2 = { "firstName": "John", "lastName": "Green", "car.make": "Honda", "car.model": "Civic", "car.revisions.0.miles": 10150, "car.revisions.0.code": "REV01", "car.revisions.0.changes": "", "car.revisions.1.miles": 20021, "car.revisions.1.code": "REV02", "car.revisions.1.changes.0.type": "asthetic", "car.revisions.1.changes.0.desc": "Left tire cap", "car.revisions.1.changes.1.type": "mechanic", "car.revisions.1.changes.1.desc": "Engine pressure regulator", "visits.0.date": "2015-01-01", "visits.0.dealer": "DEAL-001", "visits.1.date": "2015-03-01", "visits.1.dealer": "DEAL-002" };

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

A slightly more compact version could be this

function unflatten(object) {
    var result = {};
    Object.keys(object).forEach(function (k) {
        setValue(result, k, object[k]);
    });
    return result;
}

function setValue(object, path, value) {
    var way = path.split('.'),
        last = way.pop();

    way.reduce(function (o, k, i, kk) {
        return o[k] = o[k] || (isFinite(i + 1 in kk ? kk[i + 1] : last) ? [] : {});
    }, object)[last] = value;
}

var obj2 = { "firstName": "John", "lastName": "Green", "car.make": "Honda", "car.model": "Civic", "car.revisions.0.miles": 10150, "car.revisions.0.code": "REV01", "car.revisions.0.changes": "", "car.revisions.1.miles": 20021, "car.revisions.1.code": "REV02", "car.revisions.1.changes.0.type": "asthetic", "car.revisions.1.changes.0.desc": "Left tire cap", "car.revisions.1.changes.1.type": "mechanic", "car.revisions.1.changes.1.desc": "Engine pressure regulator", "visits.0.date": "2015-01-01", "visits.0.dealer": "DEAL-001", "visits.1.date": "2015-03-01", "visits.1.dealer": "DEAL-002" };

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


来源:https://stackoverflow.com/questions/42694980/how-to-unflatten-a-javascript-object-in-a-daisy-chain-dot-notation-into-an-objec

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!