I have JSON with following structure:
{
"name":"Scorpiones",
"children":[
{
"name":"Parabuthus",
"children":[
{
"name":"Parabuthus schlechteri"
},
{
"name":"Parabuthus granulatus"
}
]
},
{
"name":"Buthidae",
"children":[
{
"name":"Androctonus",
"children":[
{
"name":"Androctonus crassicauda"
},
{
"name":"Androctonus bicolor"
}
]
}
]
}
]
}
It is required to sort the elements by the name field at each level of the hierarchy. How can this be done with the help of JS?
You can use Object.keys which returns an array contains all object keys, then you can simply sort that array.
const getSortedKeys = (obj) => {
return Object.keys(obj).sort();
}
And to go throw all your object nested keys you can use recursion:
const trav = (obj) => {
if(obj instanceof Array) {
// go inside each object in the array
obj.forEach(item => trav(item))
} else if(typeof obj === "object") {
// sort object keys
let keys = getSortedKeys(obj);
// do whatevery you want...
// go the the next levels
keys.forEach(key => trav(obj[key]))
}
}
Related
I'm dealing with the following JavaScript object:
{
"gender": "man",
"jobinfo": {
"type": "teacher"
},
"children": [
{
"name": "Daniel",
"age": 12,
"pets": [
{
"type": "cat",
"name": "Willy",
"age": 2
},
{
"type": "dog",
"name": "Jimmie",
"age": 5
}
]
}
]
}
I want to print out each of the paths (keys and array indices) within the object, including the parents (i.e. children should be printed as should everything in it).
gender
jobinfo,
jobinfo.type,
children,
children.0.name,
children.0.age,
children.0.pets,
children.0.pets.0.type,
children.0.pets.0.name,
children.0.pets.0.age,
children.0.pets.1.type,
children.0.pets.1.name,
children.0.pets.1.age
I tried this code with modifications but it didnt work for me:
function getPath(object) {
for (key in object) {
if (Array.isArray(object[key]) === true) {
console.log(key)
getPath(object[key])
} else if (typeof object[key] === 'object') {
console.log(key)
getPath(object[key])
} else {
console.log(key)
}
}
}
It's printing all keys in the JSON, but I'm struggling with joining the paths, especially in nested elements.
This works:
const data = {"gender":"man","jobinfo":{"type":"teacher"},"children":[{"name":"Daniel","age":12,"pets":[{"type":"cat","name":"Willy","age":2},{"type":"dog","name":"Jimmie","age":5}]}]};
const getPath = (currPath, item) => {
console.log(currPath);
if (Array.isArray(item)) {
item.forEach((el, idx) => getPath(`${currPath}.${idx}`, el));
} else if (typeof item == "object") {
Object.entries(item).forEach(([key, value]) => {
getPath(`${currPath}.${key}`, value);
});
}
};
Object.entries(data).forEach(([key, value]) => {
getPath(key, value);
});
Basically I just loop through each of the entries in the initial object, using the key as the path at that stage and checking if the value is an object or array. I always print the path within the function (to provide the outer layers you want) and then I recurse over the inner layers, adding to the path as needed.
In this version array keys that are consisted of numbers like 'children.0' and so on handled and this gives the result exactly what you wanted:
const json = {"gender":"man","jobinfo":{"type":"teacher"},"children":[{"name":"Daniel","age":12,"pets":[{"type":"cat","name":"Willy","age":2},{"type":"dog","name":"Jimmie","age":5}]}]};
function getPath(object, previousPath) {
for (key in object) {
let currentPath = previousPath ? `${previousPath}.${key}` : key
if (Array.isArray(object[key])) {
console.log(currentPath)
getPath(object[key], currentPath)
} else if (typeof object[key] === 'object') {
if (!Array.isArray(object)) { // skipping logging array keys like children.0
console.log(currentPath)
}
getPath(object[key], currentPath)
} else {
console.log(currentPath)
}
}
}
getPath(json)
I've created an update object API that receives new update data of an existing document.
Let's say, I have two objects oldData and newData
oldData = {
me:{
name:{
short:'will'
long:'william'
}
},
friends:[
{
id: 1,
name:{
short:'mike'
long:'michael'
},games:[]
},
{
id: 2,
name:{
short:'fred'
long:'freddy'
}
},
],
favoriteGames:[
'gta',
'animal crossing',
'mortal kombat'
],
favoriteFood:['bacon'],
}
newData = {
me:{
name:{
long:'willy'
longer:'william'
}
},
friends:[
{
id:3,
name:{
short:'max',
long:'maxwell'
}
},
{
id:1,
name:{
short:'mic',
}
},
],
favoriteGames:[
'tekken'
]
}
calling applyUpdate(oldData, newData)should return
result = {
me:{
name:{
short:'will',
long:'willy',
longer:'william'
}
},
friends:[
{
id:3,
name:{
short:'max',
long:'maxwell'
}
},
{
id: 1,
name:{
short:'mic'
long:'michael'
},games:[]
}
],
favoriteGames:[
'tekken'
],
favoriteFood:['bacon'],
}
Basically, the rules for merging are:
If a key in an object is specified with new data, it overrides the
value of the same key in old data.
If a key is not specified, the
value of the same key in old data is kept.
If the value of a key in new data is an array of objects:
Each object must be merged BY id with elements in the array of the same key in old data.
Elements not included in the arrays of newData are removed from the result.
The order of elements in the arrays of newData should be preserved.
Merging must be done deeply, since nested arrays and objects of unspecified depth should be possible.
I've actually successfully implemented this with a horrendously long and ugly recursive function. But am worried about performance and readability issues. I am open to suggestions using lodash or underscore.
Thanks!
Try this. It's hard to write this in a readable way.
function customizer(oldProp, newProp) {
if (Array.isArray(newProp)) {
// check if `newProp` is an array of objects which has property `id`
if (typeof newProp[0] === 'object' && newProp[0].hasOwnProperty('id')) {
if (!Array.isArray(oldProp)) {
return newProp;
}
// merge objects of 2 arrays in `oldProp` and `newProp`
const mergedArr = [];
for (const objNewArr of newProp) {
const objOldArr = oldProp.find(o => o.id === objNewArr.id);
if (objOldArr) {
mergedArr.push(_.merge(objOldArr, objNewArr));
} else {
mergedArr.push(objNewArr);
}
}
return mergedArr;
}
return newProp;
}
if (typeof newProp === 'object') {
return _.merge(oldProp, newProp);
}
return newProp;
}
_.mergeWith(oldData, newData, customizer); // returns the merged object
Here's what worked. Thanks Duc.
function customizer(oldProp, newProp) {
if (Array.isArray(newProp)) {
if (typeof newProp[0] === 'object') {
const mergedArr = [];
for (const objNewArr of newProp) {
const objOldArr = oldProp.find(o => o._id === objNewArr._id);
if (objOldArr) {
mergedArr.push(_.mergeWith(_.cloneDeep(objOldArr), _.cloneDeep(objNewArr), customizer));
} else {
mergedArr.push(objNewArr);
}
}
return mergedArr;
}else{
return newProp;
}
}else if (typeof newProp === 'object') {
return _.merge(oldProp, newProp);
}else{
return undefined;
}
}
var result = _.mergeWith(_.cloneDeep(oldData), _.cloneDeep(newData), customizer); // returns the merged object
I have an array returnedDocs in code below (shortened via let returnedDocs = result.application.modules.module.moduleItem;) that I got as response from API, it structure is pretty complex. Some nested objects are arrays some values are placed few levels deep in the structure.
I use filter method to get only those elements that got specific value.
let toFilterSix = returnedDocs.filter(
o =>
o.repeatableGroup.repeatableGroupItem.vocabularyReference
.vocabularyReferenceItem.formattedValue._text === "Abdomen"
);
this.filterArray6 = toFilterSix;
Normally it works fine but in below example one of repeatableGroupItem is an array with two elements (image).
I've got an error due to fact that there is unexpected array within filtered objects:
Results.vue?82a0:202 Uncaught (in promise) TypeError: Cannot read property 'vocabularyReferenceItem' of undefined
at eval (Results.vue?82a0:202)
at Array.filter (<anonymous>)
at eval (Results.vue?82a0:201)
How can I avoid the error when do filter on other, non-array elements?
Here you can examine the data model, it's console.log'ed: https://lucid-villani-539a6f.netlify.com/results
If you just want to ignore the arrays, you can simply test whether repeatableGroup has a vocabularyReferences property.
let toFilterSix = returnedDocs.filter(
o =>
o.repeatableGroup.repeatableGroupItem.vocabularyReference &&
o.repeatableGroup.repeatableGroupItem.vocabularyReference.vocabularyReferenceItem.formattedValue._text === "Abdomen"
);
If you want to search the array as well, you can use an if statement to search that when it's an array.
let toFilterSix = returnedDocs.filter(
o => {
if (Array.isArray(o.repeatableGroup.repeatableGroupItem)) {
return o.repeatableGroup.repeatableGroupItem.some(el => el.vocabularyReference.vocabularyReferenceItem.formattedValue._text === "Abdomen");
} else {
return o.repeatableGroup.repeatableGroupItem.vocabularyReference.vocabularyReferenceItem.formattedValue._text === "Abdomen";
}
});
If you're trying to look for "Abdomen" regardless of whether it's a single object or an array of those objects, you could use [].concat(repeatableGroupItem). This allows you to safely assume you're dealing with an array instead of handling each case individually.
From there you could use .some() to determine if "Abdomen" exists within any of the items.
// This array contains 4 objects...
// 1: repeatableGroupItem as single object, containing "Abdomen"
// 2: repeatableGroupItem as array of objects, containing "Abdomen"
// 3: repeatableGroupItem as single object, without "Abdomen"
// 4: repeatableGroupItem as array of objects, without "Abdomen"
const returnedDocs = [{ repeatableGroup: { repeatableGroupItem: { vocabularyReference: { vocabularyReferenceItem: { formattedValue: { _text: "Abdomen" } } } } } }, { repeatableGroup: { repeatableGroupItem: [{ vocabularyReference: { vocabularyReferenceItem: { formattedValue: { _text: "Abdomen" } } } }, { vocabularyReference: { vocabularyReferenceItem: { formattedValue: { _text: "Abdomen" } } } } ] } }, { repeatableGroup: { repeatableGroupItem: { vocabularyReference: { vocabularyReferenceItem: { formattedValue: { _text: "Not Abdomen" } } } } } }, { repeatableGroup: { repeatableGroupItem: [{ vocabularyReference: { vocabularyReferenceItem: { formattedValue: { _text: "Not Abdomen" } } } }, { vocabularyReference: { vocabularyReferenceItem: { formattedValue: { _text: "Not Abdomen" } } } } ] } }];
const result = returnedDocs.filter(o => {
const item = [].concat(o.repeatableGroup.repeatableGroupItem);
return item.some(i => i.vocabularyReference.vocabularyReferenceItem.formattedValue._text === "Abdomen");
});
//Logs items 1 and 2
console.log(result);
I don't really know how to express what I want, but I'll try.
So, I have an object with an array inside with the name of recipes, that I receive from my API, and a valuePath which is an object:
Object
{
recipes: [
{
test: {
items: [
{
type: 'test1',
}
]
}
}
]
}
ValuePath
{
"allRecipes": {
"array": "recipes",
"values": {
"allTypes": {
"array": "test",
"values": {
"type": "type"
}
}
}
}
}
Briefly what I have to do, is iterate over the array recipes through out the valuePath, dynamically, because the array and the values can change. I don't really know how to explain it better and how to iterate thought deeply nested objects/array's having a valuePath as a reference to find the values.
What I've tried so far...
export const test = (object, valuePath) => {
for (const prop in valuePath) {
object = object[valuePath[prop].array]; // find the array
if (Array.isArray(object)) {
object.forEach(objRef => {
console.log('valueRef', objRef);
});
}
console.log('props->', valuePath[prop].values); // find the values
}
};
I think i need a recursion, but have no clue how to do one.
If I understood your problem, this could be an implementation...
If you run it with your data and path, it will return test1.
// INPUTS
const data = {
recipes: [
{
test: {
items: [
{
type: 'test1',
}
]
}
}
]
}
const path = {
"allRecipes": {
"array": "recipes",
"values": {
"allTypes": {
"array": "test",
"values": {
"type": "type"
}
}
}
}
}
// this is just an helper method for arrays...
Array.prototype.first = function () { return this[0] }
// this is an helper function that tells us whether
// a path object is still traversable.
// from what I understood, if it contains an `array` property
// we should follow it...
const isTraversable = path => !!path.array
// this is the actual implementation of the algorithm
const traverse = (data, path) => {
const nextPath = Object.values(path).first()
if ( isTraversable(nextPath) ) {
const array = data[nextPath.array]
// I noticed that at a certain point in the data object,
// we need to traverse an array, and in another it is an
// object with an `items` property.
// this next lines helps determine how go down
const nextData = Array.isArray(array) ? array.first() : array.items
// we recurse on the traversed data and path
return traverse(nextData, nextPath.values)
}
return data.first()[path.type]
}
console.log(traverse(data, path))
Please try this, I hope it will help you..
let obj = {
recipes: [
{
test: {
items: [
{
type: 'test1',
},
],
},
},
],
};
obj.recipes.forEach(test => {
test.test.items.forEach(items => {
console.log(items.type);
});
});
I am trying to write a search function that can search through an array of arrays of objects and return any objects that include the search string in the name.
The issue I am having is that the object array can contain an array of children and the children can also have an array of children. I need to dynamically search through all the possible children and return the results
I have tried to do this with Algolia but because the file structure won't be constantly updated I feel this would be better using Array.includes or something similar
I have tried the following function but can't seem to get it working
searchArray(subMenuItems, name) {
if (subMenuItems) {
for (let i = 0; i < subMenuItems.length; i++) {
if (subMenuItems.includes(name)) {
return subMenuItems[i];
}
const found = this.getSubItem(subMenuItems[i].children, name);
if (found) {
return found;
}
}
}
}
Here is an example of the Object Array
[
[
{
"children":[
{
"children":[
{
"fileSize":"1.2MB",
"fileUrl":"https://linktoPDF.com",
"name":"GF Kitchen ",
"type":"file"
}
],
"name":"Ground Floor Kitchen",
"type":"folder"
}
],
"name":"House",
"type":"folder"
}
],
[
{
"fileSize":"1.3MB",
"fileUrl":"https://linktoPDF.com",
"name":"Introduction and Overview",
"type":"file"
},
{
"fileSize":"20MB",
"fileUrl":"https://linktoPDF.com",
"name":"VISUAL iPad Location Drawing",
"type":"file"
},
{
"fileSize":"1MB",
"fileUrl":"https://linktoPDF.com",
"name":"Control Surface",
"type":"file"
},
{
"fileSize":"1.3MB",
"fileUrl":"https://linktoPDF.com",
"name":"Scene",
"type":"file"
}
]
]
A simple recursive function will allow you to gather the objects (or any property from the object) from each of the nested (no matter how deeply) objects.
You should consider whether case sensitivity is something that is important too.
Otherwise, this will work:
Search data for any name with 'ce'
then search for any name with 'tion'
then search for any name with 'Floor'
const data = [[{"children":[{"children":[{"fileSize":"1.2MB","fileUrl":"https://linktoPDF.com","name":"GF Kitchen","type":"file"}],"name":"Ground Floor Kitchen","type":"folder"}],"name":"House","type":"folder"}],[{"fileSize":"1.3MB","fileUrl":"https://linktoPDF.com","name":"Introduction and Overview","type":"file"},{"fileSize":"20MB","fileUrl":"https://linktoPDF.com","name":"VISUAL iPad Location Drawing","type":"file"},{"fileSize":"1MB","fileUrl":"https://linktoPDF.com","name":"Control Surface","type":"file"},{"fileSize":"1.3MB","fileUrl":"https://linktoPDF.com","name":"Scene","type":"file"}]];
let output = [];
function search(arr, str) {
arr.forEach(a => {
if (a.constructor == Array) {
search(a, str);
} else if (a.children) {
if (a.name.includes(str)) output.push(a);
search(a.children, str);
} else {
if (a.name.includes(str)) output.push(a);
}
});
}
search(data, 'ce');
console.log(output);
output = [];
search(data, 'tion');
console.log(output);
output = [];
search(data, 'Floor');
console.log(output);
You can try a recursive function as below to check the array and the children array to get the result.
function searchByName(obj, name) {
if (Array.isArray(obj)) {
return obj.reduce((acc, item) => acc.concat(searchByName(item, name)), []);
}
if (typeof obj === 'object') {
const matched = [];
const { children, ...rest } = obj;
if (obj.name && obj.name.includes(name)) {
matched.push(rest);
}
return Array.isArray(children) ?
matched.concat(searchByName(children, name)) : matched;
}
return;
}
const arr = [[{"children":[{"children":[{"fileSize":"1.2MB","fileUrl":"https://linktoPDF.com","name":"GF Kitchen","type":"file"}],"name":"Ground Floor Kitchen","type":"folder"}],"name":"House","type":"folder"}],[{"fileSize":"1.3MB","fileUrl":"https://linktoPDF.com","name":"Introduction and Overview","type":"file"},{"fileSize":"20MB","fileUrl":"https://linktoPDF.com","name":"VISUAL iPad Location Drawing","type":"file"},{"fileSize":"1MB","fileUrl":"https://linktoPDF.com","name":"Control Surface","type":"file"},{"fileSize":"1.3MB","fileUrl":"https://linktoPDF.com","name":"Scene","type":"file"}]];
console.log(searchByName(arr, 'not found'));
console.log(searchByName(arr, 'Drawing'));
console.log(searchByName(arr, 'S'));