Recursively fitting JavaScript object to a specific structure - javascript

I have this problem where I cant really figure out how to implement this in code. I think that I will have to use to power of recursion to do this, but I am really struggling with it.
I have this object (simplified for this question), where the nested depths can be unknown.
const allValues = {
features: {
title: 'features',
description: 'some features',
fields: {
featureA: false,
featureB: true,
featureC: true
}
},
otherthings: {
title: 'otherthings',
description: 'otherthingsdescription',
fields: {
nestedotherthings: {
title: 'nestedotherthings',
description: 'nestedotherthingsdescription',
fields: {
againnested: {
title: 'againsnested',
description: 'againsnested',
fields: {
finallynestedvalue: 1
}
},
againnested2: {
title: 'againsnested2',
description: 'againsnested2',
fields: {
finallynestedvalue: 200
}
}
}
}
}
}
}
I want to run this object through some code and get an output like this:
const expected_output = {
features: {
featureA: false,
featureB: true,
featureC: true
},
othertings: {
nestedotherthings: {
againnested: {
finallynestedvalue: 1
},
againnested2: {
finallynestedvalue: 2
}
}
}
}
What I have tried:
const output = _(allValues).mapValues((value, key) => flattenFields({}, value, key)).value()
function flattenFields(parent, current, key) {
if (!current || !current.fields) {
return current
}
return {
...parent,
[key]: _(current.fields).mapValues((value, key) => flattenFields(current, value, key)).value()
}
}
I hope someone can help me with this and explain what I am doing wrong.

You could take a recursive approach by checking the values and if an object take the fields property or the value. Then rebuild a new object.
function convert(object) {
return Object.fromEntries(Object
.entries(object)
.map(([k, v]) => [k, v && typeof v === 'object' ? convert(v.fields) : v])
);
}
var data = { features: { title: 'features', description: 'some features', fields: { featureA: false, featureB: true, featureC: true } }, otherthings: { title: 'otherthings', description: 'otherthingsdescription', fields: { nestedotherthings: { title: 'nestedotherthings', description: 'nestedotherthingsdescription', fields: { againnested: { title: 'againsnested', description: 'againsnested', fields: { finallynestedvalue: 1 } }, againnested2: { title: 'againsnested2', description: 'againsnested2', fields: { finallynestedvalue: 200 } } } } } } },
result = convert(data);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

JavaScript: Insert property into an object during loop

