This alternate to the answer from T.J. Crowder takes several of the decision points mentioned there and makes them explicit function calls.
const process = (test, transform, data) =>
typeof data == "object"
? ( Array .isArray (data)
? (xs) => xs .map (([_, x]) => x)
: Object .fromEntries
) ( Object .entries (data) .map (([k, v]) =>
[k, test (k, v) ? transform (v) : process (test, transform, v)]
))
: data
It accepts two functions as well as your data object. The first one is to test whether you've hit a nested value you want to massage. So we could imagine something like (k, v) => k .startsWith ('tenor')
or (k, v) => Array .isArray (v)
. The second function accepts the value at that entry and returns an updated value, perhaps (v) => v . sort((a, b) => a .date .localeCompare (b .date))
.
(Note: Your sort call is problematic. Don't use .sort ((a, b) => a < b)
, which returns a boolean that is then coerced into a 0
or a 1
, whereas a proper comparator should return -1
when a < b
. If you can compare with <
, then this should always work (a, b) => a < b ? -1 : a > b ? 1 : 0
. I don't know if there's a more complete SO question on this than Sorting an array with .sort((a,b) => a>b) works. Why?.)
You can see it in action in this snippet:
const process = (test, transform, data) =>
typeof data == "object"
? ( Array .isArray (data)
? (xs) => xs .map (([_, x]) => x)
: Object .fromEntries
) ( Object .entries (data) .map (([k, v]) =>
[k, test (k, v) ? transform (v) : process (test, transform, v)]
))
: data
const dateSort = (xs) => xs .slice (0) .sort ((a, b) => a .date .localeCompare (b .date))
const objRes = {
group1: {
type1: {
name1: {
tenor1: [
{date: '2019-12-23', value: 35},
{date: '2019-12-25', value: 32},
{date: '2019-12-24', value: 30},
]
},
name2: []
},
type2: {}
},
group2: {
type3: [
{date: '2020-01-03', value: 42},
{date: '2019-01-01', value: 43},
{date: '2019-01-02', value: 44},
]
}
};
// This one only sorts the first group of dates
console .log (
process (
(k, v) => k .startsWith ('tenor'),
dateSort,
objRes
)
)
// This one sorts both groups
console .log (
process (
(k, v) => Array .isArray (v),
dateSort,
objRes
)
)
The code is a bit dense. The outermost condition applies our processing only if the data is an object, returning it intact if not, and doing the following processing if it is:
We first use Object.entries
to turn our object into name-value pairs, then map over that object by testing each pair to see whether we've hit something we should transform and returning either the transformed value or the result of recursing on the value.
Perhaps the trickiest bit is this:
( Array .isArray (data)
? (xs) => xs .map (([_, x]) => x)
: Object .fromEntries
)
Here we just choose a function to apply to the converted pairs to turn it back into a whole. If it's an array, we skip the keys and return an array of the values. If it's not, we use Object.fromEntries
to create an object of them. If Object.entries
is not available in your environment, it's easy to shim.