Wednesday, 26 April 2017

For loop -vs- Map

I got asked last week about getting the index of an object in an array which had the earliest date, so that this array:

let objArr = [
    {
        "date": "2011-08-12T20:17:46.384Z"
    }, {
        "date": "2012-08-12T20:17:46.384Z"
    }, {
        "date": "2013-08-12T20:17:46.384Z"
    }, {
        "date": "2014-08-12T20:17:46.384Z"
    }, {
        "date": "2010-08-12T20:17:46.384Z"
    }, {
        "date": "2009-08-12T20:17:46.384Z"
    }
];

Produces 5 as that element has the earliest day.

I pondered using map but then decided to go all old school and used a for loop like this:

let boringParse = i => parseInt(moment.utc(i.date).format("x"), 10);
let forLoopInt = objA => {
    let min, i, objALength = objA.length;
    for (let y = 0; y < objALength; y++) {
        let now = boringParse(objA[y])
        if (!min || now < min) {
            min = now, i = y;
        }
    }
    return i;
};

Once I got back to my desk I decided to talk it over with James and came up with this:

let boringParse = i => parseInt(moment.utc(i.date).format("x"), 10);
let mapOne = objA => {
    let datesArr = objA.map(boringParse);
    return datesArr.indexOf(Math.min(...datesArr));
};

Turns out that the for loop is the faster, but only just!

Saturday, 22 April 2017

WebSQL bulk insert

I've been playing with WebSQL of late (after all, why wouldn't you play with a deprecated standard?). And I'm loving it for inserting data via Papa Parse, the thing is, it was proving to be really, really slow... right up until I clocked that I was doing it wrong!

The thing is, I was iterating over my data and firing a transaction with an SQL payload for each row of the CSV file I was parsing where I should've been firing a transaction and then iterating over my data and firing off multiple SQL payloads within the single transaction.

This was my first effort:

for (let record of results.data) {
    mydb.transaction(
        t => {
            t.executeSql(`
                INSERT INTO
                  accounts (
                    Date,
                    Company,
                    Account,
                    Amount
                  )
                VALUES (
                  ?,
                  ?,
                  ?,
                  ?
                )`,
                [
                    record.Date,
                    record.Company,
                    record.Account,
                    record.Amount
                ],
                t => {
                    let query = "SELECT COUNT(*) AS total FROM accounts";
                    t.executeSql(
                        query,
                        null,
                        (t, result) => {
                            if (~~result.rows[0].total === tableMetaData.total) {
                                createTable();
                            }
                        },
                        null
                    );
                },
                null
            );
        }
    );
}

Pretty, eh? But it took silly amounts of time to execute and I was planning on inserting 250,000 records. It simply wasn't practical to do it that way as it took something like 20 seconds to insert 1000 rows:

RowsTime
100.218
1001.711
100021.111
500090.703

Changing the code to this:

mydb.transaction(t => {
    for (let record of results.data) {
            t.executeSql(`
                INSERT INTO
                  accounts (
                    Date,
                    Company,
                    Account,
                    Amount
                  )
                VALUES (
                  ?,
                  ?,
                  ?,
                  ?
                )`,
                [
                    record.Date,
                    record.Company,
                    record.Account,
                    record.Amount
                ],
                null,
                null
            );
        }
    },
    error => {
        console.log("error", error);
    },
    () => {
        createTable();
    }
);
Produced these results:
RowsTime
100.081
1000.058
10000.083
50000.223

Not only quicker but I've also halved the number of queries as I don't need to check that all the transactions have completed after each insert, sweet eh? After figuring this out I came across a number of posts where the same thing had been discovered by others. Including RICAL and Gavin Pickin. Just a shame it's deprecated.

Thursday, 13 April 2017

JavaScript and checking for 0

A friend and colleague who's new to the joys of JavaScript asked about checking for a value which is zero and I automatically suggested the old double tilde trick.

I worked up a JSFiddle to illustrate the concept:

let myArray = [
    0, "0", '0', [0],["0"],['0'], 
    1, "1", '1', [1],["1"],['1']
];

for (let value of myArray) {
    console.log(value, typeof value)
    console.info(
        "~~" + value + " === 0:",
        ~~value === 0);
}