I'm looping an array of objects taken from MongoDB and attempting to insert a property into one of them, without success.
The array of objects would be:
[
{
_id: [String],
customerInformation: [ [Object] ],
purchasedBanners: [ [Object] ],
statusOfPurchase: 'new',
createdAt: 2021-02-24T15:04:42.074Z,
updatedAt: 2021-02-24T15:04:42.074Z,
__v: 0
}
...
]
I've tried:
return PurchasesModel.schemaForPurchases.find({
statusOfPurchase: args.statusOfPurchase
})
.limit(10)
.then(purchases => {
purchases.forEach(purchase => {
NotesModel.schemaForNotes.countDocuments({ purchaseId: purchase._id })
.then(numberOfNotes => {
Object.defineProperty(purchase, 'numberOfNotes', {
value: numberOfNotes
})
})
})
return purchases
})
But then I found that the forEach method is synchronous, so I tried:
return PurchasesModel.schemaForPurchases.find({
statusOfPurchase: args.statusOfPurchase
})
.limit(10)
.then(purchases => {
for (let i = 0; i < purchases.length; i++) {
let numberOfNotes = 0
numberOfNotes = NotesModel.schemaForNotes.countDocuments({ purchaseId: purchases[i]._id })
.then(numberOfNotes => {
return numberOfNotes
})
Object.defineProperty(purchases[i], 'numberOfNotes', {
value: numberOfNotes.then(numberOfNotes => {
return numberOfNotes
})
})
}
return purchases
})
In each case (including several other approaches), the objects aren't appended.
I'm new to MongoDB, so I assume I'm either doing something wrong, or perhaps the objects are somehow protected?
Thoughts welcome.
In the end, there wasn't a shortcut! Or at least I'm not aware of it.
const GET_ALL_PURCHASES_QUERY = (statusOfPurchase) => {
return gql`
query {
getAllPurchases(statusOfPurchase: "${statusOfPurchase}") {
id
customerInformation {
customerName
customerEmailAddress
}
purchasedBanners {
nameOfBanner
costOfBanner
numberOfBannersToPrint
nameOfChosenComponent
targetPDF
previewImage
dataToExport
}
numberOfNotes {
count
}
createdAt
updatedAt
}
}
... and then:
const NotesCountForPurchaseType = new GraphQLObjectType({
name: 'NotesCountForPurchase',
fields: () => ({
count: {
type: GraphQLInt
}
})
})
const PurchaseType = new GraphQLObjectType({
name: 'Purchase',
fields: () => ({
id: {
type: GraphQLID
},
customerInformation: {
type: GraphQLList(PurchaseCustomerInformationType)
},
purchasedBanners: {
type: GraphQLList(PurchaseBannerType)
},
statusOfPurchase: {
type: GraphQLString
},
createdAt: {
type: GraphQLDateTime
},
updatedAt: {
type: GraphQLDateTime
},
numberOfNotes: {
type: NotesCountForPurchaseType,
resolve(parent, args) {
return NotesModel.schemaForNotes.countDocuments({
purchaseId: parent.id
})
.then(numberOfNotes => {
console.log('Schema:numberOfNotes()', numberOfNotes)
return { count: numberOfNotes }
})
}
}
})
})
Extra work, but working.

Javascript - transforming an object of array list to new formated one?

I'm trying to transform an object contain array to another one with javascript. Below is an example of the object field and what the formatted one should look like.
let Fields = {
GAME: [
{ code: '{{PES}}', title: { en: "playPES"} },
{ code: '{{FIFA}}', title: { en: "playFIFA " } },
]
};
I need The new Fields to looks like this
let newFields = {
name: 'GAME',
tags:[
{ name: 'playPES', value: "{{PES}}" },
{ name: 'playFIFA', value: "{{FIFA}}" }
]},
One contributor suggested me a method like this but i think something need to modify in it but couldn't figure it out.
export const transform = (fields) => ({
tags: Object .entries (fields) .map (([name, innerFields]) => ({
name,
tags: innerFields.map(({code, title: title: {en})=>({name: en, value: code}))
}))
});
// newFields= transform(Fields)
I'm new working with javascript so any help is greatly appreciated, Thanks.
const transform = (o) => {
return Object.entries(o).map((e)=>({
name: e[0],
tags: e[1].map((k)=>({name: (k.title)?k.title.en:undefined, value: k.code}))
}))[0]
}
console.log(transform({
GAME: [
{ code: '{{PES}}', title: { en: "playPES"} },
{ code: '{{FIFA}}', title: { en: "playFIFA " } },
]
}))
Using the entries method you posted:
let Fields = {
GAME: [
{ code: '{{PES}}', title: { en: "playPES"} },
{ code: '{{FIFA}}', title: { en: "playFIFA " } },
]
};
// 1. Obtain keys and values from first object
Fields = Object.entries(oldFields);
// 2. Create new object
const newFields = {};
// 3. Create the name key value pair from new Fields array
newFields.name = Fields[0][0];
// 4. Create the tags key value pair by mapping the subarray in the new Fields array
newFields.tags = Fields[0][1].map(entry => ({ name: entry.title.en, value: entry.code }));
Object.entries(Fields) will return this:
[
"GAME",
[TagsArray]
]
And Object.entries(Fields).map will be mapping this values.
The first map, will receive only GAME, and not an array.
Change the code to something like this:
export const transform = (Fields) => {
const [name, tags] = Object.entries(Fields);
return {
name,
tags: tags.map(({ code, title }) => ({
name: title.en,
value: code
}))
}
}
Hope it help :)
let Fields = {
GAME: [
{ code: '{{PES}}', title: { en: "playPES"} },
{ code: '{{FIFA}}', title: { en: "playFIFA " } },
]
};
let newFields = {
name: 'GAME',
tags:[
{ name: 'playPES', value: "{{PES}}" },
{ name: 'playFIFA', value: "{{FIFA}}" }
]
}
let answer = {
name: "Game",
tags: [
]
}
Fields.GAME.map(i => {
var JSON = {
"name": i.title.en,
"value": i.code
}
answer.tags.push(JSON);
});
console.log(answer);
I think that this is more readable, but not easier... If you want the result as object you need to use reduce, because when you do this
Object.keys(Fields)
Your object transform to array, but reduce can change array to object back.
let Fields = {
GAME: [
{ code: '{{PES}}', title: { en: "playPES"} },
{ code: '{{FIFA}}', title: { en: "playFIFA " } },
]
};
const result = Object.keys(Fields).reduce((acc, rec) => {
return {
name: rec,
tags: Fields[rec].map(el => {
return {
name: el.title.en,
value: el.code
}
})
}
}, {})
console.log(result)
let Fields = {
GAME: [
{ code: '{{PES}}', title: { en: "playPES"} },
{ code: '{{FIFA}}', title: { en: "playFIFA " } },
]
};
const transform = (fields) => ({
tags: Object .entries (fields) .map (([name, innerFields]) => ({
name,
tags: innerFields.map(({code, title: title,en})=>({name: title.en, value: code}))
}))
});
//check required output in console
console.log(transform(Fields));

How to extract paths to 'enabled' objects in nested object arrays

I'm a novice to recursion and I have a JSON structure with arrays of nested objects. Some of these objects have a boolean enabled: true. I'm trying to figure out how to extract the paths to all enabled objects and their children.
I tried both cleaning up the original object by removing unused paths but I got lost in accessing the parents. I also tried building a separate array of paths using dot-notation, as I can probably build a new nested object from that. My latest attempt at the dot-notation extract:
const sourceData = {
title: "Work",
tags: [
{
title: "Cleaning",
tags: [
{
title: "Floors"
},
{ title: "Windows", enabled: true },
{ title: "Ceilings", enabled: true }
]
},
{
title: "Maintenance",
tags: [
{
title: "Walls",
enabled: true,
tags: [
{
title: "Brickwall"
},
{
title: "Wooden wall"
}
]
},
{
title: "Roof"
}
]
},
{
title: "Gardening"
}
]
};
function getEnabledPaths(level, acc) {
for (const tag of level.tags) {
if (tag.enabled) {
return tag.title;
} else if (tag.hasOwnProperty("tags")) {
var path = this.getEnabledPaths(tag);
if (path) acc.push(tag.title + "." + path);
}
}
return acc;
}
console.log(getEnabledPaths(sourceData, []));
I only get:
[
"Cleaning.Windows",
"Maintenance.Walls"
]
I would ideally end up with something like this:
[
'Work.Cleaning.Windows',
'Work.Cleaning.Ceilings',
'Work.Maintenance.Walls.Brickwall',
'Work.Maintenance.Walls.Wooden Wall'
]
In a perfect world (but I tried for days and went back to getting the dot notation results):
{
title: "Work",
tags: [
{
title: "Cleaning",
tags: [
{
title: "Windows",
enabled: true
},
{
title: "Ceilings",
enabled: true
}
]
},
{
title: "Maintenance",
tags: [
{
title: "Walls",
enabled: true,
tags: [
{
title: "Brickwall"
},
{
title: "Wooden wall"
}
]
}
]
}
]
};
The key to the recursion function is to both a) deal with children and b) the item itself.
Here's my take, which seems to work:
const sourceData = {title:"Work",tags:[{title:"Cleaning",tags:[{title:"Floors"},{title:"Windows",enabled:true},{title:"Ceilings",enabled:true}]},{title:"Maintenance",tags:[{title:"Walls",enabled:true,tags:[{title:"Brickwall"},{title:"Woodenwall"}]},{title:"Roof"}]},{title:"Gardening"}]};
function itemFilter(item) {
// enabled? done with this item
if (item.enabled) return item;
// not enabled and no tags? set to null
if (!item.tags) return null;
// filter all children, remove null children
item.tags = item.tags.map(child => itemFilter(child)).filter(child => child);
return item;
}
console.log(itemFilter(sourceData));
.as-console-wrapper {
max-height: 100vh !important;
}
You could pass enabled parameter down to lower levels of recursion if true value is found on some of the upper levels and based on that add path to the results or not.
const data ={"title":"Work","tags":[{"title":"Cleaning","tags":[{"title":"Floors"},{"title":"Windows","enabled":true},{"title":"Ceilings","enabled":true}]},{"title":"Maintenance","tags":[{"title":"Walls","enabled":true,"tags":[{"title":"Brickwall"},{"title":"Wooden wall"}]},{"title":"Roof"}]},{"title":"Gardening"}]}
function paths(data, prev = '', enabled = false) {
const result = [];
prev += (prev ? "." : '') + data.title;
if (!enabled && data.enabled) enabled = true;
if (!data.tags) {
if (enabled) {
result.push(prev);
}
} else {
data.tags.forEach(el => result.push(...paths(el, prev, enabled)))
}
return result;
}
const result = paths(data)
console.log(result)

Turn object with (possible nested) arrays into an array of objects with single-item arrays

I am trying to create a recursive function that can turn this array:
const originalObj = {
field: "parent",
msg:[{
field: "child1a",
msg: [{
field: "child2a",
msg: "child2a-msg"
},
{
field: "child2b",
msg: "child2b-msg"
}
]
}, {
field: "child1b",
msg: "child1b-msg"
}
]
};
Into this one:
[
{
field: "parent",
msg: [
{
field: "child1a",
msg: [
{
field: "child2a",
msg: "child2a-msg"
}
]
},
]
},
{
field: "parent",
msg: [
{
field: "child1a",
msg: [
{
field: "child2b",
msg: "child2b-msg"
}
]
},
]
},
{
field: "parent",
msg: [
{
field: "child1b",
msg: "child1b-msg"
}
]
}
]
So, to be clear: the msg object can be either a string or a single item array.
It should be recursive; since a msg-object can contain an array, which can contain a deeper msg-object, which can contain another array, etc.
Here is my attempt, but I can't figure it out.
https://jsfiddle.net/rnacken/42e7p8hz/31/
As you can see in the fiddle, the arrays are nested, parent is missing and I'm missing a child. I am afraid I am way lost and on the wrong track here.
You could iterate msg and build a new nested part result of the nested items. Then iterate and build single objects for the result array.
function getSingle({ field, msg }) {
var array = [];
if (!msg || !Array.isArray(msg)) {
return [{ field, msg }];
}
msg.forEach(o => getSingle(o).forEach(s => array.push({ field, msg: [s] })));
return array;
}
var object = { field: "parent", msg: [{ field: "child1a", msg: [{ field: "child2a", msg: [{ field: "child3a", msg: "child3a-msg" }, { field: "child3b", msg: "child3b-msg" }] }, { field: "child2b", msg: "child2b-msg" }] }, { field: "child1b", msg: "child1b-msg" }] };
console.log(getSingle(object));
.as-console-wrapper { max-height: 100% !important; top: 0; }
I have added a simple recursive approach that you can use.
const originalObj = {
field: "parent",
msg: [
{
field: "child1a",
msg: [
{
field: "child2a",
msg: "child2a-msg"
},
{
field: "child2b",
msg: "child2b-msg"
}
]
},
{
field: "child1b",
msg: "child1b-msg"
}
]
};
const flatten = ({field, msg}) => {
const res = []; // creating an array to create the msg array in case of multiple entries in msg
if (typeof msg === "string") {
return {field, msg}; // return plain object if the msg is "string"
}
if (msg.constructor === Array) {
// recursion here
msg.map(message => flatten(message, msg))
.forEach(m => {
// after flattening array msg, we push them to msg field
res.push({field, msg: m})
});
}
return res; // returning final result here
}
const newObj = flatten(originalObj);
With flatten function you can map any depth of array and expect the same result.

Flattened Array with Objects

I'm certain this question is very common, but I can't seem to find a robust answer for my use case.
I have an Array of objects with nesting in two levels. Here is an example of the array:
let array = [
{ company: 'CompanyName1',
child: [
{ title: 'title1a',
baby: [
{ title: 'title1ab' },
{ title: 'title1abc' }
]
},
{ title: 'title2a',
baby: [
{ title: 'titleb2abcd' },
{ title: 'titleb2abcde' }
]
}
]
},
{ company: 'CompanyName2',
child: [
{ title: 'title2b',
baby: [
{ title: 'titleb3ab' },
{ title: 'titleb3abc' }
]
}
]
}
]
And this is my expected Array:
let newArray = [
{
company: 'companyName1',
child_title_0: 'title1a',
child_title_1: 'title1a',
child_baby_0: 'title1ab',
child_baby_1: 'title1abc',
child_baby_2: 'title1abcd',
child_baby_3: 'title1abcde',
},
{
company: 'companyName2',
child_title_0: 'title2b',
child_baby_0: 'titleb3ab',
child_baby_1: 'titleb3abc',
}
]
Basically I need to flatten each of the top level objects of the array. Since the nested objects have the same keys (follow a model, and are dynamic -- some items have 10 nested objects, some 0, etc.) I have to dynamically generate each of the new keys, possibly based in the index of the loops.
Any help -- direction is appreciated.
Thanks!
You can use the map function to return a manipulated version of each object in the array.
let results = [
{
company: 'CompanyName1',
child: [
{
title: 'title1a',
baby: [
{ title: 'title1ab' },
{ title: 'title1abc' }
]
},
{
title: 'title2a',
baby: [
{ title: 'titleb2abcd' },
{ title: 'titleb2abcde' }
]
}
]
},
{
company: 'CompanyName2',
child: [
{
title: 'title2b',
baby: [
{ title: 'titleb3ab' },
{ title: 'titleb3abc' }
]
}
]
}
];
let flattened = results.map(company => {
let childCount = 0, babyCount = 0;
company.child.forEach(child => {
company['child_title_'+childCount] = child.title;
child.baby.forEach(baby => {
company['child_baby_'+babyCount] = baby.title;
babyCount++;
});
childCount++;
});
delete company.child;
return company;
});
console.log(flattened);

Categories

Resources