简体   繁体   中英

loop through a json-Array with javascript

i have a problem looping through a json-Array with javascript in nodejs. my array called birthdaysArray looks like:

[{"birth":"23.04.1988","name":"Tom Smith"},{"birth":"15.04.2010","name":"Mini Jall"},...,{"birth":"23.04.2001","name":"Michael Bird"},{"birth":"15.11.1999","name":"Frank Middleton"}]

now i want to have an array which list the birthdays (sorted by the day of birth) of the current month. entries with the same day and month (eg 23.04) should be listed in the same date. it should looks like, if april is the current month (the list has more than 100 entries.):

[{"day":"15","name":["Mini Jall"]},{"day":"23", "name": ["Tom Smith","Michael Bird"]}]

i looked around, but i don't find a solution for that. i checked this:

for(var i = 0; i < json.length; i++) {
    var obj = json[i];
    console.log(obj.id);
}

but didn't match. Who can help?

attachment 1: my code in the node_helper.js:

    start() {
    console.log("Starting module helper: " + this.name);
    //  console.log("Pfad zur csv-Datei: " + this.path + "/data/birthdays.csv");

    const csvFilePath = this.path + '/data/birthdays.csv';
    csv()
    .fromFile(csvFilePath)
    .then((jsonObj)=>{      
        birthdaysArray = JSON.stringify(jsonObj);
        console.log("birthdaysArray: " + birthdaysArray);

    var result = Object.entries(birthdaysArray.reduce((a, {birth, name}) => {
        const day = +birth.split('.')[0];
        a[day] = [...(a[day] || []), name];
        return a
    }, {})).map(([day, name]) => ({day, name})).sort((a, b) => +a.day - b.day)
    console.log("sorted birthdays : " + result);
    })
}

=> output console.log:

  Unhandled rejection TypeError: birthdaysArray.reduce is not a function
at /home/dirk/MagicMirror/modules/perlchamp/node_helper.js:27:47
at Object.onfulfilled (/home/dirk/MagicMirror/node_modules/csvtojson/v2/Converter.js:112:33)
at Result.endProcess (/home/dirk/MagicMirror/node_modules/csvtojson/v2/Result.js:83:50)
at Converter.processEnd (/home/dirk/MagicMirror/node_modules/csvtojson/v2/Converter.js:179:21)
at /home/dirk/MagicMirror/node_modules/csvtojson/v2/Converter.js:172:19
at tryCatcher (/home/dirk/MagicMirror/node_modules/bluebird/js/release/util.js:16:23)
at Promise._settlePromiseFromHandler (/home/dirk/MagicMirror/node_modules/bluebird/js/release/promise.js:547:31)
at Promise._settlePromise (/home/dirk/MagicMirror/node_modules/bluebird/js/release/promise.js:604:18)
at Promise._settlePromise0 (/home/dirk/MagicMirror/node_modules/bluebird/js/release/promise.js:649:10)
at Promise._settlePromises (/home/dirk/MagicMirror/node_modules/bluebird/js/release/promise.js:729:18)
at _drainQueueStep (/home/dirk/MagicMirror/node_modules/bluebird/js/release/async.js:93:12)
at _drainQueue (/home/dirk/MagicMirror/node_modules/bluebird/js/release/async.js:86:9)
at Async._drainQueues (/home/dirk/MagicMirror/node_modules/bluebird/js/release/async.js:102:5)
at Immediate.Async.drainQueues [as _onImmediate] (/home/dirk/MagicMirror/node_modules/bluebird/js/release/async.js:15:14)
at processImmediate (internal/timers.js:439:21)

so, what can i do?

attachment 2: first I wanted to filter the string by month:

        var today_month = moment().format("MM")
        var monthList = [];

        for(let prop in jsonObj) {
            if (jsonObj[prop]["birth"].split(".")[1] == today_month) {
                //console.log("fifth: ", jsonObj[prop]);
                monthList += jsonObj[prop];
            }
        }
        console.log("monthList: ", monthList);

...and then apply the code (green hook, the second of them) to it. unfortunately does not work as I had imagined can you do this now in one go? So filter from the annual list of birthdays by the current month, and then display these entries as I mentioned above.

Now that you changed the expected result - it's a little cleaner

 var data = [{ "birth": "23.04.1988", "name": "Tom Smith" }, { "birth": "15.04.2010", "name": "Mini Jall" }, { "birth": "23.04.2001", "name": "Michael Bird" }, { "birth": "17.05.2001", "name": "May Gibbs" }, { "birth": "04.05.2001", "name": "May The Force be with you" }, { "birth": "17.05.2001", "name": "Jenny May" } ]; var currentMonth = new Date().toLocaleDateString('en', {month:'2-digit'}); var result = Object.entries(data.filter(({birth}) => birth.split('.')[1] === currentMonth).reduce((a, {birth, name}) => { const day = +birth.split('.')[0]; a[day] = [...(a[day] || []), name]; return a }, {}) ).map(([day, name]) => ({day, name})).sort((a, b) => +a.day - b.day); console.log(result);

The goal is to create an Array of Object s 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 (eg, "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, ie, 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 string s.

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 Object s.

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 Object s.

 [ { "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 Object s such that the String name is instead an Array of all name s 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 Object s 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));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM