Javascript Object key sort using moment.js - javascript

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);

Related

access the properties and their values of an object dynamically in JavaScript

I wanna access the properties and their values of an object dynamically, the scenario is:
const SoldeN = {
1:4,
}
const SoldeN_1 = {
2:5,
}
const soldes = [
{ formula: '=SoldeN("1")',value:0},
{ formula: '=SoldeN_1("2")',value:0},
{ formula: '=SoldeN_1("1")+SoldeN_1("2")',value:0},
];
What I've tried so far:
const getProperty = string => string.match(/\(.*?\)/g).map(x => x.replace(/[(")]/g, ''))
const getObject = string => string.match(/[=+](.*?)\(/g).map(x => x.replace(/[=(+]/g, ''))
console.log(soldes.map(solde=>[getObject(solde.formula),getProperty(solde.formula)]))
[
[["SoldeN"], ["1"]],
[["SoldeN_1"], ["2"]],
[
["SoldeN_1", "SoldeN_1"],
["1", "2"],
],
];
Till now Everything is ok, but To get the value of an object dynamically based on property I used this function:
const getPropertyValue = (obj, prop) => obj[prop];
getPropertyValue('SoldeN','1')
//'o'
It gives me 'o' instead of 1, I know If I've passed the reference of an object it bring its value; but to get it dynamically I've to pass the actual name of an object which is string, not the object reference.
The expected Result would be:
result = [
{ formula: '=SoldeN("1")',value:4},
{ formula: '=SoldeN_1("2")',value:5},
{ formula: '=SoldeN_1("1")+SoldeN_1("2")',value:9},
];
Why don't you just put your data inside an object and map the data inside loop?
There is no need to use eval at all.
const data = {
SoldeN: {
1: 4,
},
SoldeN_1: {
2: 5,
},
};
const soldes = [
{ formula: '=SoldeN("1")', value: 0 },
{ formula: '=SoldeN_1("2")', value: 0 },
{ formula: '=SoldeN("1")+SoldeN_1("2")', value: 0 },
];
const getProperty = string =>
string.match(/\(.*?\)/g).map(x => x.replace(/[(")]/g, ""));
const getObject = string =>
string.match(/[=+](.*?)\(/g).map(x => x.replace(/[=(+]/g, ""));
const output = soldes.map(solde => {
const objs = getObject(solde.formula);
const props = getProperty(solde.formula);
const newVal = objs.reduce((carry, item, idx) => {
carry += data[item][props[idx]];
return carry;
}, solde.value);
return {
...solde,
value: newVal,
};
});
console.log(output);
You can eval the object name.
const SoldeN = { 1:4 };
const getPropertyValue = (obj, prop) => eval(obj)[prop];
console.log(getPropertyValue('SoldeN','1'));
But be cautious as it is risky! If bad code can get into the eval argument, things can happen.
You can make use of javascript eval method to execute the expression.
Logic
Loop through nodes in soldes.
Replace all "=" with empty string. "(" with "[" and ")" with "]".
This will mmake the expression =SoldeN("1")+SoldeN_1("2") to SoldeN["1"]+SoldeN_1["2"].
Evaluating this will give you the expected result.
const SoldeN = {
1: 4,
};
const SoldeN_1 = {
2: 5,
};
const soldes = [
{ formula: '=SoldeN("1")', value: 0 },
{ formula: '=SoldeN_1("2")', value: 0 },
{ formula: '=SoldeN("1")+SoldeN_1("2")', value: 0 },
];
const getValue = (formula) => eval(formula.replaceAll("=", "").replaceAll("(", "[").replaceAll(")", "]"));
const result = soldes.map((item) => ({
formula: item.formula,
value: getValue(item.formula)
}));
console.log(result);
Please Note Error handling is considered as out of scope.

Merge values of objects with same keys into an array using lodash

I have an array of objects which looks like this:
const childrens = [
{ orderName: "#1004" },
{ orderName: "#1006" },
{ orderName: "#1007" },
{ orderName: "#1005" },
{ deliveryDate: "25-25-25" },
{ signature: "xq" },
];
I want this to be converted into object and have values of same keys as an array of values like this
{
orderName: [ '#1004', '#1006', '#1007', '#1005' ],
deliveryDate: [ '25-25-25' ],
signature: [ 'xq' ]
}
The way i'm doing this right now is by using a reduce function like this
_.reduce(
childrens,
(acc, cur) => {
const pairs = _.chain(cur).toPairs().flatten().value();
if (acc[pairs[0]] === undefined) {
acc[pairs[0]] = [pairs[1]];
} else {
acc[pairs[0]].push(pairs[1]);
}
return acc;
},
{},
);
I'm wondering if there's a cleaner way using built-in lodash functions?
You can use _.mergeWith() with array spread. When merging 2 values, check if the 1st (a) is still undefined (the empty object), if so return the 2nd item wrapped in an array, if it exists, use array spread to concat them:
const children = [{"orderName":"#1004"},{"orderName":"#1006"},{"orderName":"#1007"},{"orderName":"#1005"},{"deliveryDate":"25-25-25"},{"signature":"xq"}];
const result = _.mergeWith({}, ...children, (a, b) =>
_.isUndefined(a) ? [b] : [...a, b]
);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js" integrity="sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
If you don't use lodash, you can do like below.
const children = [
{ orderName: "#1004" },
{ orderName: "#1006" },
{ orderName: "#1007" },
{ orderName: "#1005" },
{ deliveryDate: "25-25-25" },
{ signature: "xq" },
];
const output = children.reduce((a, b) => {
const key = Object.keys(b)[0];
if (!a[key]) {
a[key] = [b[key]];
} else {
a[key].push(b[key]);
}
return a;
}, {});
console.log(output);
Something like this will work on modern JS platforms (added bonus, you don't need lodash to use reduce). Essentially:
loop over each item in childrens
get the key(s) for that object
loop over the key(s)
if result doesn't have that key set it to an empty array
push the value for the key to the relevant array on the result
const desiredOutput = childrens.reduce(
(acc, current) => {
const keys = Object.keys(current);
keys.forEach(key => {
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(current[key]);
});
return acc;
},
{}
);
You can simply get the result using vanilla JS using reduce and Object.entries
(acc[prop] = acc[prop] ?? []).push(value);
const childrens = [
{ orderName: "#1004" },
{ orderName: "#1006" },
{ orderName: "#1007" },
{ orderName: "#1005" },
{ deliveryDate: "25-25-25" },
{ signature: "xq" },
];
const result = childrens.reduce((acc, curr) => {
const [[prop, value]] = Object.entries(curr);
(acc[prop] = acc[prop] ?? []).push(value);
return acc;
}, {});
console.log(result);

How to sort array of objects by dynamic proprety name?

I would appreciate your help or idea how to sort array of object in ascending order by first value.
This is array
[{"0":"yanni"},
{"1150":"finally"},
{"852":"discovered"},
{"59":"what"},
{"30064":"had"},
{"397":"really"},
{"3100":"happened"},
{"3":"to"},
{"0":"skura"},
{"7523":"lets"},
{"6550":"try"},
]
and I want to be sorted by first number like:
[{"0":"yanni"},
{"0":"skura"},
{"3":"to"},
{"59":"what"},
.....
]
I tried like this
const keys = Object.keys(sentenceFreq);
console.log("key",keys)
const valuesIndex = keys.map((key) => ({key, value: sentenceFreq[key]}));
valuesIndex.sort((a, b) => b.value - a.value); // reverse sort
const newObject = {};
for (const item of valuesIndex) {
newObject[item.key] = item.value;
}
console.log("test", newObject);
but they are sorted only by key values...
Any help is appericated.
Thank you!
Use sort. Grab the first element of the Object.keys of both a and b and coerce them to an integer, then return the new sort order.
const arr = [{"0":"yanni"},{"1150":"finally"},{"852":"discovered"},{"59":"what"},{"30064":"had"},{"397":"really"},{"3100":"happened"},{"3":"to"},{"0":"skura"},{"7523":"lets"},{"6550":"try"}];
arr.sort((a, b) => {
const first = +Object.keys(a)[0];
const second = +Object.keys(b)[0];
return first - second;
});
console.log(arr);
By default, the sort method sorts elements alphabetically. but in your case, you can get the keys and try sort numerically just add a new method that handles numeric sorts (shown below) -
let arr = [{
"0": "yanni"
}, {
"1150": "finally"
}, {
"852": "discovered"
}, {
"59": "what"
}, {
"30064": "had"
}, {
"397": "really"
}, {
"3100": "happened"
}, {
"3": "to"
}, {
"0": "skura"
}, {
"7523": "lets"
}, {
"6550": "try"
}];
arr.sort(function(a, b) {
const first = +Object.keys(a)[0];
const second = +Object.keys(b)[0];
return first - second
});
console.log(arr);

Convert string dot notation into javascript object

I'm working on an existing project that takes query parameters in an oddly formatted string dot notation. But they must be converted into objects before processing. This is currently being performed with conditionals on specific keys by name.
How can this be performed dynamically? Below you will find an example of the input and desired output.
Input:
{
date.gte: '2019-01-01',
date.lt: '2020-01-01'
}
Output:
{
date: {
gte: '2019-01-01',
lt: '2020-01-01'
}
}
You could use reduce and split methods to split each key into array and build nested structure based on that array.
const data = {
'date.gte': '2019-01-01',
'date.lt': '2020-01-01'
}
const result = Object.entries(data).reduce((r, [k, v]) => {
k.split('.').reduce((a, e, i, ar) => {
return a[e] || (a[e] = ar[i + 1] ? {} : v)
}, r)
return r;
}, {})
console.log(result)
By you saying "oddly formatted string dot notation" I assume you mean "date.gte" & "date.lt"
const input = {
"date.gte": "2019-01-01",
"date.lt": "2020-01-01"
};
const res = Object.keys(input).reduce(
(result, current) => {
const [, operator] = current.split(".");
result.date[operator] = input[current];
return result;
},
{ date: {} }
);
console.log(res);
Here's an improvement on Dan's answer that doesn't rely on knowing the key-value pairs in the original object. As much as Nenad's answer blows this out of the water, I worked for too long on this to not post it :)
const formatter = (weirdObject, s = '.') => Object.keys(weirdObject).reduce((acc, cur) => {
const [parent, child] = cur.split(s);
if (!acc[parent]) acc[parent] = {};
acc[parent][child] = weirdObject[cur];
return acc;
}, {});
// -- Demonstration:
const input1 = {
"date.gte": "2019-01-01",
"date.lt": "2020-01-01"
};
const input2 = {
"person:name": "Matt",
"person:age": 19
};
const res1 = formatter(input1);
const res2 = formatter(input2, ':');
console.log(res1);
console.log(res2);

Search for values of a specific key in a nested object

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.

Categories