The goal is to create an Array of Objects who's structure is [{ day: String, name: [String] }, …]. Noting that repeated days will be grouped into the same Object who's day indicates the grouping.
The input data is an Array who's format is [{ birth: String, name: String }, …].
Firstly you will want to extract a meaningful day of the month out of a date string that isn't formatted in a standard way (e.g.,
"25.04.1988").
To transform this String you can use a regular expression, with a callback parameter that orders the date in ISO-8601 Date format which the ECMA standard stipulates that Date must support in its construction. Note also that Date expects its input to be in UTC time, i.e., it is ignorant of timezone.
Such a construction may look like
const input = new Date('23.04.1988'.replace(/^(\d+)[^\d+](\d+)[^\d+](\d+)$/, (...backRefs) => {
return `${backRefs[3]}-${backRefs[2]}-${backRefs[1]}`;
}));
The ECMAScript syntax (which Node.js implements) for declaring a regular expression literal is utilised here: /RegularExpressionBody/RegularExpressionFlags, where the flags are empty and the expression body is ^(\d+)[^\d+](\d+)[^\d+](\d+)$.
This regular-expression does not match a valid date, but instead any construction of three series of numerals (\d+), broken by non-numeric characters [^\d+] which constitutes an entire String. It then reconstructs them using back-references in the order of 3-2-1 with dashes separating them as per the ISO-8601 Date's format.
That is the part `${backRefs[3]}-${backRefs[2]}-${backRefs[1]}`, which utilises Template Literals often referred to incorrectly as template strings.
Constructing a Date Object from this new variable const input will work, but it's toString() method will return the String "Invalid Date" if it is indeed, invalid. If the input date does not match the regular expression, such that a back-reference indexes a capture group that doesn't capture; then the type of the constructed Date is undefined as it will be constructed with invalid inputs. This particular result could be used to aggregate invalid birth-dates.
Now that you know how to extract the day correctly, you can get to sorting and grouping the inputs.
const input = [{
"birth" : "23.04.1988",
"name": "Tom Smith"
},
{
"birth": "15.04.2010",
"name": "Mini Jall"
},
{
"birth": "23.04.2001",
"name":"Michael Bird"
}];
Let input be your input Array of Objects.
The Array Object provides a Function called map on its prototype that you can use on input since it is an Array. This Function's specification is Array.prototype.map(callbackFn[, thisArg]). It is called once for each element that exists in the Array which is the object being traversed. The returned value of the callback replaces the original object in a temporary Array which is returned by map upon completion. In other words, you can map your Array into a new structure, by returning that structure from the callback using each element's properties as you iterate. Note that the argument thisArg is a context under which the map Function is invoked, and that if you call input.map then the context is inherited as input and so, thisArg is only optional.
Such an invocation would look like input.map(argumentsList) where argumentsList is a list that contains only a callback Function. The callback takes up to three parameters: currentValue, currentInndex, and the object being traversed.
So your callback should take the form of
(curr, idx, arr) => { return…; } // or
function (curr, idx, arr) { return…; }
In this callback, you want to transform the birth parameter to a day, so using the discussed methodology you would do something like
let dateCB = ({ birth, name }, idx, arr) => {
const dateString = birth.replace(/^(\d+)[^\d+](\d+)[^\d+](\d+)$/, (...backRefs) => {
return `${backRefs[3]}-${backRefs[2]}-${backRefs[1]}`;
});
const date = new Date(dateString);
const retObj = { day: date.getDate(), month: date.getUTCMonth() + 1, name };
Object.defineProperty(retObj, 'month', { enumerable: false });
return retObj;
};
We add 1 to the month because getUTCMonth returns a zero indexed month of the year. Also we define the property month to be non-enumerable as we don't want it to show up in the result object. Note also that the curr from earlier, is being destructured in { birth, name } and re-structured in { day: date.getDate(), month: date.getUTCMonth() + 1, name }. Destructuring assignment allows you to reduce an Object's properties into property names and in the argumentsList it declares those properties as variables in the scope of the arrow function. This is actually a shorthand for { birth: birth, name: name } since they have the same identifier as the properties of the input object curr.
At this point, you will have an
Array of
Objects.
[
{
"day" :23,
"month": 4,
"name": "Tom Smith"
},
{
"day": 15,
"month": 4,
"name": "Mini Jall"
},
{
"day": 23,
"month": 4,
"name": "Michael Bird"
}
]
Except that the month properties will not be enumerable.
You want to collate these Objects such that the String name is instead an Array of all names who's parent Object shares identical day and month properties with other Object members of the Array.
So we will look to using Array.prototype.reduce which is defined as Array.prototype.reduce(callbackfn[, initialValue]). The callback Function takes up to four parameters: previousValue, currentValue, currentIndex, and the object being traversed. If you provide initialValue then reduce iterates beginning at the first element, and the initialValue is provided to the callback as previousValue. However if you omit initialValue then reduce iterates beginning at the second element providing the first element as previousValue. What you return from the callback is then passed to the next iteration as its previousValue.
We'll also be using Array.prototype.findIndex which is defined as Array.prototype.findIndex(predicate[, thisArg ]). The predicate Function takes up to three parameters and should return a boolean coercible result (such as 1, 0, true, false, undefined, etc). findIndex will return -1 if no predicate returns true, or it will return the index it reached when the first predicate does.
We can use this to find out if an array contains a matching day and month for an iteration of the reducer.
({ day: tDay, month: tMonth, name: tName }) => {
return tDay === … && tMonth === …;
}
We want to construct a new Array output and we will iterate over the input using reduce, constructing the output as we go. For this reason, reduce will be called with an empty array as its optional second argument
let reducer = (prev, { day, month, name }) => {
if (prev.length === 0) { /// this is the first iteration, where prev is empty
prev.push({ day, month, name: [name] });
} else { /// this is any other iteration, now we have to search `prev`
let where = prev.findIndex(({ day: tDay, month: tMonth, name: tName }) => {
return tDay === day && tMonth === month;
});
if (where !== -1) {
prev[where].name
.push(name);
} else {
prev.push({ day, month, name: [name] });
}
}
return prev;
}
And the invocation looks like
input.map(dateCB)
.reduce(reducer, []);
Lastly we look at the function
Array.prototype.sort defined as
Array.prototype.sort(comparefn). The
comparefn Function receives two arguments,
x and
y. The job of
comparefn is to describe how
x relates to
y. The sorter expects a negative
Number returned from
comparefn if
x < y, zero if
x == y and positive if
x > y.
x and
y are two members of the
Array being sorted, and so since those members have the same structure you may destructure
x and
y as
{ day: xDay }, { day: yDay } and return
xDay - yDay for a sufficient result.
In this final snippet, I introduce a new variable
cDay which is just a
String representation of the
day property. I also implement the non-enumerable property on the final
Objects in the
Array and the sorter on the output.
const input = [{
"birth" : "23.04.1988",
"name": "Tom Smith"
},
{
"birth": "15.04.2010",
"name": "Mini Jall"
},
{
"birth": "23.04.2001",
"name":"Michael Bird"
}];
const dateCB = ({ birth, name }, idx, arr) => {
const dateString = birth.replace(/^(\d+)[^\d+](\d+)[^\d+](\d+)$/, (...backRefs) => {
return `${backRefs[3]}-${backRefs[2]}-${backRefs[1]}`;
});
const date = new Date(dateString);
const retObj = { day: date.getDate(), month: date.getUTCMonth() + 1, name };
return retObj;
};
const reducer = (prev, { day, month, name }) => {
const cDay = day.toString(10);
const retObj = { day: cDay, month, name: [name] };
Object.defineProperty(retObj, 'month', { enumerable: false });
if (prev.length === 0) {
prev.push(retObj);
} else {
const where = prev.findIndex(({ day: tDay, month: tMonth, name: tName }) => {
return tDay === cDay && tMonth === month;
});
if (where !== -1) {
prev[where].name
.push(name);
} else {
prev.push(retObj);
}
}
return prev;
};
const sorter = ({ day: bDay }, { day: aDay }) => {
return bDay - aDay;
};
const output = input.map(dateCB).reduce(reducer, []).sort(sorter);
console.log(JSON.stringify(output));
jsonelements do not have anid, why do you think logging it would work?birthproperty is in the current month, then you'll reduce that array to group people by day of birth, then sort the resulting array to get it in order"name": "Mini Jall"and not"name": ["Mini Jall"]?