But then I got to thinking about what else the value might be, like an object, string or an array of strings: {"name":"Fred"}, "Fred", 'Fred', [{"name":"Fred"},{"name":"Fred"}], ["Fred"], ['Fred'].

These, oddly, also equated to true when compared to 0. So I started thinking about checking the type and doing something interesting (complicated) but then I remembered the toString() method and all the issues I've had in the past with seeing [Object, Object] in pages I've been working on.

Perhaps I could use that and reverse the solution?

So instead of checking for 0 as a number I'd instead check for it as a string. This, then, is a solution that seems to work quite well:

let myArray = [
    0, "0", '0', [0], ["0"], ['0'], 
    1, "1", '1', [1], ["1"], ['1'], 
    {"name":"Fred"}, "Fred", 'Fred', [{"name":"Fred"},{"name":"Fred"}], ["Fred"], ['Fred']
];

for (let value of myArray) {
    console.log(value, typeof value)
    console.info(
        value + ".toString() === '0':", 
        value.toString() === '0');
}

Hope that helps someone, it's something I'll keep in mind in future!

Monday, 3 April 2017

Avoiding Uncaught TypeError: Cannot read property '*' of undefined in JavaScript

I've lost count of the number of times I've run into issues diving into a deep JSON structure only to be confronted with the error TypeError: Cannot read property '*' of undefined. It's one of my major bugbears, and I've spent so many hours staring at it that I've finally come up with a way of avoiding it, the thing is, it uses eval...

I'm pretty sure that this use isn't overly evil but I'm not 100% sure TBH

Let me illustrate my use case and solution. Given this JavaScript object:

let obj = {
    "one": 1,
    "two": 2,
    "three": {
        "threeOne": 31,
        "threeTwo": 32
    },
    "four": 4
};

We can interrogate it in all sorts of ways:

console.log("obj:", obj) // Object {one: 1, two: 2, three: Object, four: 4}
console.log("obj.three.threeOne === 31:", obj.three.threeOne === 31); // true
console.log("obj.three.threeTwo === 31:", obj.three.threeTwo === 31); // false
console.log("obj.three.threeThree === 31:", obj.three.threeThree === 33); // false
console.log("obj.five === 31:", obj.five === 31); // false
console.log("obj.five:", obj.five); // undefined

But if we start digging a little deeper we run into trouble:

console.log("obj.five.fiveOne === 51:", obj.five.fiveOne === 51); 
// EXCEPTION: Uncaught TypeError: Cannot read property 'fiveOne' of undefined
console.log("obj.five.fiveOne:", obj.five.fiveOne); 
// EXCEPTION: Uncaught TypeError: Cannot read property 'fiveOne' of undefined

Except that's not 100% accurate as the browser throws its hands up after the first error and the second won't be shown as all bets are off!

It's not the fact that we're asking about an undefined thing but that we're asking about a property of an undefined thing. Should this then mean that we should check to see if the parent element has such a property before interrogating it, like this?

if(obj.hasOwnProperty("five") && obj.five.hasOwnProperty("fiveOne") && obj.five.fiveOne === 51){
    console.log("obj.five.fiveOne === 51:", true); 
}else{
    console.log("obj.five.fiveOne === 51:", false);
} // false
if(obj.hasOwnProperty("five") && obj.five.hasOwnProperty("fiveOne") ){
    console.log("obj.five.fiveOne:", obj.five.fiveOne);
}else{
    console.log("obj.five.fiveOne:", undefined);
} // undefined

This works though, depending upon the depth of the object this can get really ugly really fast, perhaps we should take advantage of the errors and do something with them. This we can do using a try...catch::

try {
    console.log("obj.five.fiveOne === 51:", obj.five.fiveOne === 51);
} catch (ex) {
    console.log("obj.five.fiveOne === 51:", false);
} // false
try {
    console.log("obj.five.fiveOne:", obj.five.fiveOne);
} catch (ex) {
    console.log("obj.five.fiveOne:", undefined);
} // undefined

