简体   繁体   中英

Transform flat object into nested structure by keys

Is there any recommended way in Javascript that can parse the keys of an object to transform it into something nested? Here is an example:

Transform this:

{
  "person[profile][0][degree_type]": "Bachelor's",
  "person[profile][0][college_name]": "AI Miami International University of Art and Design",
  "person[profile][0][business_school_name]": "",
  "person[profile][0][law_school_name]": "",
  "person[profile][0][other_school_name]": "",
  "person[profile][0][undergraduate_major_name]": "Anthropology",
  "person[profile][0][max_gpa]": "",
  "person[profile][1][degree_type]": "",
  "person[profile][1][college_name]": ""
}

To this:

"person": {
  "profile": [
    {
      "degree_type": "Bachelor's",
      "college_name": "AI Miami International University of Art and Design",
      "business_school_name": "",
      "law_school_name": "",
      "other_school_name": "",
      "undergraduate_major_name": "",
      "max_gpa": ""
    },
    {
      .....
    }
  ]
}

Here you go! :) That was fun.

var flatObj = {
  "person[profile][0][degree_type]": "Bachelor's",
  "person[profile][0][college_name]": "AI Miami International University of Art and Design",
  "person[profile][0][business_school_name]": "",
  "person[profile][0][law_school_name]": "",
  "person[profile][0][other_school_name]": "",
  "person[profile][0][undergraduate_major_name]": "Anthropology",
  "person[profile][0][max_gpa]": "",
  "person[profile][1][degree_type]": "",
  "person[profile][1][college_name]": ""
};

var parse = function(data, string, value) {
  if (string.indexOf("]") >= 0) {
    var match = string.charAt(0) != "[" ? string.match(/([^\[]+)\[/) : string.match(/\[([^\]]+)\]/);
    var key = match[1];

    var token = key + ']';
    var index = string.indexOf(token) + token.length;

    if (!data.hasOwnProperty(key)) {
      data[key] = isNaN(key) ? {} : [];
    }

    if (index >= string.length) {
      data[key] = value;
    } else {
      parse(data[key], string.substring(index), value);
    }
  }
};

var data = {};

for (var prop in flatObj) {
  parse(data, prop, flatObj[prop]);
}

Using ES5 Object.keys and array.reduce methods, you could have something like:

var data = {
  "person[profile][0][degree_type]": "Bachelor's",
  "person[profile][0][college_name]": "AI Miami International University of Art and Design",
  "person[profile][0][business_school_name]": "",
  "person[profile][0][law_school_name]": "",
  "person[profile][0][other_school_name]": "",
  "person[profile][0][undergraduate_major_name]": "Anthropology",
  "person[profile][0][max_gpa]": "",
  "person[profile][1][degree_type]": "",
  "person[profile][1][college_name]": ""
};

var object = Object.keys(data).reduce(function(result, item) {
  var o = result;
  var leaf = item.match(/\w+/g).reduce(function(current, next) {
    o[current] = o[current] || (String(next >>> 0) === next ? [] : {});
    o = o[current];
    return next;
  });
  o[leaf] = data[item];
  return result;
}, {});

The zero-fill right shift operator ( >>> ) is the used to mimic the internal ToUint32 operation in JS, in order to ensure that a string is a valid array's index, otherwise is considered a property (eg "1.5" can be parsed as number, but is not a valid array's index). See ECMAScript specs section 15.4

This is definitely overkill, but here's an example on how you could do it by lexing your key syntax with a traditionnal lexer. The advantage would be having good error reporting as well as easily being able to enhance the syntax.

Here's an example of the kind of syntax error you could get with something like:

expandKeys({"person[profile][0]degree_type]": "Bachelor's" });

Uncaught Error: invalid key 'person[profile][0]degree_type]'; expected '[' and instead saw 'd' at character 18

 /*jshint esnext:true*/ var expandKeys = (function () { 'use strict'; var LBRACK = '[', RBRACK = ']'; return function (obj) { var result = {}; Object.keys(obj).forEach(function (k) { var tokens = tokensFrom(k), o = result, t = nextFrom(tokens, k).value; while (t) { let next = nextFrom(tokens, k); if (next.done) o[t] = obj[k]; else if (o.hasOwnProperty(t)) o = o[t]; else o = o[t] = isInt(+next.value)? [] : {}; t = next.value; } }); return result; }; function isInt(val) { return val >>> 0 === val; } function nextFrom(gen, k) { try { return gen.next(); } catch (e) { throw new Error("invalid key '" + k + "'; " + e.message); } } function *tokensFrom(input) { var state = prop, i = 0, c = input[0], token; while (state = state()) yield token; yield token; function prop() { var p = ''; while (c && c !== LBRACK && c !== RBRACK) { p += c; consume(); } if (!p) error('expected a property name'); token = p; return c? surroundedProp : null; } function surroundedProp() { match(LBRACK); prop(); match(RBRACK); return c? surroundedProp : null; } function match(char) { if (c === char) consume(); else error("expected '" + char + "' and instead saw '" + (c || '') + "'"); } function consume() { return c = input[++i]; } function error(msg) { throw new Error(msg + ' at character ' + i); } } })(); var data = { "person[profile][0][degree_type]": "Bachelor's", "person[profile][0][college_name]": "AI Miami International University of Art and Design", "person[profile][0][business_school_name]": "", "person[profile][0][law_school_name]": "", "person[profile][0][other_school_name]": "", "person[profile][0][undergraduate_major_name]": "Anthropology", "person[profile][0][max_gpa]": "", "person[profile][1][degree_type]": "", "person[profile][1][college_name]": "" }; var result = expandKeys(data), resultJson = JSON.stringify(result, null, 2), pre = document.createElement('pre'); pre.textContent = resultJson; document.body.appendChild(pre);

If you happen to be using ramda.js, this is a pretty small bit of code

 const obj = { 'nestedObject[first]': 10, 'nestedObject[second]': 5, 'nestedArray[0]': 3.1, 'nestedArray[1]': 3.2, 'param': 5 } const deFlatten = (object) => { const parsePath = R.pipe(R.match(/(\\w+)/g), R.map(R.ifElse(R.pipe(R.unary(parseInt), isNaN), R.identity, R.unary(parseInt)))); const reducer = (acc, flatKey) => R.set(R.lensPath(parsePath(flatKey)), object[flatKey])(acc); return R.pipe(R.keys, R.reduce(reducer, {}))(object); }; console.log(deFlatten(obj));
 <script src="https://cdn.jsdelivr.net/npm/ramda@0.25.0/dist/ramda.min.js"></script>

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