I have an array of paths:
const paths = [
"/path/to/first",
"/path/to/second",
"/path/to/third",
"/path/from/first",
"/path/from/second",
"/users/bill",
"/users/john"
];
I'd like to convert this to a javascript object of this form:
{
path: {
to: {
first,
second,
third
},
from: {
first,
second
}
},
users: {
bill,
john
}
}
How would I do this using pure javascript (not jQuery, and not using JSON.parse or other shortcuts).
If necessary, the end points can be key:value pairs that point to "null" for example:
user: {
bill: null,
john: null
}
I'm hoping for a non-recursive solution, but if this is significantly simpler with recursion, then that's ok!
CodePudding user response:
You should split the strings with a delimiter of /
, which should result in an array like ['', 'path', 'to', 'first']
for the first path in the array. Then its as simple as checking if path
is in the array, if so, is to
in the array, if so, add first
to obj.path.to.
Something like (and this could be refactored more, its a 0th pass attempt)
const obj = {
path: {
to: {
},
from: {
}
},
users: {
}
}
for (let path of paths) {
const pathArr = path.split('/');
if (pathArr.includes('path') && pathArr.includes('to')) {
obj.path.to[pathArr[3]] = null;
}
else if (pathArr.includes('path') && pathArr.includes('from')) {
obj.path.from[pathArr[3]] = null;
}
...
}
and so on.
Obviously this could be made more efficient, dynamically creating the keys path
, to
, from
, and users
, but this would work
CodePudding user response:
Here's a simple way to do it. It can only handle up to three though. You can try expanding it if you want.
const paths = [
"/path/to/first",
"/path/to/second",
"/path/to/third",
"/path/from/first",
"/path/from/second",
"/users/bill",
"/users/john"
];
const res = paths.reduce((acc, path) => {
const [first, second, third] = path.split("/").filter((a) => a);
if (first && !acc[first]) acc[first] = {};
if (second && !acc[first][second]) acc[first][second] = {};
if (third && !acc[first][second][third]) acc[first][second][third] = {};
return acc;
}, {});
console.log(res);
CodePudding user response:
Here is a version built atop some simple helper functions:
const setPath = ([p, ...ps]) => (v) => (o) =>
p == undefined ? v : Object .assign (
Array .isArray (o) || Number .isInteger (p) ? [] : {},
{...o, [p]: setPath (ps) (v) ((o || {}) [p])}
)
const hydrate = (xs) =>
xs .reduce ((a, [p, v]) => setPath (p) (v) (a), {})
const build = (ps) =>
hydrate (ps .map (p => [p .slice (1) .split ('/'), null]))
const paths = ["/path/to/first", "/path/to/second", "/path/to/third", "/path/from/first", "/path/from/second", "/users/bill", "/users/john"]
console .log (build (paths))
.as-console-wrapper {max-height: 100% !important; top: 0}
Here, setPath
takes a path such as ['path', 'to', 'first'], and a value such as 'null' and an object and returns a copy of that object, setting the value at that path, creating any intermediate nodes as needed.
hydrate` takes an array of path-value pairs and creates a brand new object as needed.
Our build
function then splits the paths on "/"
, maps each to a [['path', 'to', 'first'], null]
-style entry, and then passes the resulting array to hydrate
.
Recursion makes this code simpler. We could write setPath
in a more imperative way, using iteration versus recursion, but I would expect much uglier code for very little benefit. I'd love to be proven wrong on this, though.