I have the following object:
const modules = {
celebrity: {
actor: {
male: 'male',
female: 'female'
},
director: 'director'
},
movie: 'movie',
user: 'user'
};
In result I want an array of string as the following:
[
"celebrity/actor/male",
"celebrity/actor/female",
"celebrity/director",
"movie",
"user"
]
I create the following function:
function getPathsList(node, path = '') {
let pathsList = [];
const childs = Object.entries(node);
for (const child of childs) {
if (typeof child[1] === 'string') {
path += `/${child[1]}`
pathsList.push(path)
} else {
path += `/${child[0]}`
pathsList = [...pathsList, ...getPathsList(child[1], path, pathsList)]
}
}
return pathsList;
}
But I got:
[
"/celebrity/actor/male",
"/celebrity/actor/male/female",
"/celebrity/actor/director",
"/celebrity/movie",
"/celebrity/movie/user"
]
I know that the path variable should be initialized somewhere, but I can't figure it out.
You could use an appraoch which works without a path, but assembles the path by iterating the nested part object.
function getPathsList(node) {
const pathsList = [];
for (const [key, value] of Object.entries(node)) {
if (value && typeof value === 'object') {
pathsList.push(...getPathsList(value).map(p => `${key}/${p}`))
} else {
pathsList.push(key);
}
}
return pathsList;
}
const modules = {
celebrity: {
actor: {
male: 'male',
female: 'female'
},
director: 'director'
},
movie: 'movie',
user: 'user'
};
console.log(getPathsList(modules));
Another way, using reduce:
const modules = {celebrity:{actor:{male:"male",female:"female"},director:"director"},movie:"movie",user:"user"};
function getPathsList(node, path = '') {
return Object.entries(node)
.reduce(
(res, [k, v]) => res.concat(
typeof v === "string" ? `${path}${v}` : getPathsList(v, `${path}${k}/`
)
), []);
}
console.log(getPathsList(modules));
You may consider a "dfs" like algorithm where you explore every path from root to leaf.
You then join your path with '/'.
Subtlety: don't put the leaf itself into the path (e.g: otherwise you would get movie/movie)
Below an example using flatMap
const modules = {"celebrity":{"actor":{"male":"male","female":"female"},"director":"director"},"movie":"movie","user":"user"}
const flatten = x => {
if (typeof(x) === 'string') { return [[]] }
// each node has for children its entries
// each node returns an array of path
return Object.entries(x).flatMap(([k, v]) => {
return flatten(v).map(path => [k , ...path])
})
}
console.log(flatten(modules).map(path => path.join('/')))
Where is the difficulty ?
const
modules =
{ celebrity:
{ actor: { male: 'male', female: 'female' }
, director: 'director'
}
, movie: 'movie'
, user: 'user'
}
, pathsList = []
;
function getPathsList( obj, path='' )
{
for (let key in obj )
{
if (typeof(obj[key]) === 'object') getPathsList( obj[key], path+'/'+key )
else pathsList.push( (path+'/'+key).substring(1) )
}
}
getPathsList( modules )
console.log( pathsList )
A very simple recursive solution using Object .entries returns an array containing only an empty string for non-objects, and otherwise, for every key-value, combines the key with the results of a recursive call to the value. The only slightly tricky part is to not insert the slash (/) before an empty string. It looks like this:
const getPathsList = (obj) => Object (obj) === obj
? Object .entries (obj) .flatMap (
([k, v]) => getPathsList (v) .map (p => p ? k + '/' + p : k)
)
: ['']
const modules = {celebrity: {actor: {male: 'male', female: 'female'}, director: 'director'}, movie: 'movie', user: 'user'}
console .log (getPathsList (modules))
But I would prefer to do this a slightly different way, building our function atop one that gathers the result into arrays of values (e.g. [['celebrity', 'actor', 'male'], ['celebrity', 'actor', 'female'], ... ['user']]), then simply joining those new arrays together with slashes. It's quite similar:
const getPaths = (obj) => Object (obj) === obj
? Object .entries (obj) .flatMap (
([k, v]) => getPaths (v) .map (p => [k, ...p])
)
: [[]]
const getPathsList = (obj) =>
getPaths (obj) .map (xs => xs .join ('/'))
const modules = {celebrity: {actor: {male: 'male', female: 'female'}, director: 'director'}, movie: 'movie', user: 'user'}
console .log (getPathsList (modules))
I find that intermediate array format much more helpful.
This is a slightly less sophisticated version of getPaths than I generally write. Usually I distinguish between numeric array indices and string object keys, but that's not relevant here, since we're folding them back into strings, so this version is simplified.
Related
this is my object:
{
"name":"fff",
"onlineConsultation":false,
"image":"",
"primaryLocation":{
"locationName":"ggg",
"street":"",
},
"billingAndInsurance":[
],
"categories":[
""
],
"concernsTreated":[
""
],
"education":[
{
"nameOfInstitution":"ffff",
"description":"fff",
}
],
"experience":[
{
"from":"",
"current":"",
}
],
}
What is the algorithm to recursively remove all empty objects, and empty arrays from this?
this is my code:
function rm(obj) {
for (let k in obj) {
const s = JSON.stringify(obj[k]);
if (s === '""' || s === "[]" || s === "{}") {
delete obj[k];
}
if (Array.isArray(obj[k])) {
obj[k] = obj[k].filter((x) => {
const s = JSON.stringify(obj[x]);
return s !== '""' && s !== "[]" && s !== "{}";
});
obj[k] = obj[k].map(x=>{
return rm(x)
})
}
}
return obj
}
I'v tried multiple algorithms, but none worked. the one above should work with a little more completeness. But I'v exhausted all my resources to make it work
One nice thing about keeping around helpful functions is that you can often solve for your new requirements pretty simply. Using some library functions I've written over the years, I was able to write this version:
const removeEmpties = (input) =>
pathEntries (input)
.filter (([k, v]) => v !== '')
.reduce ((a, [k, v]) => assocPath (k, v, a), {})
This uses two function I had around, pathEntries and assocPath, and I'll give their implementations below. It returns the following when given the input you supplied:
{
name: "fff",
onlineConsultation: false,
primaryLocation: {
locationName: "ggg"
},
education: [
{
nameOfInstitution: "ffff",
description: "fff"
}
]
}
This removes empty string, arrays with no values (after the empty strings are removed) and objects with no non-empty values.
We begin by calling pathEntries (which I've used in other answers here, including a fairly recent one.) This collects paths to all the leaf nodes in the input object, along with the values at those leaves. The paths are stored as arrays of strings (for objects) or numbers (for arrays.) And they are embedded in an array with the value. So after that step we get something like
[
[["name"], "fff"],
[["onlineConsultation"], false],
[["image"], ""],
[["primaryLocation", "locationName"], "ggg"],
[["primaryLocation", "street"], ""],
[["categories", 0], ""],
[["concernsTreated", 0], ""],
[["education", 0, "nameOfInstitution"], "ffff"],
[["education", 0, "description"],"fff"],
[["experience", 0, "from"], ""],
[["experience", 0, "current"], ""]
]
This should looks something like the result of Object.entries for an object, except that the key is not a property name but an entire path.
Next we filter to remove any with an empty string value, yielding:
[
[["name"], "fff"],
[["onlineConsultation"], false],
[["primaryLocation", "locationName"], "ggg"],
[["education", 0, "nameOfInstitution"], "ffff"],
[["education", 0, "description"],"fff"],
]
Then by reducing calls to assocPath (another function I've used quite a few times, including in a very interesting question) over this list and an empty object, we hydrate a complete object with just these leaf nodes at their correct paths, and we get the answer we're seeking. assocPath is an extension of another function assoc, which immutably associates a property name with a value in an object. While it's not as simple as this, due to handling of arrays as well as objects, you can think of assoc like (name, val, obj) => ({...obj, [name]: val}) assocPath does something similar for object paths instead of property names.
The point is that I wrote only one new function for this, and otherwise used things I had around.
Often I would prefer to write a recursive function for this, and I did so recently for a similar problem. But that wasn't easily extensible to this issue, where, if I understand correctly, we want to exclude an empty string in an array, and then, if that array itself is now empty, to also exclude it. This technique makes that straightforward. In the implementation below we'll see that pathEntries depends upon a recursive function, and assocPath is itself recursive, so I guess there's still recursion going on!
I also should note that assocPath and the path function used in pathEntries are inspired by Ramda (disclaimer: I'm one of it's authors.) I built my first pass at this in the Ramda REPL and only after it was working did I port it to vanilla JS, using the versions of dependencies I've created for those previous questions. So even though there are a number of functions in the snippet below, it was quite quick to write.
const path = (ps = []) => (obj = {}) =>
ps .reduce ((o, p) => (o || {}) [p], obj)
const assoc = (prop, val, obj) =>
Number .isInteger (prop) && Array .isArray (obj)
? [... obj .slice (0, prop), val, ...obj .slice (prop + 1)]
: {...obj, [prop]: val}
const assocPath = ([p = undefined, ...ps], val, obj) =>
p == undefined
? obj
: ps.length == 0
? assoc(p, val, obj)
: assoc(p, assocPath(ps, val, obj[p] || (obj[p] = Number .isInteger (ps[0]) ? [] : {})), obj)
const getPaths = (obj) =>
Object (obj) === obj
? Object .entries (obj) .flatMap (
([k, v]) => getPaths (v) .map (p => [Array.isArray(obj) ? Number(k) : k, ... p])
)
: [[]]
const pathEntries = (obj) =>
getPaths (obj) .map (p => [p, path (p) (obj)])
const removeEmpties = (input) =>
pathEntries (input)
.filter (([k, v]) => v !== '')
.reduce ((a, [k, v]) => assocPath (k, v, a), {})
const input = {name: "fff", onlineConsultation: false, image: "", primaryLocation: {locationName: "ggg", street:""}, billingAndInsurance: [], categories: [""], concernsTreated: [""], education: [{nameOfInstitution: "ffff", description: "fff"}], experience: [{from: "", current:""}]}
console .log(removeEmpties (input))
At some point, I may choose to go a little further. I see a hydrate function looking to be pulled out:
const hydrate = (entries) =>
entries .reduce ((a, [k, v]) => assocPath2(k, v, a), {})
const removeEmpties = (input) =>
hydrate (pathEntries (input) .filter (([k, v]) => v !== ''))
And I can also see this being written more Ramda-style like this:
const hydrate = reduce ((a, [k, v]) => assocPath(k, v, a), {})
const removeEmpties = pipe (pathEntries, filter(valueNotEmpty), hydrate)
with an appropriate version of valuesNotEmpty.
But all that is for another day.
It's an interesting problem. I think it can be solved elegantly if we write a generic map and filter function that works on both Arrays and Objects -
const map = (t, f) =>
isArray(t)
? t.map(f)
: isObject(t)
? Object.fromEntries(Object.entries(t).map(([k, v]) => [k, f(v, k)]))
: t
const filter = (t, f) =>
isArray(t)
? t.filter(f)
: isObject(t)
? Object.fromEntries(Object.entries(t).filter(([k, v]) => f(v, k)))
: t
We can write your removeEmpties program easily now -
if the input, t, is an object, recursively map over it and keep the non-empty values
(inductive) t is not an object. If t is a non-empty value, return t
(inductive) t is not an object and t is an empty value. Return the empty sentinel
const empty =
Symbol()
const removeEmpties = (t = {}) =>
isObject(t)
? filter(map(t, removeEmpties), nonEmpty) // 1
: nonEmpty(t)
? t // 2
: empty // 3
Now we have to define what it means to be nonEmpty -
const nonEmpty = t =>
isArray(t)
? t.length > 0
: isObject(t)
? Object.keys(t).length > 0
: isString(t)
? t.length > 0
: t !== empty // <- all other t are OK, except for sentinel
To this point we have use is* functions to do dynamic type-checking. We will define those now -
const isArray = t => Array.isArray(t)
const isObject = t => Object(t) === t
const isString = t => String(t) === t
const isNumber = t => Number(t) === t
const isMyType = t => // As many types as you want
Finally we can compute the result of your input -
const input =
{name:"fff",zero:0,onlineConsultation:false,image:"",primaryLocation:{locationName:"ggg",street:""},billingAndInsurance:[],categories:[""],concernsTreated:[""],education:[{nameOfInstitution:"ffff",description:"fff"}],experience:[{from:"",current:""}]}
const result =
removeEmpties(input)
console.log(JSON.stringify(result, null, 2))
{
"name": "fff",
"zero": 0,
"onlineConsultation": false,
"primaryLocation": {
"locationName": "ggg"
},
"education": [
{
"nameOfInstitution": "ffff",
"description": "fff"
}
]
}
Expand the program below to verify the result in your browser -
const map = (t, f) =>
isArray(t)
? t.map(f)
: isObject(t)
? Object.fromEntries(Object.entries(t).map(([k, v]) => [k, f(v, k)]))
: t
const filter = (t, f) =>
isArray(t)
? t.filter(f)
: isObject(t)
? Object.fromEntries(Object.entries(t).filter(([k, v]) => f(v, k)))
: t
const empty =
Symbol()
const removeEmpties = (t = {}) =>
isObject(t)
? filter(map(t, removeEmpties), nonEmpty)
: nonEmpty(t)
? t
: empty
const isArray = t => Array.isArray(t)
const isObject = t => Object(t) === t
const isString = t => String(t) === t
const nonEmpty = t =>
isArray(t)
? t.length > 0
: isObject(t)
? Object.keys(t).length > 0
: isString(t)
? t.length > 0
: t !== empty
const input =
{name:"fff",zero:0,onlineConsultation:false,image:"",primaryLocation:{locationName:"ggg",street:""},billingAndInsurance:[],categories:[""],concernsTreated:[""],education:[{nameOfInstitution:"ffff",description:"fff"}],experience:[{from:"",current:""}]}
const result =
removeEmpties(input)
console.log(JSON.stringify(result, null, 2))
function removeEmpty(obj){
if(obj.__proto__.constructor.name==="Array"){
obj = obj.filter(e=>e.length)
return obj.map((ele,i)=>{
if(obj.__proto__.constructor.name==="Object")return removeEmpty(ele) /* calling the same function*/
else return ele
})
}
if(obj.__proto__.constructor.name==="Object")for(let key in obj){
switch(obj[key].__proto__.constructor.name){
case "String":
if(obj[key].length===0)delete obj[key]
break;
case "Array":
obj[key] = removeEmpty(obj[key]) /* calling the same function*/
if(! obj[key].length)delete obj[key]
break;
case "Object":
obj[key] = removeEmpty(obj[key]) /* calling the same function*/
break;
}
}
return obj;
}
const input = {name: "fff", onlineConsultation: false, image: "", primaryLocation: {locationName: "ggg", street:""}, billingAndInsurance: [], categories: [""], concernsTreated: [""], education: [{nameOfInstitution: "ffff", description: "fff"}], experience: [{from: "", current:""}]}
console .log(removeEmpty(input))
function dropEmptyElements(object) {
switch (typeof object) {
case "object":
const keys = Object.keys(object || {}).filter(key => {
const value = dropEmptyElements(object[key]);
if(value === undefined) {
delete object[key];
}
return value !== undefined;
});
return keys.length === 0 ? undefined : object;
case "string":
return object.length > 0 ? object : undefined;
default:
return object;
}
}
This should do the trick for you ;)
I'm new to programming, and I was working a question which is bother me for a while.
I want to recursively create a nested object from another nested object in javascript,
below is the sample data for input, but in real situation, I don't how deep will this object to be.
nums = {
Obj:{
x1:{
x11:43,
x12:4,
x13:612
},
x2:{
x21:4,
x22:7,
},
x3:2,
}}
this is the result I want (see number is even or odd, even=true, odd=false)
res = {
Obj:{
x1:{
x11:false,
x12:true,
x13:true
},
x2:{
x21:true,
x22:false,
},
x3:true,
}}
and this is my code
const nums = {
Obj:{
x1:{
x11:43,
x12:4,
x13:612
},
x2:{
x21:4,
x22:7,
},
x3:2,
}
}
const res ={};
getResult(nums);
console.log(res);
function getResult(x){
Object.keys(x).forEach(element => {
if(isNaN(x[element])){
res[element]=getResult(x[element]);
} else {
let result = (x[element] % 2 < 1)? true:false;
return {[element]: result}; // this is where I don't know what to, I try to return a object,
// but it gives{x1: undefined, x2: undefined, Obj: undefined}
//
// if I change to "return res[element]=result"
// every sub-Object will add under the same level
}
});
}
I will really appreciate if someone can help me on this.
Instead of return {[element]: result}; , overwrite the value, and return the mutated object from the function after the loop :
note that this will mutate the original object, if you want to keep it, make a copy :
const copy = JSON.parse(JSON.stringify(nums));
const nums = {
Obj: {
x1: {
x11: 43,
x12: 4,
x13: 612
},
x2: {
x21: 4,
x22: 7,
},
x3: 2,
}
}
const res = {};
const copy = JSON.parse(JSON.stringify(nums));
getResult(copy);
console.log(res);
function getResult(x) {
Object.keys(x).forEach(element => {
if (isNaN(x[element])) {
res[element] = getResult(x[element]);
} else {
let result = (x[element] % 2 < 1) ? true : false;
x[element] = result; // overwrite the number with true or flse
}
});
return x; // return the mutated object
}
Instead of mutating something make something more functional and return a new object:
const getResults = o => typeof o === "object"
? Object.keys(o).reduce((a, k) => ({ ...a, [k]: getResults(o[k]) }), {})
: o % 2 === 1;
Basically we check if object is an object (using typeof) and go deeper if so. Otherwise we check if it is odd or even.
You can also think of this more generically, writing a function that will apply your transformation to all the leaf nodes of your object, then calling it with an isEven function. Here's one technique:
const mapLeaves = (fn) => (tree) =>
typeof tree == "object"
? Object .fromEntries (Object .entries (tree) .map (
([k, v]) => [k, mapLeaves (fn) (v)]
))
: fn (tree)
const isEven = (n) => n % 2 == 0
const nums = {Obj: {x1: {x11: 43, x12: 4, x13: 612}, x2: {x21: 4, x22: 7}, x3: 2}}
console .log (
mapLeaves (isEven) (nums)
)
And of course mapLeaves (isEven) is a reusable function that you could apply to multiple objects.
This does not handle arrays. It would only be slightly more complex to create a version of mapLeaves that also applied this to entries of an array:
const mapLeaves = (fn) => (tree) =>
Array .isArray (tree)
? tree .map (x => mapLeaves (fn) (x))
: typeof tree == "object"
? Object .fromEntries (Object .entries (tree) .map (
([k, v]) => [k, mapLeaves (fn) (v)]
))
: fn (tree)
I want to loop through a nested object and modify each key (delete first character).
The following code iterates through the whole object but does not modify the key. The Object still looks the same after running the function.
const removeFirstCharacterOfKey = (obj) => {
Object.keys(obj).forEach((key) => {
if (typeof obj[key] === 'object') {
if(Array.isArray(obj[key])) {
return;
}
return removeFirstCharacterOfKey (obj[key]);
}
key = key.substring(1);
});
}
A possibility would also be to create a new object with modified keys. Is it possible to achieve this?
https://codesandbox.io/embed/cool-golick-vdf6k?fontsize=14&hidenavigation=1&previewwindow=tests&theme=dark
The array.reduce is your best bet here. Basic implementation in your case would be
function serializer(obj) {
return Object.entries(obj).reduce((obj, [key, entry]) => ({
...obj,
[key.substr(1)]: entry
}),{});
}
However you have some additional check for array so we need to modify the method to accept custom modifier
function serializer(obj, modifier) {
return Object.entries(obj).reduce(
(obj, [key, entry]) => ({
...obj,
[modifier(key, entry)]: entry
}),
{}
);
}
and use it as
const myObj = {
$a: "hello a",
$b: "hello b",
$c: [],
$d: "hello d",
$e: "hello e"
};
function serializer(obj, modifier) {
return Object.entries(obj).reduce(
(obj, [key, entry]) => ({
...obj,
[modifier(key, entry)]: entry
}),
{}
);
}
const serialized = serializer(myObj, (key, entry) =>
Array.isArray(entry) ? key : key.substr(1)
);
test("", () => {
expect(serialized).toEqual({
a: "hello a",
b: "hello b",
$c: [],
d: "hello d",
e: "hello e"
});
});
In your current code, you're never mutating the existing object (or creating a new object), you're only reassigning the key parameter, which won't have any side-effects.
Consider using a recursive Object.fromEntries mapping function instead:
const obj = {
foo: {
bar: 'val',
baz: {
buzz: 'val',
buzz2: 'val'
}
}
};
const objWithSlicedKeys = obj => Object.fromEntries(
Object.entries(obj).map(
([key, val]) => [
key.slice(1),
typeof val === 'object' && val !== null ? objWithSlicedKeys(val) : val
]
)
);
console.log(objWithSlicedKeys(obj));
I have a requirement to replace the available keys with the desired keys in an object for which I was trying to execute below code, which later I found out to be incorrect usage of filter for desired output. hence I need help in getting the desired results using es6 array functions.
const columns = Object.keys(someArray).filter((columnName) => {
if (someCheck === "somecheck") {
if (columnName === 'MyName') {
const newcolumnName = `Pranav`;
return newcolumnName;
} else if (columnName === 'YourName') {
const newcolumnName = `Alex`;
return newcolumnName;
}
} else {
return (columnName !== 'sometingelse') ? columnName : '';
}
}
);
Here the someArray is as below:
someArray{
abc:"djfhdjf",
xyz:"ssss",
MyName:"onename",
YourName:"somename",
sometingelse:'somevalue'
}
I am expecting columns to be:
columns{
abc:"djfhdjf",
xyz:"ssss",
Pranav:"onename",
Alex:"somename",
sometingelse:'somevalue'
}
Please suggest how can I achieve the above expected output?
Note: I dont want to use function keyword in callbacks to avoid eslint errors
You could filter the wanted keys for replacement and replace the keys by using a new key and eleting the old one.
const
object = { abc: "djfhdjf", xyz: "ssss", MyName: "onename", YourName: "somename", sometingelse: 'somevalue' },
replacements = { MyName: 'Pranav', YourName: 'Alex', sometingelse: '' };
Object
.keys(object)
.filter(k => k in replacements)
.forEach(k => {
object[replacements[k]] = object[k];
delete object[k];
});
console.log(object);
For generating an object, you could map new objects and assign them to a single object.
const
object = { abc: "djfhdjf", xyz: "ssss", MyName: "onename", YourName: "somename", sometingelse: 'somevalue' },
replacements = { MyName: 'Pranav', YourName: 'Alex', sometingelse: '' },
result = Object.assign(...Object
.entries(object)
.map(([k, v]) => ({ [k in replacements ? replacements[k] : k]: v }))
);
console.log(result);
const obj = {
abc: 'djfhdjf',
xyz: 'ssss',
MyName: 'onename',
YourName: 'somename',
sometingelse: 'somevalue'
};
const newObj = Object.keys(obj).reduce((acc, key) => {
if (key === 'MyName') {
acc.newMyName = obj[key];
} else if (key === 'YourName') {
acc.newYourName = obj[key];
} else {
acc[key] = obj[key];
}
return acc;
}, {});
console.log('newObj = ', newObj);
Here is my approach, a bit long solution, but its on purpose so you can see how to do it simple without too much abstraction:
const someArray = {
abc:"djfhdjf",
xyz:"ssss",
MyName:"onename",
YourName:"somename",
sometingelse:'somevalue'
}
let foo = Object.keys(someArray).map(key => {
if(key === 'MyName') {
return 'Alex'
} else if(key === 'YourName') {
key = 'Pranav'
}
return key;
})
let bar = Object.entries(someArray).map((el, i) => {
el[0] = res[i];
return el;
})
let baz = r.reduce((acc, el)=>{
acc[`${el[0]}`] = el[1];
return acc;
},{})
console.log(baz);
You could use .reduce like so. It uses a similar idea that Nina proposed by using an object to hold your replacements. Here I have used the spread syntax to add the changed key to the accumulated object, along with it's associated value.
const someArray = {abc: "djfhdjf", xyz: "ssss", MyName: "onename", YourName: "somename", sometingelse: 'somevalue'},
toUse = {MyName: "Pranav", YourName: "Alex"}, // define the keys you want to change and what they should change to
res = Object.keys(someArray).reduce((acc, key) =>
({...acc, [key in toUse ? toUse[key] : key]:someArray[key]})
, {});
console.log(res);
I am running a reduce on the keys of some array starting with an empty object. The ...acc spreads out all the properties in the reduced object. ...{ [keysMap[key] || key]: obj[key] } checks if the current key is present in keysMap.If it is present,it uses that key (keysMap[key]) otherwise it just uses the keys of the existing object.(|| key).Hope that makes sense
const renameKeys = (keysMap, obj) =>
Object.keys(obj).reduce(
(acc, key) => ({
...acc,
...{ [keysMap[key] || key]: obj[key] }
}),
{}
)
const columns = renameKeys({'MyName':'Pranav','YourName':'Alex'},someArray)
i have this type of object which fetched from Redis
{
'username': 'hamet',
'username_Type': 'string',
'meta': 'object',
'meta_Type': 'object',
'meta.avatar': '/avatar.png',
'meta.avatar_Type': 'string',
'meta.active': 'false',
'meta.active_Type': 'boolean',
'meta.someArr': 'array',
'meta.someArr_Type': 'array',
'meta.someArr.0': 'object',
'meta.someArr.0_Type': 'object',
'meta.someArr.0.field': '123',
'meta.someArr.0.field_Type': 'number',
'meta.someArr.1': 'object',
'meta.someArr.1_Type': 'object',
'meta.someArr.1.field': '321',
'meta.someArr.1.field_Type': 'number'
}
all i want is convert this object to valid object like this:
{
username: 'hamet',
meta: {
avatar: '/avatar.png',
active: false,
someArr: [
{ field: 123 },
{ field: 321 }
]
}
}
once i created iterated function, but there was a problem with that. is it possible to convert with Iterated function and how?
You could create object with value types that you will use for creating new instances of different data types and then use reduce() method to build your object.
const data = {"username":"hamet","username_Type":"string","meta":"object","meta_Type":"object","meta.avatar":"/avatar.png","meta.avatar_Type":"string","meta.active":"false","meta.active_Type":"boolean","meta.someArr":"array","meta.someArr_Type":"array","meta.someArr.0":"object","meta.someArr.0_Type":"object","meta.someArr.0.field":"123","meta.someArr.0.field_Type":"number","meta.someArr.1":"object","meta.someArr.1_Type":"object","meta.someArr.1.field":"321","meta.someArr.1.field_Type":"number"}
const result = {}
const create = {'string': String,'number': Number,'boolean': Boolean,'array': Array,'object': Object}
const findType = (key, obj) => obj[key]
Object.keys(data).forEach(key => {
if (!key.includes('Type')) {
key.split('.').reduce((r, e, i, arr) => {
let type = findType(key + '_Type', data);
let value = create[data[key]] || arr[i + 1] ? new create[type] : new create[type](data[key]).valueOf()
if (data[key] == 'false') value = false
r[e] = r[e] || value;
return r[e]
}, result)
}
})
console.log(result)
Get an array of keys with Object.keys(). Filter out the _Type keys. Sort the keys to ensure that parents (shorter) keys are first, since keys` order in an object is not ensured.
Reduce the array of keys, and for each key get it's value by type. If the type is not object/array use the actual key value. Iterate the result object with Array.forEach(), until you get to the leaf. Add the key with the value.
const obj = {"meta.someArr.1.field":"321","username":"hamet","username_Type":"string","meta":"object","meta_Type":"object","meta.avatar":"/avatar.png","meta.avatar_Type":"string","meta.active":"false","meta.active_Type":"boolean","meta.someArr":"array","meta.someArr_Type":"array","meta.someArr.0":"object","meta.someArr.0_Type":"object","meta.someArr.0.field":"123","meta.someArr.0.field_Type":"number","meta.someArr.1":"object","meta.someArr.1_Type":"object","meta.someArr.1.field_Type":"number"};
const byType = {
object: Object,
array: Array
};
const result = Object.keys(obj)
.filter((k) => !k.includes('_Type')) // remove Type keys
.sort((a, b) => a.length - b.length) // ensures that shorter (parent) keys are first
.reduce((r, k) => {
const type = obj[`${k}_Type`];
const valueByType = byType[type] && byType[type]();
const value = valueByType ? valueByType : obj[k];
const keys = k.split('.');
let current = r;
keys.forEach((key, i) => {
if(!(key in current)) current[key] = value;
else current = current[key];
});
return r;
}, {});
console.log(result);
const result = {};
function apply(obj, value, key, ...keys) {
if(keys.length) {
apply(obj[key] || (obj[key] = {}), value ...keys);
} else {
obj[key] = value;
}
}
for(const [key, value] of Object.entries(yourObj))
apply(result, value, ...key.split("."));
You could use a recursive approach to generate the nested structure. I havent included a check if key is a number so that it creates an array, thats your job ;)
If you prefer functional programming:
const apply = (obj, value, ...keys) => keys.slice(1).reduce((obj, key) => obj[key] || (obj[key] = {}), obj)[keys.pop()] = value;