This works, and it works well, but it is almost as ugly! To tidy it up I started writing a simple function but soon ran into a problem where, if I passed something which didn't have the property then the call to the function failed and caused a similar error. Then I clocked I could use a string and the eval function within a try...catch to do the work, I also added in some default checking of the value for my particular use case, but this is not necessary really:

let testObject = (objPath, val) => {
    val = typeof val !== 'undefined' ? val : "Completed";
    var result = false;
    try {
        result = (eval(objPath) === val) ? true : false;
    } catch (e) {}
    return result;
};

The above testing can then be replaced with this:

console.log("testObject('obj.three.threeOne', 31):", testObject('obj.three.threeOne', 31)); // true
console.log("testObject('obj.three.threeTwo', 31):", testObject('obj.three.threeTwo', 31)); // false
console.log("testObject('obj.three.threeThree', 31):", testObject('obj.three.threeThree', 31)); // false
console.log("testObject('obj.five', 31):", testObject('obj.five', 31)); // false
console.log("testObject('obj.five.fiveOne', 51):", testObject('obj.five.fiveOne', 51)); // false
console.log("testObject('obj.five.fiveOne'):", testObject('obj.five.fiveOne')); // false

This is already helping me; I hope you might find it useful as well. Here's a working JSFiddle with the code. I've made use of the marvellous console-log-div to output the results to a div. Otherwise, you'd need to open the DevTools on the JSFiddle to see the results. Hope it helps someone, and if you have any comments, please do chip in below.

Monday, 20 March 2017

Getting unique values from an array of objects in Javascript

In my previous post I looked at using JSFiddle to process data from a MySQL database into DynamoDB. One of the things I fumbled with was extracting the unique dates from the array of objects:

let attendance = [{
    "date": "2015-01-07",
    "username": "Others",
    "name": 8,
    "dues": null
}, {
    "date": "2015-01-14",
    "username": "Others",
    "name": 10,
    "dues": null
}, {
    "date": "2015-01-21",
    "username": "Others",
    "name": 9,
    "dues": null
}, {
    "date": "2015-01-28",
    "username": "Others",
    "name": 8,
    "dues": null
}, {
    "date": "2015-02-04",
    "username": "Others",
    "name": 8,
    "dues": null
}, {
    "date": "2015-12-09",
    "username": "Others",
    "name": 9,
    "dues": null
}, {
    "date": "2015-12-16",
    "username": "Others",
    "name": 9,
    "dues": null
}, {
    "date": "2015-12-23",
    "username": "Others",
    "name": 8,
    "dues": null
}, {
    "date": "2015-12-30",
    "username": "Others",
    "name": 7,
    "dues": null
}, {
    "date": "2016-01-13",
    "username": "user1@example.com",
    "name": "Dominic",
    "dues": 1
}, {
    "date": "2016-01-13",
    "username": "user2@example.com",
    "name": "Derek",
    "dues": 1
}, {
    "date": "2016-01-13",
    "username": "user3@example.com",
    "name": "Gemma",
    "dues": 1
}, {
    "date": "2016-01-13",
    "username": "user5@example.com",
    "name": "Chris",
    "dues": 1
}, {
    "date": "2016-01-13",
    "username": "user6+user2@example.com",
    "name": "Jack",
    "dues": 1
}, {
    "date": "2016-01-13",
    "username": "user4@example.com",
    "name": "James",
    "dues": 1
}, {
    "date": "2016-01-13",
    "username": "user7@example.com",
    "name": "Peter",
    "dues": 1
}, {
    "date": "2016-01-13",
    "username": "user8@example.com",
    "name": "Mariusz",
    "dues": 1
}, {
    "date": "2016-01-13",
    "username": "user9@example.com",
    "name": "Patrick",
    "dues": 1
}, {
    "date": "2016-01-13",
    "username": "user11@example.com",
    "name": "Russell",
    "dues": 1
}, {
    "date": "2016-01-13",
    "username": "user10@example.com",
    "name": "Sarah",
    "dues": 1
}, {
    "date": "2016-01-17",
    "username": "user1@example.com",
    "name": "Dominic",
    "dues": 1
}, {
    "date": "2016-01-17",
    "username": "user2@example.com",
    "name": "Derek",
    "dues": 0
}, {
    "date": "2016-01-17",
    "username": "user3@example.com",
    "name": "Gemma",
    "dues": 0
}, {
    "date": "2016-01-17",
    "username": "user5@example.com",
    "name": "Chris",
    "dues": 0
}, {
    "date": "2016-01-17",
    "username": "user6+user2@example.com",
    "name": "Jack",
    "dues": 0
}, {
    "date": "2016-01-17",
    "username": "user7@example.com",
    "name": "Peter",
    "dues": 0
}, {
    "date": "2016-01-19",
    "username": "user1@example.com",
    "name": "Dominic",
    "dues": 0
}, {
    "date": "2016-01-19",
    "username": "user2@example.com",
    "name": "Derek",
    "dues": 0
}, {
    "date": "2016-01-19",
    "username": "user3@example.com",
    "name": "Gemma",
    "dues": 0
}, {
    "date": "2016-01-19",
    "username": "user4@example.com",
    "name": "James",
    "dues": 0
}];

