I got a schema object that looks like this:
const schema = {
social: {
facebook: 'someValue',
twitter: {
department: {
departmentImage: {
editable: 'someValue'
}
}
}
}
};
The editable property indicates a value that I want to edit, and may appear in several nested locations in the object.
My approach to edit it is to recursively create a new object who is an exact copy of the original, and populate a new value where I encounter editable.
Like this:
const formatSchema = (schema, data, formattedSchema = {}) => {
for (const schemaKey in schema) {
const firstKey = Object.keys(schema[schemaKey])[0];
if (schema[schemaKey] instanceof Object) {
formattedSchema[schemaKey] = schema[schemaKey];
formatschema(schema[schemaKey], data, formattedSchema[schemaKey]);
}
if (schema[schemaKey] instanceof Object && firstKey === 'editable') {
*replacing data logic*
formattedSchema[schemaKey] = ...*replacingData*;
formatschema(schema[schemaKey], data, formattedSchema[schemaKey]);
} else {
formattedSchema[schemaKey] = schema[schemaKey];
}
}
return formattedSchema;
};
But I feel this solution may be inefficient as I create every single bit of the object from scratch and this would happen thousands of times a day.
Is there a way to do it better?
Here's a recursive immutable update that works for any native input type. Don't worry about performance here as it's plenty fast, even if your object has thousands of fields. Let me know how this suits you and I can make a change if it's needed -
function update(t, func) {
switch (t?.constructor) {
case Object:
return Object.fromEntries(
Object.entries(t).map(([k,v]) =>
[k, func([k, update(v, func)])]
)
)
case Array:
return t.map((v, k) => func([k, update(v, func)]))
default:
return func([null, t])
}
}
const schema = {
social: {
facebook: 'someValue',
twitter: {
department: {
departmentImage: {
editable: 'someValue'
}
},
someArr: [{ editable: 1 }, { editable: 2 }, { hello: "world" }]
},
}
}
console.log(update(schema, ([k,v]) =>
k == "editable" ? "✅" : v
))
.as-console-wrapper {min-height: 100% !important; top: 0}
{
"social": {
"facebook": "someValue",
"twitter": {
"department": {
"departmentImage": {
"editable": "✅"
}
},
"someArr": [
{
"editable": "✅"
},
{
"editable": "✅"
},
{
"hello": "world"
}
]
}
}
}
Related
We are using vue-i18n and maintain our messages within an js variable. This leads to deeply nested objects or key/value pairs.
const messages = {
en: {
message: {
page1: {
title: "Some title",
button: {
title: "Foo",
},
subpage: {
...
}
},
...
},
},
de: {...},
};
As u can see, without an appropriate sorting this file will be really confusing. My idea is to sort the whole file alphabetically by keys.
Is there an algorithm/code that can be used for this? Or do I have to write it myself?
You can do some recursivity like :
I used the following answer to write the order function
const order = (unordered) => Object.keys(unordered).sort().reduce(
(obj, key) => {
obj[key] = unordered[key];
return obj;
}, {}
);
const message = {
fr: {
message: "Bonjour",
a: 1
},
en: {
message: "Hello",
a: {
c: 1,
b: 2
}
},
es: "Hola"
}
const sortObjectDeeply = (object) => {
for (let [key, value] of Object.entries(object)) {
if (typeof(value) === "object") {
object[key] = sortObjectDeeply(value)
}
}
return order(object)
}
console.log(sortObjectDeeply(message))
I have attached a working snippet ,I am looking for more optimised solution ,as I will be dealing with fairly large amount of data .Any solution with fairly less complexity and more optimised which you can suggest thanks.
let data = [
{
category_name: "Home",
isparent: true
},
{
category_name: "City",
isparent: true
},
{
category_name: "Home town",
parent: "Home",
isparent: false
},
{
category_name: "City_town",
parent: "City",
isparent: false
}
]
/**
* req
* let data=[
* {category_name:"home",
* children:[{""}]
* }
* ]
*/
let res = data.reduce((acc, ele) => {
let obj = {
children: []
}
if (ele.isparent) {
obj = ele;
obj.children = [];
acc.push(obj);
} else {
let _filt = acc.find(_ele => _ele.category_name == ele.parent)
_filt.children.push(ele);
}
return acc;
}, [])
console.log(JSON.stringify(res));
Your current solution is inefficient because of the nested acc.find() - it will cause the solution to approach O(N^2). You can achieve a much more efficient solution as you have unique keys, just make a map/dictionary (native js object works fine for this) - I think it is a much easier format to work with but you can simply Object.values(map) to get the requested output format.
let data = [ { category_name: "Home", isparent: true }, { category_name: "City", isparent: true }, { category_name: "Home town", parent: "Home", isparent: false }, { category_name: "City_town", parent: "City", isparent: false } ];
let res = Object.values(data.reduce((acc, ele) => {
if (ele.isparent) {
acc[ele.category_name] = ele;
ele.children = [];
} else {
acc[ele.parent].children.push(ele);
}
return acc;
}, {}));
console.log(JSON.stringify(res));
This implementation only requires one loop over the data and is closer to O(N).
Would something like this work? Not sure if it's more performant, but maybe a little?
const as_obj = data.reduce((accum, curr) => {
const { category_name, isparent, parent } = curr;
if (isparent && accum[category_name]) {
// This is a parent, but we've already seen one of its children;
// we created the parent key for that already.
return accum;
}
if (isparent && !accum[category_name]) {
// This is the first time we've seen the parent key, creating an object for it.
return { ...accum, [category_name]: { ...curr, children: [] } };
}
if (parent && !accum[parent]) {
// We haven't seen this child's parent yet, but we'll make a place for it.
accum[parent] = { category_name, children: [] };
}
const children = [ ...accum[parent].children, curr];
return { ...accum, [parent]: { ...accum.parent, children } };
}, {});
const res = Object.values(as_obj);
consider the following array.
routingButtonsHighlighter = [
{vehicle: true},
{userAssignment: false},
{relations: false}
];
What is the best way to build a function which can do the following goals?
1) will set all members to false
2) set chosen member to true ( passed as a parameter )
Absent more specific requirements, this is a bit of a choose-your-own-adventure.
(Note: For brevity this code uses ES6 computed property names and destructuring assignment and ES2018 object spread syntax, all of which can be transpiled by TypeScript.)
If each object has exactly one key
...and you want to mutate the original array and objects
const objects = [ { vehicle: true }, { userAssignment: false }, { relations: false } ];
function selectKey(objects, selectedKey) {
for (let obj of objects) {
const [key] = Object.keys(obj);
obj[key] = key === selectedKey;
}
return objects;
}
selectKey(objects, 'userAssignment');
console.log(objects);
...and you want a new array of new objects
const objects = [ { vehicle: true }, { userAssignment: false }, { relations: false } ];
function selectKey(objects, selectedKey) {
const newObjects = [];
for (let obj of objects) {
const [key] = Object.keys(obj);
newObjects.push({ [key]: key === selectedKey });
}
return newObjects;
}
console.log(selectKey(objects, 'userAssignment'))
...but you really like functional style
const objects = [ { vehicle: true }, { userAssignment: false }, { relations: false } ];
function selectKey(objects, selectedKey) {
return objects.map(obj => {
const [key] = Object.keys(obj);
return { [key]: key === selectedKey };
});
}
console.log(selectKey(objects, 'userAssignment'))
If the objects can have more than one key
...and you want to mutate the original array and objects
const objects = [
{ vehicle: true, relations: false },
{ userAssignment: false, vehicle: true },
{ relations: false, userAssignment: false },
];
function selectKey(objects, selectedKey) {
for (let obj of objects) {
for (let key of Object.keys(obj)) {
obj[key] = key === selectedKey;
}
}
return objects;
}
selectKey(objects, 'userAssignment');
console.log(objects);
...and you want a new array of new objects
const objects = [
{ vehicle: true, relations: false },
{ userAssignment: false, vehicle: true },
{ relations: false, userAssignment: false },
];
function selectKey(objects, selectedKey) {
const newObjects = [];
for (let obj of objects) {
const newObj = {};
for (let key of Object.keys(obj)) {
newObj[key] = key === selectedKey;
}
newObjects.push(newObj);
}
return newObjects;
}
console.log(selectKey(objects, 'userAssignment'))
...but you really like functional style
const objects = [
{ vehicle: true, relations: false },
{ userAssignment: false, vehicle: true },
{ relations: false, userAssignment: false },
];
function selectKey(objects, selectedKey) {
return objects.map(obj =>
Object.keys(obj).reduce((newObj, key) =>
({ ...newObj, [key]: key === selectedKey }),
{}
)
);
}
console.log(selectKey(objects, 'userAssignment'))
You can iterate the array with Array.forEach(), get the key using Object.keys(), compare to the selected key, and set the value accordingly:
const routingButtonsHighlighter = [{vehicle: true}, {userAssignment: false}, {relations: false}];
const select = (arr, selectedKey) =>
arr.forEach((o) => {
const key = Object.keys(o)[0];
o[key] = key === selectedKey;
});
select(routingButtonsHighlighter, 'userAssignment');
console.log(routingButtonsHighlighter);
Creating a method for something like this would be highly specialized, so to abstract it, I've decided to write it like this:
function arrayFlagSinglePropertyTrue(key, arrayofobjects) {
for (let i in arrayofobjects) {
let keys = Object.keys(arrayofobjects[i]);
if (keys[0] == key) {
arrayofobjects[i][keys[0]] = true;
} else {
arrayofobjects[i][keys[0]] = false;
}
}
return arrayofobjects;
}
routingButtonsHighlighter = [
{vehicle: true},
{userAssignment: false},
{relations: false}
];
console.log(arrayFlagSinglePropertyTrue("relations", routingButtonsHighlighter));
Although this will get what you require done, its highly specialized and only works if the objects in the array contain one property or at the very least the first property in the object itself is the one you want to set to flag.
Edit: Some advice:
Uniformity in lists helps avoid the issue you have. By structuring your objects with uniform property names and then acting on the values themselves, you no longer require the use of specialized functions or code in order to modify it. At this point you can rely on fundamental programming logic to change the properties efficiently.
If you get the list from some external source and have no control over it, then you may need to either reorganize it yourself. If you can't then making specialized functions/codes is your last resort.
If possible, take something like this:
routingButtonsHighlighter = [
{vehicle: true},
{userAssignment: false},
{relations: false}
];
Organize it into something like this where the actual object properties are uniform:
let betterStructureObject = [
{ propertyName: "vehicle", status: true },
{ propertyName: "userAssignment", status: false },
{ propertyName: "vehicle", status: false },
]
So you can easily loop over it and not have to worry about writing specialized code.
for (let i in betterStructureObject) {
if (betterStructureObject[i].propertyName == "vehicle")
betterStructureObject[i].status = true;
else betterStructureObject[i].status = false;
}
I have a list of properties like this
[{"ID":"0"},{"Day":""},{"Time":""},{"Type":"Both"},{"Status":"false"},
{"ID":"0"},{"Day":""},{"Time":""},{"Type":"Both"},{"Status":"false"}]
I would like to convert them to something like this
[{"ID":"0","Day":"","Time":"","Type":"Both","Status":"false"},
{"ID":"0","Day":"","Time":"","Type":"Both","Status":"false"}]
This is one problem that i face nearly everytime converting a form to json and submitting it to a controller with complex type.
You could check if an object contains the key ID and build a new object with all following objects.
var data = [{ ID: "0" }, { Day: "" }, { Time: "" }, { Type: "Both" }, { Status: "false" }, { ID: "0" }, { Day: "" }, { Time: "" }, { Type: "Both" }, { Status: "false" }],
result = data.reduce(function (r, o) {
if ('ID' in o) {
r.push(Object.assign({}, o));
} else {
Object.assign(r[r.length - 1], o);
}
return r;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
That is one weird input,..
With the following I'm assuming there is always an ID, and it comes first. As that data structure has no way of splitting the properties.
var a = [{"ID":"0"},{"Day":""},{"Time":""},{"Type":"Both"},{"Status":"false"},
{"ID":"0"},{"Day":""},{"Time":""},{"Type":"Both"},{"Status":"false"}];
var
ret = [],
c = null;
a.forEach((r) => {
if (r.ID) { c = {}; ret.push(c); }
let k = Object.keys(r)[0];
c[k] = r[k];
});
console.log(ret);
I figure I shouldn't be having trouble with this, but I am. I am trying to switch up the syntax/variables of a JSON object to match a certain parameters.
Here is the JSON I am working with:
{
"name":"BHPhotovideo",
"prices":[
{
"price":"799.00",
"createdAt":"2017-07-23T16:17:11.000Z",
"updatedAt":"2017-07-23T17:21:41.000Z"
},
{
"price":"770.00",
"createdAt":"2017-07-21T16:17:11.000Z",
"updatedAt":"2017-07-23T16:17:11.000Z"
},
{
"price":"599.00",
"createdAt":"2017-07-19T16:17:11.000Z",
"updatedAt":"2017-07-22T16:17:11.000Z"
},
{
"price":"920.00",
"createdAt":"2017-07-22T16:17:11.000Z",
"updatedAt":"2017-07-22T16:17:11.000Z"
}
]
},
etc...
I am just trying to get the data to be formatted like this:
{
"label":"BHPhotoVideo", // Same as name
"data":[
{
"x":"2017-07-23T16:17:11.000Z", // Same as createdAt
"y":799 // Same as price
},
{
"x":"2017-07-21T16:17:11.000Z",
"y":770
},
{
"x":"2017-07-19T16:17:11.000Z",
"y":599
},
{
"x":"2017-07-22T16:17:11.000Z",
"y":920
}
]
},
etc...
The amount of these objects are dynamic/subject to change, I've been making a mess out of foreach loops and trying to piece this together. I keep coming into errors, what's the best way to approach this?
What about this ?
data.map(
(item) => ({
"label":"BHPhotoVideo", // Same as name
"data": item.prices.map(nested => ( {
"x":nested.createdAt,
"y":nested.price
}))
})
)
Did you want the y values to be integers?
var ar = [
{
"name":"BHPhotovideo",
"prices":[
{
"price":"799.00",
"createdAt":"2017-07-23T16:17:11.000Z",
"updatedAt":"2017-07-23T17:21:41.000Z"
},
{
"price":"770.00",
"createdAt":"2017-07-21T16:17:11.000Z",
"updatedAt":"2017-07-23T16:17:11.000Z"
},
{
"price":"599.00",
"createdAt":"2017-07-19T16:17:11.000Z",
"updatedAt":"2017-07-22T16:17:11.000Z"
},
{
"price":"920.00",
"createdAt":"2017-07-22T16:17:11.000Z",
"updatedAt":"2017-07-22T16:17:11.000Z"
}
]
},
{
"name":"Adorama",
"prices":[
{
"price":"799.00",
"createdAt":"2017-07-22T16:17:11.000Z",
"updatedAt":"2017-07-23T17:21:41.000Z"
},
{
"price":"799.00",
"createdAt":"2017-07-20T16:17:11.000Z",
"updatedAt":"2017-07-23T16:17:11.000Z"
},
{
"price":"810.00",
"createdAt":"2017-07-18T16:17:11.000Z",
"updatedAt":"2017-07-22T16:17:11.000Z"
},
{
"price":"799.00",
"createdAt":"2017-07-17T16:17:11.000Z",
"updatedAt":"2017-07-22T16:17:11.000Z"
}
]
}
];
var out = ar.map( function(a) {
return {
"label" : a.name,
"prices" : a.prices.map( function(aa) { return {x: aa.createdAt, y: aa.price} })
}
});
console.log( out );
map over the original array returning a changed object; returning the name, and a new array from using map over the prices.
const obj2 = obj.map((item) => {
return {
label: item.name,
data: item.prices.map((data) => {
return {
x: data.createdAt,
y: data.price
}
})
}
});
DEMO