I have this nested object:
{
"PINS" : {
"2017" : {
"Nov-2017" : {
"VJkRWX7pTSl_5w1Np" : {
"pin" : "6K3jP5vLyN",
"say": "Hello"
},
"MsdsXiO9G9mwM3Qa" : {
"pin" : "hnPKh7ywvT",
"say": "Hello"
}
},
"Dec-2017" : {
"Mm35Gjb-nY0k2TV" : {
"pin" : "xWwaNNE2XG",
"say": "Hello"
},
"WzajCLEJmJHmzg0" : {
"pin" : "vMU1mKbZAi",
"say": "Hello"
}
}
},
"2018" : {
"Jan-2018" : {
"Wf8E1unVaOh03a43" : {
"pin" : "qXJCQREATD",
"say": "Hello"
},
"JZqP8fVCLSja6J82v" : {
"pin" : "o5D8S8Lvtb",
"say": "Hello"
}
},
"Feb-2018" : {
"lMMAKNLy8jtnnXAN" : {
"pin" : "9zDuHcw6qH",
"say": "Hello"
},
"e9EV3HDKCceM" : {
"pin" : "kPllwcoaob",
"say": "Hello"
}
}
}
}
}
what I need is to find for all the 'pin' keys, and get their values, to put them into an array.
Exactly, I need to have an array like this:
['6K3jP5vLyN', 'hnPKh7ywvT', 'xWwaNNE2XG', 'vMU1mKbZAi', 'qXJCQREATD', 'o5D8S8Lvtb', '9zDuHcw6qH', 'kPllwcoaob']
I have tried:
const array = [];
function iter(obj){
for(key in obj){
if(obj.pin)
array.push(obj.pin);
if(obj[key]!==null && typeof obj[key]==="object"){
iter(obj[key]);
}
}
}
iter(obj);
But I get the values of each key twice. Is there some improved way to do this?
You can use a recursive method to flatten the nested object to it's leaf values.
Option 1 - the value is the only property on the leaf (original answer before question updated)
The method extracts the current values to an array using Object.values(). It iterates the array with Array.map(), and flattens any value which is an object. The result of each run is spread into Array.concat() to flatten the nested arrays.
const data = {"PINS":{"2017":{"Nov-2017":{"VJkRWX7pTSl_5w1Np":{"pin":"6K3jP5vLyN"},"MsdsXiO9G9mwM3Qa":{"pin":"hnPKh7ywvT"}},"Dec-2017":{"Mm35Gjb-nY0k2TV":{"pin":"xWwaNNE2XG"},"WzajCLEJmJHmzg0":{"pin":"vMU1mKbZAi"}}},"2018":{"Jan-2018":{"Wf8E1unVaOh03a43":{"pin":"qXJCQREATD"},"JZqP8fVCLSja6J82v":{"pin":"o5D8S8Lvtb"}},"Feb-2018":{"lMMAKNLy8jtnnXAN":{"pin":"9zDuHcw6qH"},"e9EV3HDKCceM":{"pin":"kPllwcoaob"}}}}};
const flattenObj = (obj) =>
[].concat(...Object.values(obj).map((o) => typeof o === 'object' ? flattenObj(o) : o));
const result = flattenObj(data);
console.log(result);
Option 2 - the value is not the only property on the leaf
If your data contains other keys, this variant uses Object.entries() to extract a specific key:
const data = {"PINS":{"2017":{"Nov-2017":{"VJkRWX7pTSl_5w1Np":{"pin":"6K3jP5vLyN","say":"Hello"},"MsdsXiO9G9mwM3Qa":{"pin":"hnPKh7ywvT","say":"Hello"}},"Dec-2017":{"Mm35Gjb-nY0k2TV":{"pin":"xWwaNNE2XG","say":"Hello"},"WzajCLEJmJHmzg0":{"pin":"vMU1mKbZAi","say":"Hello"}}},"2018":{"Jan-2018":{"Wf8E1unVaOh03a43":{"pin":"qXJCQREATD","say":"Hello"},"JZqP8fVCLSja6J82v":{"pin":"o5D8S8Lvtb","say":"Hello"}},"Feb-2018":{"lMMAKNLy8jtnnXAN":{"pin":"9zDuHcw6qH","say":"Hello"},"e9EV3HDKCceM":{"pin":"kPllwcoaob","say":"Hello"}}}}};
const flattenObjKey = (obj, key) =>
[].concat(...Object.entries(obj)
.map(([k, v]) => typeof v === 'object' ?
flattenObjKey(v, key) : (k === key ? v : [])
));
const result = flattenObjKey(data, 'pin');
console.log(result);
It looks like your data is consistently structured, so this is fairly simple.
const pins = [];
for (let year in pins) {
for (let month in year) {
for (let key in months) {
pins.push(key.pin);
}
}
}
If it's not consistently structured, you'll need to extract a recursive solution that looks for pin keys.
Related
I have the following object structure
{ "Apr-18" : { ... },
"Jan-18" : { ... },
"Feb-18" : { ... },
...
}
I am trying to sort the month (MMM-YY) keys so that it shows as follows
{ "Jan-18" : { ... },
"Feb-18" : { ... },
"Apr-18" : { ... },
...
}
My code for this is below. I am using moment.js to convert the date into its epoch for the sort comparison. I have roughly followed the solution shown here Sort JavaScript object by key However it's not working.
The console.log returns the object as it was, no sorting has occurred. What am I missing?
const object = {
"Apr-18" : { "a":"b" },
"Jan-18" : { "c":"d" },
"Feb-18" : { "e":"f" }
}
const sortObjectMonths = (obj) => Object.fromEntries(Object.entries(obj).sort( (a, b) =>
Date.parse(moment(a, "MMM-YY") - Date.parse(moment(b, "MMM-YY")))
));
let sorted = sortObjectMonths(object)
console.log(sorted)
You can use Object.entries() to get the object property keys and values, then use Array.sort() to sort them using moment. We can simply subtract the moment values to sort them.
The Array.sort() accepts two arguments, firstEl, secondEl, in this case that will be [key1, value1], [key2, value2]. We can use destructuring to write these as ([a,],[b,]), where a and b are the object keys (e.g. 'Apr-18').
Then we'll use Object.fromEntries() to get our sorted object.
const object = {
"Apr-18" : { "a":"b" },
"Jan-18" : { "c":"d" },
"Feb-18" : { "e":"f" },
}
console.log('Original object:', object)
const sortedObject = Object.fromEntries(
Object.entries(object).sort(([a,],[b,]) => {
return moment(a, "MMM-YY") - moment(b, "MMM-YY");
})
)
console.log('Sorted object:', sortedObject)
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js" referrerpolicy="no-referrer"></script>
Without moment
const months = ["Jan","Feb","Mar","Apr"]
const object = {
"Apr-18" : { "a":"b" },
"Jan-18" : { "c":"d" },
"Feb-18" : { "e":"f" },
}
const sortedObject = Object.fromEntries(
Object.entries(object)
.sort(([a,],[b,]) => months.indexOf(a.split("-")[0]) - months.indexOf(b.split("-")[0]))
)
console.log('Sorted object:', sortedObject)
Your code is almost okay but in .sort() the element a and b both are arrays of key and value. Key is at index 0 and value at index 1. Date.parse() won't work and converting the value by using new Date() is suggested. So, your code should be -
const moment = require("moment");
const sort = {
clientname: {
"Feb-18": { test: "c" },
"Jan-18": { test: "a" },
"Apr-18": { test: "v" },
},
};
const sortObjectMonths = (obj) => {
return Object.fromEntries(
Object.entries(obj).sort(
(a, b) => moment(new Date(a[0])) - moment(new Date(b[0]))
)
);
};
let sorted = sortObjectMonths(sort.clientname);
console.log(sorted);
I have a JSON object which is
let data = {
"key1" : 1,
"key2" : 2,
"subKeys":{
"subKey1" : 3,
"subKey2" : 4
}
}
I want my resultant JSON like this structure:
let resultantData = {
"key1":1,
"key2":2,
"subKey1":3,
"subKey2":4
}
How can I achieve this goal
Assuming you're using a fairly recent version of a JavaScript runtime due to the 'let' statement.
There are a couple of ways of doing this but the most direct is to merge the two hashes and then delete the key 'subKeys'.
I found this in another SO article. Please upvote if it helps.
let data = {
"key1" : 1,
"key2" : 2,
"subKeys":{
"subKey1" : 3,
"subKey2" : 4
}
};
let merged = { ...data, ...data['subKeys']};
delete merged['subKeys']
console.log(merged);
This should do it for you:
Object.entries(data).forEach(([key, val]) => {
if (typeof val === 'object' && !Array.isArray(val)) {
// if the item is an object and not an array, copy over the values.
Object.entries(val).forEach(([subKey, subVal]) => {
data[subKey] = data[subVal];
});
// delete original child object.
delete data[key];
}
})
To make this method more dynamic, you can use a recursive approach to flatten deeply nested objects with multiple levels. You can do this by taking the object's entries, and mapping them until no [key, value] pair array has a value which is an object by recursively mapping the object value's entires. To show this, I've added an additional nested object within your subKeys object called subsubKeys:
const data = { "key1" : 1, "key2" : 2, "subKeys":{ "subKey1" : 3, "subKey2" : 4, "subsubKeys": {"subsubKey1": 100}, "subKey3": 5 } };
const getEntries = obj => Object.entries(obj).flatMap(
([key, val]) => Object(val) === val
? getEntries(val)
: [[key, val]]
);
const flattenObj = obj => Object.fromEntries(getEntries(obj));
console.log(flattenObj(data));
I figured this must be a dup, but I can't find it on SO. Given an object like this:
let obj = { keyA: { keyB: 'hi', keyC: { keyD: null } }, keyE: 'hi' }
Is there a way I can find key paths to a given value, like this:
keyPaths(obj, 'hi') // -> [ 'keyA.keyB', 'keyE' ]
keyPaths(obj) // -> [ 'keyA.keyB.keyD' ]
I tried to adapt some of the answers that find deep values knowing the key, and I was almost able to adapt this one that finds deep nulls, but I can't figure out how to get the path back, instead of just the deepest key.
I would go with a depth first search like this :
let obj = { keyA: { keyB: 'hi', keyC: { keyD: null } }, keyE: 'hi' }
function keyPaths(parent, value = null, chain) {
let allResults = [];
for (const prop in parent) {
if (parent.hasOwnProperty(prop)) {
const element = parent[prop];
const newChain = chain ? chain + '.' + prop : prop;
if (element === value) {
allResults.push(newChain);
}
else if (Object.keys(prop).length > 1) {
allResults = [...allResults, ...keyPaths(element, value, newChain)];
}
}
}
return allResults;
}
console.log(keyPaths(obj, 'hi')) // -> [ 'keyA.keyB', 'keyE' ]
console.log(keyPaths(obj)) // -> [ 'keyA.keyB.keyC' ]
Basically, I check all the properties of the given element for a matching value. If a property doesn't match the value, but has child properties, I recursively call the function, and merge the results from the call iteration and the recursive call.
You do this pretty cleanly by using reduce inside a recursive function. The function will return an array, which you can than map() to whatever string values you want.
let obj = { keyA: { keyB: 'hi', keyC: { keyD: null } }, keyE: 'hi' }
function keyPaths(obj, val, path = [] ){
if (!obj) return
return Object.entries(obj).reduce((res, [k, v]) => {
let p = [...path, k]
if (v == val) res.push(p)
else if (v && typeof v == 'object') res.push(...keyPaths(v, val, p))
return res
}, [])
}
console.log(keyPaths(obj, 'hi').map(a => a.join('.')))
console.log(keyPaths(obj).map(a => a.join('|')))
If it's ok to use Lodash+Deepdash, then:
let paths = _(obj).filterDeep((v)=>v=='hi').paths().value();
Codepen is here
I have stuck at a point where I want to recursively traverse all the objects in an array and get the keys of those objects in a array data structure. I know how to loop over and get the keys of the object. But the problem here is I need that recursively for flexible objects. By flexible I mean it can have any level of nested properties.
So, what I have is an array like this:
let record = [{
"province": "string",
"city": "string",
"type": "alternative_address",
"address_line1": "string",
"post_code": "5858"
},
{
"province": "string",
"city": "string",
"type": "alternative_address",
"post_code": "5858",
"embedeer": {
"veryEmbedded": {
"veryveryEmbeded": 'yes'
}
}
}
];
And with some computation I am expecting an output like:
['province','city','type','address_line1','post_code','embedeer', 'embedeer.veryEmbedded', 'embedeer.veryEmbedded.veryveryEmbeded'];
For the effort I tried on this, I used the reduce() operation on array but I am unable to get that.
You need to write a recursive function that takes 2 inputs
object
prefix (undefined for first level keys)
let record = [{"province":"string","city":"string","type":"alternative_address","address_line1":"string","post_code":"5858"},{"province":"string","city":"string","type":"alternative_address","post_code":"5858","embedeer":{"veryEmbedded":{"veryveryEmbeded":"yes"}}}];
function addKeysToSet(o, p) {
Object.keys(o).forEach(k => {
let key = p ? p + "." + k : k; // Create the key hierarchy
keys.add(key); // Add key to the set
// If the value is an object, call the function recursively
if(typeof o[k] === 'object') {
addKeysToSet(o[k], key);
}
});
}
let keys = new Set(); // Create set of unique keys
// For each object in array, call function that adds keys to the set
record.forEach(o => addKeysToSet(o));
let result = Array.from(keys); // Create array from set
console.log(result); // logs result
You could take an iterative and recursive approach and take the power of a Set for getting unique values.
function iter(object, keys) {
return Object
.entries(object)
.reduce((r, [k, v]) => r.concat(keys.concat(k).join('.'), v && typeof v === 'object'
? iter(v, keys.concat(k))
: []
), []);
}
var record = [{ province: "string", city: "string", type: "alternative_address", address_line1: "string", post_code: "5858" }, { province: "string", city: "string", type: "alternative_address", post_code: "5858", embedeer: { veryEmbedded: { veryveryEmbeded: 'yes' } } }],
keys = [...record.reduce((s, o) => iter(o, []).reduce((t, v) => t.add(v), s), new Set)];
console.log(keys);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can Flatten the object and then get the keys.
// form https://gist.github.com/penguinboy/762197
let record = [{"province":"string","city":"string","type":"alternative_address","address_line1":"string","post_code":"5858"},{"province":"string","city":"string","type":"alternative_address","post_code":"5858","embedeer":{"veryEmbedded":{"veryveryEmbeded":"yes"}}}];
var flattenObject = function(a) {
var b = {};
for (var c in a)
if (a.hasOwnProperty(c))
if ("object" == typeof a[c]) {
var d = flattenObject(a[c]);
for (var e in d) d.hasOwnProperty(e) && (b[c + "." + e] = d[e]);
} else b[c] = a[c];
return b;
};
console.log(flattenObject(record) )
/*
It is also taking care of index numbers of the array. ("0.province" instead of "province" If multiple entries are passed)
*/
console.info( "All keys", Object.keys(flattenObject(record) ) )
// Simple
console.info( "keys", Object.keys(flattenObject(record[1]) ) )
var record1 = [{"province": "string","city": "string","type": "alternative_address","address_line1": "string","post_code": "5858" },
{ "province": "string","city": "string",
"type": "alternative_address",
"post_code": "5858",
"embedeer": {
"veryEmbedded": {
"veryveryEmbeded": 'yes'
}
}
}
];
var output = [];
function getAllKeys(obj,precedor="") {
var temp = Object.entries(obj);
temp.forEach((el) =>
typeof el[1] == "object" ? ( output.push(el[0]),getAllKeys(el[1],precedor==""? el[0]: precedor+"."+el[0])): output.push(precedor==""? el[0]: precedor+"."+el[0]));
}
record1.forEach((el,i) => getAllKeys(el,""));
//To avoid duplicate entries convert array to object.
console.log(...(new Set(output)));
I have an object as below:
obj = {
'fruita' : 'eat',
'fruitb' : 'eat',
'fruitc' : 'throw',
'fruitd' : 'throw'
}
output = {
'eat' : ['fruita','fruitb'],
'throw' : ['fruitc','fruitd']
}
How to apply _.groupBy in order to get the list of eat and throw fruits seperately?
We can turn our object to an array of key-value pairs:
var keyValues = Object.keys(obj).map(key => ({ key, value: obj[key] }));
And then we can perform a reduce to construct our object:
var values = keyValues.reduce((acc, kv) => {
if (typeof acc[kv.value] === 'undefined') {
acc[kv.value] = [];
}
acc[kv.value].push(kv.key);
return acc;
}, {});
No lodash necessary!
You could use a for..in loop to set properties of output object to values of obj , push obj properties to items within array at output
var obj = {
'fruita' : 'eat',
'fruitb' : 'eat',
'fruitc' : 'throw',
'fruitd' : 'throw'
}
var output = {};
for (var prop in obj) {
if (!output[obj[prop]]) {
output[obj[prop]] = [];
output[obj[prop]].push(prop)
} else {
output[obj[prop]].push(prop)
}
}
console.log(output)