I have an array of string, I want to convert to nested object where key is value of array. I've try with reduce, but all of the value nested with the last object is last item from the array. Can you help me?
Thanks!
let m = [
'1.',
'1.1.',
'1.2.',
'1.3.',
'1.4.',
'1.1.1.',
'1.1.2.',
'1.1.3.',
'1.2.1.',
'1.2.2.',
'1.3.1.',
'1.3.2.',
'1.3.3.',
'1.3.4.',
'1.4.1.',
'1.4.3.',
];
I want to convert this array to nested object.
Return
{
"1":{
"1":{
"1":"1.1.1.",
"2":"1.1.2.",
"3":"1.1.3."
},
"2":{
"1":"1.2.1.",
"2":"1.2.2."
},
"3":{
"1":"1.3.1.",
"2":"1.3.2.",
"4":"1.3.4."
},
"4":{
"1":"1.4.1.",
"3":"1.4.3."
}
}
}
Here's a working example using reduce().
let m = [
'1.',
'1.1.',
'1.2.',
'1.3.',
'1.4.',
'1.1.1.',
'1.1.2.',
'1.1.3.',
'1.2.1.',
'1.2.2.',
'1.3.1.',
'1.3.2.',
'1.3.3.',
'1.3.4.',
'1.4.1.',
'1.4.3.',
];
const addToObj = (obj_, path, newData) => {
const obj = typeof obj_ === 'string' ? {} : obj_ // Special logic to cause a value at 1.2.3. to override a value at 1.2.
if (path.length === 0) return newData
const [head, ...tail] = path
return {
...obj,
[head]: addToObj(obj[head] || {}, tail, newData),
}
}
const res = m.reduce(
(obj, path) => addToObj(obj, path.split('.').slice(0, -1), path),
{}
)
console.log(res)
It works by using a addToObj function, that'll take an object as a parameter, a path into that object, and a new value wanted at the end of that path, and it'll return a new object with the new value added.
Special logic was added to addToObj() to make sure keys like 1.2.3. always overwrote a string value that might have been placed at 1.2..
Related
I am trying to merge some JSON data sets BY key value WHILE including duplicate values WHERE the key matches.
I have tried this quite a bit now but can't seem to produce the object that I need.
Object 1
[
{"userId":"1",
"email":"email1#gmail.com"
},
{"userId":"2",
"email":"email2#gmail.com"
}
]
Object 2
[
{"id":"1abc",
"listingId":"4def",
"userId":"2"
},
{"id":"2abc",
"listingId":"2def",
"userId":"1"
},
{"id":"3abc",
"listingId":"3def",
"userId":"2"
}
]
I need to merge these objects in a way that looks like this:
Desired Output
[
{"id":"1abc",
"listingId":"4def",
"userId":"2",
"email":"email2#gmail.com"
},
{"id":"2abc",
"listingId":"2def",
"userId":"1",
"email":"email1#gmail.com"
},
{"id":"3abc",
"listingId":"3def",
"userId":"2",
"email":"email2#gmail.com"
}
]
Problems I am Experiencing
I am able to merge the data sets successfully using a function that looks like this:
function merge(a, b, key) {
function x(a) {
a.forEach(function (b) {
if (!(b[key] in obj)) {
obj[b[key]] = obj[b[key]] || {};
array.push(obj[b[key]]);
}
Object.keys(b).forEach(function (k) {
obj[b[key]][k] = b[k];
});
});
}
var array = [],
obj = {};
x(a);
x(b);
return array;
}
https://stackoverflow.com/a/35094948/1951144
But it produces results that look like this:
[
{"id":"1abc",
"listingId":"4def",
"userId":"2",
"email":"email2#gmail.com"
},
{"id":"2abc",
"listingId":"2def",
"userId":"1",
"email":"email1#gmail.com"
}
]
Is there a way to use the above function WHILE keeping AND including the duplicate values where my keys match?
For each element in arr2, create a new element containing the props of the item from arr2, and the email of the corresponding entry in arr1.
let arr1 = [
{"userId":"1",
"email":"email1#gmail.com"
},
{"userId":"2",
"email":"email2#gmail.com"
}
];
let arr2 = [
{"id":"1abc",
"listingId":"4def",
"userId":"2"
},
{"id":"2abc",
"listingId":"2def",
"userId":"1"
},
{"id":"3abc",
"listingId":"3def",
"userId":"2"
}
];
let output = arr2.map(a2 => ({...a2, email: arr1.find(a1 => a1.userId === a2.userId)?.email}));
console.log(output);
This solution works even if the key isn't known yet. .flatMap() both arrays and pass in the desired key (in example it's "userId"). Use Object.entries() on each object so they will be an array of pairs.
[{A1: A1v}, {A2: A2v},...]
// into
[[A1, A1v], [A2, A2v],...]
.flatMap() the second array and on each iteration .flatMap() the first array. Then compare the given key ("userID") with the key of each object from the second array ("a") AND the value of that key and the value of the key of the object in the first array.
a === key && av === bv
If both criteria are meet then merge those objects and return it, otherwise...
? {...objA, ...objB}
return an empty array, which ultimately results to nothing since .flatMap() flattens one level of arrays.
: []
const arrA=[{userId:"1",email:"email1#gmail.com"},{userId:"2",email:"email2#gmail.com"}];const arrB=[{id:"1abc",listingId:"4def",userId:"2"},{id:"2abc",listingId:"2def",userId:"1"},{id:"3abc",listingId:"3def",userId:"2"}];
function masterKey(primary, key, secondary) {
let result = secondary.flatMap(objB => Object.entries(objB).flatMap(([b, bv]) =>
primary.flatMap(objA => Object.entries(objA).flatMap(([a, av]) =>
a === key && av === bv ? {...objA, ...objB} : []))));
return result;
}
console.log(masterKey(arrA, "userId", arrB));
I'm having the JSON like this i need to group this JSON with all the keys in JSON object and value should in array (excluding duplicates).
var people = [
{sex:"Male", name:"Jeff"},
{sex:"Female", name:"Megan"},
{sex:"Male", name:"Taylor"},
{sex:"Female", name:"Madison"}
];
My output should be like
{"sex":["Male","Female"],"name":["Jeff","Megan","Taylor","Madison"]}
how we can able to achieve this
function getValues(array) {
var result = {};
array.forEach(obj => {
Object.keys(obj).forEach(key => {
if(!Array.isArray(result[key]))
result[key] = [];
result[key].push(obj[key]);
})
})
return result;
}
You could use the Array.reduce() method to transform your array into a single object:
var people = [
{sex:"Male", name:"Jeff"},
{sex:"Female", name:"Megan"},
{sex:"Male", name:"Taylor"},
{sex:"Female", name:"Madison"}
];
const transformed = people.reduce((acc, e) => {
Object.keys(e).forEach((k) => {
if (!acc[k]) acc[k] = [];
if (!acc[k].includes(e[k])) acc[k].push(e[k]);
});
return acc;
}, {});
console.log(transformed);
If for one of the object keys (sex or name in this case) a value array does not exist, it is created. Before a value is pushed into any of the value arrays, it is verified that it is not already present in that array.
I am looking to find out an index and group the item belong to in a parent json group, how can I do it?
I am open to reformat the json as well if need be,
I tried JSON.stringify() but it returns the wrong index as well.
let Content = {
group1: [
[{content:"hello"},{content:"world"}],
[{content:"hello1"},{content:"world"}],
[{content:"hello2"},{content:"world"}],
[{content:"hello3"},{content:"world"}],
[{content:"hello4"},{content:"world"}],
[{content:"hello5"},{content:"world"}],
],
group2: [
[{content:"hello10"},{content:"world"}],
[{content:"hello11"},{content:"world"}],
[{content:"hello12"},{content:"world"}],
[{content:"hello13"},{content:"world"}],
[{content:"hello14"},{content:"world"}],
[{content:"hello15"},{content:"world"}],
],
};
// let currentItem = {type:'group2',index:5};
// let currentItemContent = Content[currentItem.type][currentItem.index];
let obj = [{content:"hello15"},{content:"world"}];
let newIndex = Content["group1"].indexOf(obj);
let type = "group1";
if(newIndex < 0)
{
type="group2"
console.log(Content["group2"]);
newIndex = Content["group2"].indexOf(obj);
}
console.log({"type":type,"index":newIndex});
expected: {type:'group2',index:5}
Loop through the Content object using for...in. Check if the given array is in each group by using findIndex. Since both the objects in the array seem to be in order, you can simply compare the string returned by JSON.stringify
let Content={group1:[[{content:"hello"},{content:"world"}],[{content:"hello1"},{content:"world"}],[{content:"hello2"},{content:"world"}],[{content:"hello3"},{content:"world"}],[{content:"hello4"},{content:"world"}],[{content:"hello5"},{content:"world"}]],group2:[[{content:"hello10"},{content:"world"}],[{content:"hello11"},{content:"world"}],[{content:"hello12"},{content:"world"}],[{content:"hello13"},{content:"world"}],[{content:"hello14"},{content:"world"}],[{content:"hello15"},{content:"world"}]]}
function find(input, search) {
for (const type in input) {
const group = input[type];
const index = group.findIndex(a => JSON.stringify(a) === JSON.stringify(search));
if (index != -1)
return { type, index }
}
return null
}
console.log(find(Content, [{content:"hello15"},{content:"world"}]))
console.log(find(Content, [{content:"hello"},{content:"world"}]))
You could also use Array.find in combination with Object.keys and Array.some. The array comparison you can do via JSON.stringify however remember that if your keys are in different order that would not work:
[{content:"world"},{content:"hello"}] vs [{content:"hello"},{content:"world"}]
would not match as you would expect since you are matching on strings and they are now different.
let Content = { group1: [ [{content:"hello"},{content:"world"}], [{content:"hello1"},{content:"world"}], [{content:"hello2"},{content:"world"}], [{content:"hello3"},{content:"world"}], [{content:"hello4"},{content:"world"}], [{content:"hello5"},{content:"world"}], ], group2: [ [{content:"hello10"},{content:"world"}], [{content:"hello11"},{content:"world"}], [{content:"hello12"},{content:"world"}], [{content:"hello13"},{content:"world"}], [{content:"hello14"},{content:"world"}], [{content:"hello15"},{content:"world"}], ], };
let findArray = (data, obj) => {
let index, group = Object.keys(data).find((k,i) => {
index = i
return data[k].some(x => JSON.stringify(x) === JSON.stringify(obj))
})
return { index, group }
}
console.log(findArray(Content, [{content:"hello"},{content:"world"}]))
console.log(findArray(Content, [{content:"hello10"},{content:"world"}]))
Is there a native (built in) in ES6 (or subsequent versions), Javascript or in TypeScript method to convert a JSON string to ES6 map OR a self-made parser to be implemented is the option? The goal is to preserve the order of the keys of the JSON string-encoded object.
Note: I deliberately don't use the word "parse" to avoid converting a JSON string first to ECMA script / JavaScript object which by definition has no order of its keys.
For example:
{"b": "bar", "a": "foo" } // <-- This is how the JSON string looks
I need:
{ b: "bar", a: "foo" } // <-- desired (map version of it)
UPDATE
https://jsbin.com/kiqeneluzi/1/edit?js,console
The only thing that I do differently is to get the keys with regex to maintain the order
let j = "{\"b\": \"bar\", \"a\": \"foo\", \"1\": \"value\"}"
let js = JSON.parse(j)
// Get the keys and maintain the order
let myRegex = /\"([^"]+)":/g;
let keys = []
while ((m = myRegex.exec(j)) !== null) {
keys.push(m[1])
}
// Transform each key to an object
let res = keys.reduce(function (acc, curr) {
acc.push({
[curr]: js[curr]
});
return acc
}, []);
console.log(res)
ORIGINAL
If I understand what you're trying to achieve for option 2. Here's what I came up with.
https://jsbin.com/pocisocoya/1/edit?js,console
let j = "{\"b\": \"bar\", \"a\": \"foo\"}"
let js = JSON.parse(j)
let res = Object.keys(js).reduce(function (acc, curr) {
acc.push({
[curr]: js[curr]
});
return acc
}, []);
console.log(res)
Basically get all the keys of the object, and then reduce it. What the reducer function convert each keys to an object
function jsonToMap(jsonStr) {
return new Map(JSON.parse(jsonStr));
}
More details : http://2ality.com/2015/08/es6-map-json.html
use for in loop
let map = new Map();
let jsonObj = {a:'a',b:'b',c:'c'}
for (let i in jsonObj){
map.set(i,jsonObj[i]);
}
btw, i saw the comment below and i think map is not ordered because you use key to achieve data in map, not the index.
This is a follow up post from Compare key values within object for duplicate for a follow up answer.
I have an object:
myObj = {
attendent-0-id:"123",
attendent-0-name:"Bob Smith",
attendent-1-id:"1234",
attendent-1-name:"Alex Smith",
attendent-2-id:"123",
attendent-2-name:"Charlie Smith",
attendent-0-id:"123",
attendent-0-name:"John Smith",
attendent-maxGuest:1,
attendent-party-name:"",
}
Thanks to help on here (Rick) I was able to get 90% of the way there.
function errorOnDuplicateIds(obj) {
const map = {};
const pattern = /^attendent-\d+-id$/;
for (const key of Object.keys(obj)) {
if (pattern.test(key)) {
const value = obj[key]
if (value in map) {
map[value] = [map[value], key];
} else {
map[value] = key
}
}
}
return map;
}
I am getting a return of:
array:[
0:(2) ["attendent-0-name", "attendent-1-name"]
1:"attendent-2-name"
]
but I am looking for:
array:[
0:(2) ["attendent-0-name", "attendent-1-name", "attendent-2-name"]
]
The issue I am having is that while this works if there are two matching keys it will not work (Correctly) if there are three or more.
If you want to have an array of all matches for each key in you map, you need to start by setting an array when you find a key the first time. On subsequent matches, just push into that array:
const myObj = {'attendent-0-id': "1234",'attendent-0-name': "Bob Smith",'attendent-1-id': "123",'attendent-1-name': "Alex Smith",'attendent-2-id': "123",'attendent-2-name': "Charlie Smith",'attendent-maxGuest': 1,'attendent-party-name': "",};
function errorOnDuplicateIds(obj) {
const map = {};
const pattern = /^attendent-\d+-id$/;
for (const key of Object.keys(obj)) {
if (pattern.test(key)) {
const value = obj[key]
if (value in map) {
map[value].push(key); // push a new value
} else {
map[value] = [key] // set it to an array
}
}
}
/* if you only want lengths > 1
otherwise just return map */
let filtered = Object.entries(map)
.reduce((a, [key, value]) => value.length > 1 ? Object.assign(a, {[key]: value}) : a, {})
return filtered;
}
console.log(errorOnDuplicateIds(myObj));
If you are only interested in values with more than one hit, you can reduce() down to a map with only values of length greater than one, which is what the last bit in the snippet does.