While I was looking at this process I came across this answer on Stack Overflow:

const dates = [...new Set(attendance.map(item => item.date))];

It's brilliant! So brilliant that I'm putting it here so I don't forget (working JSFiddle)!

Tuesday, 14 March 2017

Scratching an itch with JSFiddle

I'm presently moving the Witchford Archers site away from the LAMP stack towards a serverless architecture on AWS and so I needed to move the attendance records from MySQL to DynamoDB. Presently they look like this:

CREATE TABLE IF NOT EXISTS `attendance` (
  `date` date NOT NULL,
  `username` varchar(255) NOT NULL DEFAULT '0',
  `name` varchar(255) DEFAULT NULL,
  `dues` tinyint(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

--
-- Dumping data for table `attendance`
--

INSERT INTO `attendance` (`date`, `username`, `name`, `dues`) VALUES
('2015-01-07', 'Others', '8', NULL),
('2015-01-14', 'Others', '10', NULL),
('2015-01-21', 'Others', '9', NULL),
('2015-01-28', 'Others', '8', NULL),
('2015-02-04', 'Others', '8', NULL),
('2015-12-09', 'Others', '9', NULL),
('2015-12-16', 'Others', '9', NULL),
('2015-12-23', 'Others', '8', NULL),
('2015-12-30', 'Others', '7', NULL),
('2016-01-13', 'user1@example.com', 'Dominic', 1),
('2016-01-13', 'user2@example.com', 'Derek', 1),
('2016-01-13', 'user3@example.com', 'Gemma', 1),
('2016-01-13', 'user5@example.com', 'Chris', 1),
('2016-01-13', 'user6+user2@example.com', 'Jack', 1),
('2016-01-13', 'user4@example.com', 'James', 1),
('2016-01-13', 'user7@example.com', 'Peter', 1),
('2016-01-13', 'user8@example.com', 'Mariusz', 1),
('2016-01-13', 'user9@example.com', 'Patrick', 1),
('2016-01-13', 'user11@example.com', 'Russell', 1),
('2016-01-13', 'user10@example.com', 'Sarah', 1),
('2016-01-17', 'user1@example.com', 'Dominic', 1),
('2016-01-17', 'user2@example.com', 'Derek', 0),
('2016-01-17', 'user3@example.com', 'Gemma', 0),
('2016-01-17', 'user5@example.com', 'Chris', 0),
('2016-01-17', 'user6+user2@example.com', 'Jack', 0),
('2016-01-17', 'user7@example.com', 'Peter', 0),
('2016-01-19', 'user1@example.com', 'Dominic', 0),
('2016-01-19', 'user2@example.com', 'Derek', 0),
('2016-01-19', 'user3@example.com', 'Gemma', 0),
('2016-01-19', 'user4@example.com', 'James', 0);

Using a Text Editor and a few Macros I converted this into a CSV file:

date,username,name,dues
2015-01-07,Others,8,NULL
2015-01-14,Others,10,NULL
2015-01-21,Others,9,NULL
2015-01-28,Others,8,NULL
2015-02-04,Others,8,NULL
2015-12-09,Others,9,NULL
2015-12-16,Others,9,NULL
2015-12-23,Others,8,NULL
2015-12-30,Others,7,NULL
2016-01-13,user1@example.com,Dominic,1
2016-01-13,user2@example.com,Derek,1
2016-01-13,user3@example.com,Gemma,1
2016-01-13,user5@example.com,Chris,1
2016-01-13,user6+user2@example.com,Jack,1
2016-01-13,user4@example.com,James,1
2016-01-13,user7@example.com,Peter,1
2016-01-13,user8@example.com,Mariusz,1
2016-01-13,user9@example.com,Patrick,1
2016-01-13,user11@example.com,Russell,1
2016-01-13,user10@example.com,Sarah,1
2016-01-17,user1@example.com,Dominic,1
2016-01-17,user2@example.com,Derek,0
2016-01-17,user3@example.com,Gemma,0
2016-01-17,user5@example.com,Chris,0
2016-01-17,user6+user2@example.com,Jack,0
2016-01-17,user7@example.com,Peter,0
2016-01-19,user1@example.com,Dominic,0
2016-01-19,user2@example.com,Derek,0
2016-01-19,user3@example.com,Gemma,0
2016-01-19,user4@example.com,James,0

With this I used Papa Parse and a few other scripts/plugins to create the data I needed for DynamoDB. I guess the main issue I wanted to raise was the use of JSFiddle. This is the final result:

[
    {
        "Date": "2015-01-07",
        "Attendance": [
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            }
        ]
    },
    {
        "Date": "2015-01-14",
        "Attendance": [
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            }
        ]
    },
    {
        "Date": "2015-01-21",
        "Attendance": [
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            }
        ]
    },
    {
        "Date": "2015-01-28",
        "Attendance": [
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            }
        ]
    },
    {
        "Date": "2015-02-04",
        "Attendance": [
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            }
        ]
    },
    {
        "Date": "2015-12-09",
        "Attendance": [
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            }
        ]
    },
    {
        "Date": "2015-12-16",
        "Attendance": [
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            }
        ]
    },
    {
        "Date": "2015-12-23",
        "Attendance": [
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            }
        ]
    },
    {
        "Date": "2015-12-30",
        "Attendance": [
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            },
            {
                "Name": "Unknown",
                "User": "Unknown",
                "Status": "Paid"
            }
        ]
    },
    {
        "Date": "2016-01-13",
        "Attendance": [
            {
                "Name": "Dominic",
                "User": "user1@example.com",
                "Status": "Paid"
            },
            {
                "Name": "Derek",
                "User": "user2@example.com",
                "Status": "Paid"
            },
            {
                "Name": "Gemma",
                "User": "user3@example.com",
                "Status": "Paid"
            },
            {
                "Name": "Chris",
                "User": "user5@example.com",
                "Status": "Paid"
            },
            {
                "Name": "Jack",
                "User": "user6+user2@example.com",
                "Status": "Paid"
            },
            {
                "Name": "James",
                "User": "user4@example.com",
                "Status": "Paid"
            },
            {
                "Name": "Peter",
                "User": "user7@example.com",
                "Status": "Paid"
            },
            {
                "Name": "Mariusz",
                "User": "user8@example.com",
                "Status": "Paid"
            },
            {
                "Name": "Patrick",
                "User": "user9@example.com",
                "Status": "Paid"
            },
            {
                "Name": "Russell",
                "User": "user11@example.com",
                "Status": "Paid"
            },
            {
                "Name": "Sarah",
                "User": "user10@example.com",
                "Status": "Paid"
            }
        ]
    },
    {
        "Date": "2016-01-17",
        "Attendance": [
            {
                "Name": "Dominic",
                "User": "user1@example.com",
                "Status": "Paid"
            },
            {
                "Name": "Derek",
                "User": "user2@example.com",
                "Status": "Unpaid"
            },
            {
                "Name": "Gemma",
                "User": "user3@example.com",
                "Status": "Unpaid"
            },
            {
                "Name": "Chris",
                "User": "user5@example.com",
                "Status": "Unpaid"
            },
            {
                "Name": "Jack",
                "User": "user6+user2@example.com",
                "Status": "Unpaid"
            },
            {
                "Name": "Peter",
                "User": "user7@example.com",
                "Status": "Unpaid"
            }
        ]
    },
    {
        "Date": "2016-01-19",
        "Attendance": [
            {
                "Name": "Dominic",
                "User": "user1@example.com",
                "Status": "Unpaid"
            },
            {
                "Name": "Derek",
                "User": "user2@example.com",
                "Status": "Unpaid"
            },
            {
                "Name": "Gemma",
                "User": "user3@example.com",
                "Status": "Unpaid"
            },
            {
                "Name": "James",
                "User": "user4@example.com",
                "Status": "Unpaid"
            }
        ]
    }
]

JSFiddle was created by Piotr Zalewa and it is described by him thus:

JsFiddle is a playground for web developers, a tool which may be used in many ways. One can use it as an online editor for snippets build from HTML, CSS and JavaScript.

The code can then be shared with others, embedded on a blog, etc. Using this approach, JavaScript developers can very easily isolate bugs. We aim to support all actively developed frameworks - it helps with testing compatibility.

I love it! I use it ever-so-much for all sorts of things, not least because it's a quick way of getting a proof of concept up and running as quickly as possible. I use all sorts of IDEs and Text Editor in my work but JSFiddle, as well as Chrome's developer console, is where I do most of my work.

Anyway, the rather over-engineered code which produced the JSON above is this:

let syntaxHighlight = (json) => json
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, (match) => {
        var cls = 'number';
        if (/^"/.test(match)) {
            if (/:$/.test(match)) {
                cls = 'key';
            } else {
                cls = 'string';
            }
        } else if (/true|false/.test(match)) {
            cls = 'boolean';
        } else if (/null/.test(match)) {
            cls = 'null';
        }
        return '<span class="' + cls + '">' + match + '</span>';
    });

$("form").on("click", ".fileinput-upload", (e) => {
    e.preventDefault();
    if (window.File && window.FileReader && window.FileList && window.Blob) {
        var files = document.getElementById("input").files;
        if (files[0].type === "application/vnd.ms-excel") {
            data = [];
            Papa.parse(files[0], {
                "download": true,
                "header": true,
                "dynamicTyping": true,
                "complete": results => {
                    let data = [];
                    let Status = ["Unpaid", "Paid", "Subscriber"];
                    results.data.forEach((element) => {
                        let index = data.findIndex(e => e.Date === element.date);
                        if (index === -1) {
                            if (element.username === "Others") {
                                let Attendance = [];
                                for (let i = 0; i < element.name; i++) {
                                    Attendance.push({
                                        "Name": "Unknown",
                                        "User": "Unknown",
                                        "Status": Status[1]
                                    });
                                }
                                data.push({
                                    "Date": element.date,
                                    "Attendance": Attendance
                                });
                            } else {
                                data.push({
                                    "Date": element.date,
                                    "Attendance": [{
                                        "Name": element.name,
                                        "User": element.username,
                                        "Status": Status[element.dues]
                                    }]
                                });
                            }
                        } else {
                            if (element.username === "Others") {
                                for (let i = 0; i < element.name; i++) {
                                    data[index].Attendance.push({
                                        "Name": "Unknown",
                                        "User": "Unknown",
                                        "Status": Status[1]
                                    });
                                }
                            } else {
                                data[index].Attendance.push({
                                    "Name": element.name,
                                    "User": element.username,
                                    "Status": Status[element.dues]
                                });
                            }
                        }
                    });
                    $("#output").html(syntaxHighlight(JSON.stringify(data, undefined, 4))).show();
                }
            });
        } else {
            toastr["error"]("Please only upload CSV format files.", "Naughty!");
        }
    } else {
        toastr["warning"]("The File APIs are not fully supported in this browser.", "Oops!");
    }
});

I'm using jQuery, BootStrap, Toastr, Papa Parse, Bootstrap File Input and I borrowed user123444555621's answer on Stack Overflow. Many of the resources were grabbed from cdnjs.

But I guess the main thing is that I managed to get all this done really quickly and relatively painlessly thanks to the wealth of resources out there, often available for free! I can spin up a simple application to process data just in my browser rather than setting up a server and pulling in external resources, pulling in Frameworks and libraries as I like. This isn't without some problems though and I guess the skill lies in knowing which things work well with others.