convert CSV lines into Javascript objects - javascript

I have a simple csv file
people.csv:
fname, lname, uid, phone, address
John, Doe, 1, 444-555-6666, 34 dead rd
Jane, Doe, 2, 555-444-7777, 24 dead rd
Jimmy, James, 3, 111-222-3333, 60 alive way
What I want to do it get each line of the CSV, convert it to a JavaScript object, store them into an array, and then convert the array into a JSON object.
server.js:
var http = require('http');
var url = require('url');
var fs = require('fs');
var args = process.argv;
var type = args[2] || 'text';
var arr = [];
var bufferString;
function csvHandler(req, res){
fs.readFile('people.csv',function (err,data) {
if (err) {
return console.log(err);
}
//Convert and store csv information into a buffer.
bufferString = data.toString();
//Store information for each individual person in an array index. Split it by every newline in the csv file.
arr = bufferString.split('\n');
console.log(arr);
for (i = 0; i < arr.length; i++) {
JSON.stringify(arr[i]);
}
JSON.parse(arr);
res.send(arr);
});
}
//More code ommitted
My question is if I am actually converting that CSV lines into Javascript objects when I call the .split('\n') method on bufferString or is there another way of doing so?

By doing this:
arr = bufferString.split('\n');
you will have an array containing all rows as string
["fname, lname, uid, phone, address","John, Doe, 1, 444-555-6666, 34 dead rd",...]
You have to break it again by comma using .split(','), then separate the headers and push it into an Javascript Object:
var jsonObj = [];
var headers = arr[0].split(',');
for(var i = 1; i < arr.length; i++) {
var data = arr[i].split(',');
var obj = {};
for(var j = 0; j < data.length; j++) {
obj[headers[j].trim()] = data[j].trim();
}
jsonObj.push(obj);
}
JSON.stringify(jsonObj);
Then you will have an object like this:
[{"fname":"John",
"lname":"Doe",
"uid":"1",
"phone":"444-555-6666",
"address":"34 dead rd"
}, ... }]
See this FIDDLE

Using ES6/ES7 and some functional programming guidelines:
All variables are const (immutability)
Use map/reduce instead of while/for
All functions are Arrow
No dependencies
// Split data into lines and separate headers from actual data
// using Array spread operator
const [headerLine, ...lines] = data.split('\n');
// Split headers line into an array
// `valueSeparator` may come from some kind of argument
// You may want to transform header strings into something more
// usable, like `camelCase` or `lowercase-space-to-dash`
const valueSeparator = '\t';
const headers = headerLine.split(valueSeparator);
// Create objects from parsing lines
// There will be as much objects as lines
const objects = lines
.map( (line, index) =>
line
// Split line with value separators
.split(valueSeparator)
// Reduce values array into an object like: { [header]: value }
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
.reduce(
// Reducer callback
(object, value, index) => ({
...object,
[ headers[index] ]: value,
}),
// Initial value (empty JS object)
{}
)
);
console.log("Objects:", objects);
For CSV files using , as separator and quotes string values, you can use this version:
// Split data into lines and separate headers from actual data
// using Array spread operator
const [headerLine, ...lines] = data.split('\n');
// Use common line separator, which parses each line as the contents of a JSON array
const parseLine = (line) => JSON.parse(`[${line}]`);
// Split headers line into an array
const headers = parseLine(headerLine);
// Create objects from parsing lines
// There will be as much objects as lines
const objects = lines
.map( (line, index) =>
// Split line with JSON
parseLine(line)
// Reduce values array into an object like: { [header]: value }
.reduce(
(object, value, index) => ({
...object,
[ headers[index] ]: value,
}),
{}
)
);
return objects;
Note: For big files, it would be better to work with streams, generators, iterators, etc.

You could try using MVC Razor,
<script type="text/javascript">
MyNamespace.myConfig = #Html.Raw(Json.Encode(new MyConfigObject()));
</script>
The Json.Encode will serialize the initialized object to JSON format. Then the Html.Raw will prevent it from encoding the quotes to ".
Here the entire example

You can use lodash (or underscore) to help with this.
var objects = _.map(arr, function(item){return item.split(',');});
var headers = objects[0];
objects.splice(0, 1); // remove the header line
populatedObject = [];
objects.forEach(function(item){
var obj = _.zipObject(headers, item);
populatedObject.push(obj);
});
The .zipObject method will match each header to each value in the items array and produce an object.

Here is a solution if you already have an array and want that the csv header (first line) to be the object's property.
const csvArrayToObj = (csvData) => {
return csvData
.map((csvLine, csvIndex) => {
if (csvIndex === 0 || !csvLine.length) return null; // skip header and empty lines
return csvLine.reduce((a, v, i) => ({ ...a, [csvData[0][i]]: v }), {});
})
.filter((filter) => !!filter); //filter empty lines
};
const csvArray = [
['name', 'age'],
['John Doe', 20],
['Jane Doe', 30],
];
csvArrayToObj(csvArray);
// output
[
{
"name": "John Doe",
"age": 20
},
{
"name": "Jane Doe",
"age": 30
}
]

Related

Convert JSON to Array of Objects using lodash

I have a JSON object in NoSql database in this format. We are getting this data after migrating some records from some other database and these are multi-valued fields.(Basically we are trying to clean the data for further processing).
{
"BPContName":"aName;bName;cName",
"BPContEmail":"aEmail;bEmail;cEmail",
"BPContPWID":"aPWID;bPWID;cPWID"
}
I want to add another key "bpTableDataName" in the same JSON which should have this format and values,
"bpTableDataName": [
{
"name": "aName",
"email": "aEmail",
"pwdid": "aPWID"
},
{
"name": "bName",
"email": "bEmail",
"pwdid": "bPWID"
},
{
"name": "cName",
"email": "cEmail",
"pwdid": "cPWID"
}
],
Is there a way we can achieve this using lodash?
Try following code -
o = {
"BPContName": "aName;bName;cName",
"BPContEmail": "aEmail;bEmail;cEmail",
"BPContPWID": "aPWID;bPWID;cPWID"
}
map = { "BPContName" : "name", "BPContEmail": "email", "BPContPWID": "pwdid" }
const result = _.reduce(o, (arr, v, k) => ( v.split(";").forEach((x,i) => _.set(arr, `${i}.${map[k]}`, x)), arr ), [])
console.log(result)
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.11/lodash.min.js"></script>
You can use split() to split the values into an array.
Then iterate over the array and create the require json and then push that into results.
Check this out.
var data = {
"BPContName":"aName;bName;cName",
"BPContEmail":"aEmail;bEmail;cEmail",
"BPContPWID":"aPWID;bPWID;cPWID"
}
var names = data.BPContName.split(';');
var emails = data.BPContEmail.split(';');
var pwids = data.BPContPWID.split(';');
var results = [];
for(var i = 0 ; i < names.length; i++) {
var obj = {
name: names[i],
email: emails[i],
pwdid: pwids[i]
}
results.push(obj);
}
console.log(results)
You could reduce the entries returned by Object.entries like this:
let obj = {
"BPContName": "aName;bName;cName",
"BPContEmail": "aEmail;bEmail;cEmail",
"BPContPWID": "aPWID;bPWID;cPWID"
}
let bpTableDataName = Object.entries(obj).reduce((r, [key, value]) => {
let splits = value.split(";");
key = key.replace("BPCont", "").toLowerCase();
splits.forEach((split, i) => (r[i] = r[i] || {})[key] = split)
return r;
}, [])
obj.bpTableDataName = bpTableDataName;
console.log(obj)
Object.entries returns an array of key-value pair. Loop through each of them
split the each value at ;
get the key by removing BPCont part and making it lowerCase
Loop through the splits and update specific keys of objects at each index
Update:
Since you have an extra d in the output's key, you can create a mapping object:
propertyMap = {
"BPContName": "name",
"BPContEmail": "email",
"BPContPWID": "pwdid"
}
And inside the reduce, change the replace code to this:
key = propertyMap[key]
Using Object.assign, Object.entries, Array#map and the spread operator make this trivial
const inputdata = {
"BPContName":"aName;bName;cName",
"BPContEmail":"aEmail;bEmail;cEmail",
"BPContPWID":"aPWID;bPWID;cPWID"
};
const t1=Object.assign({},...Object.entries(inputdata).map(([k,v])=>({[k]:v.split(';')})));
inputdata.bpTableDataName=t1.BPContName.map((name,i)=>({name,email:t1.BPContEmail[i],pwdid:t1.BPContPWID[i]}));
console.log(inputdata);
Of course, it wouldn't be me without a one-liner
const obj = {
"BPContName":"aName;bName;cName",
"BPContEmail":"aEmail;bEmail;cEmail",
"BPContPWID":"aPWID;bPWID;cPWID"
};
// one line to rule them all
obj.bpTableDataName=Object.entries(obj).reduce((r,[k,v])=>(v.split(';').forEach((v,i)=>(r[i]=r[i]||{})[{BPContName:'name',BPContEmail:'email',BPContPWID:'pwdid'}[k]]=v),r),[]);
//
console.log(obj);
Basically what you need is to zip it.
Snippet:
let obj = {"BPContName":"aName;bName;cName","BPContEmail":"aEmail;bEmail;cEmail","BPContPWID":"aPWID;bPWID;cPWID"},
res = _.zipWith(
..._.map(obj, v => v.split(';')),
(name, email, pwid) => ({name, email, pwid})
);
console.log(res)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
Note, the sequence of the parameters we have to put such a way, the original object give us values when using Object.values or giving us keys when using Object.keys usually it is alphabetical order. But, In case in any env the order is not guranted we can sort it with a sequence of keys as a metadata.
Or else you can explicitly pass the arguments like:
(obj.BPContName.split(';'), obj.BPContEmail.split(';'), obj.BPContPWID.split(';'))
You can use lodash's _.flow() to create a function. Use _.map() with _.overArgs() to create a function that splits the values, format the key, and then converts them to an array of pairs using _.unzip(), for example [['name', 'x'], ['name', 'y']]. Transpose the array of arrays with _.unzip() to combine pairs of different properties. Then use _.map() to iterate, and convert each array of pairs to an object using _.fromPairs().
const { flow, partialRight: pr, map, unzip, overArgs, times, size, constant, split, fromPairs } = _
const keysMap = new Map([['BPContName', 'name'], ['BPContEmail', 'email'], ['BPContPWID', 'pwdid']])
const formatKey = key => keysMap.get(key)
const splitVals = pr(split, ';')
const fn = flow(
pr(map, overArgs(
(vals, k) => unzip([vals, times(size(vals), constant(k))]),
[splitVals, formatKey])
),
unzip, // transpose
pr(map, fromPairs) // convert each pairs array to object
)
const data = {
"BPContName":"aName;bName;cName",
"BPContEmail":"aEmail;bEmail;cEmail",
"BPContPWID":"aPWID;bPWID;cPWID"
}
const results = fn(data)
console.log(results)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>

How to group multiple sets of duplicate integers in an array into their own array of arrays?

I am trying to split an array of integers into an array of arrays by duplicate values. The original array is composed of a list of 6 digit integers, some of these integers come in pairs, others come in groups of 3 or 4s. I'd like to get these duplicates pushed to their own arrays and have all of these arrays of duplicates composed into an array of arrays that I can later loop through.
I've looked on in the lodash library for some method or combination of but can't quite find anything that seems to work. I've also tried a few different configurations with nested for loops but also am struggling with that.
const directory = "X/";
let files = fs.readdirSync(directory);
let first6Array = [ ];
for(i=0; i< files.length; i++){
let first6 = files[i].substring(0, 6);
first6Array.push(first6);
};
console.log(first6Array);
example output of first6Array:
[ '141848',
'141848',
'141848',
'142851',
'142851',
'143275',
'143275']
I'd like to end up with something like
let MasterArray = [[141848,141848,141848],[142851,142851],[143275,143275]];
You can use new Set() to filter out the duplicates.
Then you use the unique Array and filter for every value.
const firstArray = [ '141848', '141848', '141848', '142851', '142851', '143275', '143275'];
const numberArray = firstArray.map(Number);
const masterArray = [];
const unique = new Set (numberArray); // Set {141848, 142851, 143275}
unique.forEach(u => {
masterArray.push(numberArray.filter(e => e === u));
});
console.log(masterArray);
Using lodash, you can create a function with flow:
map the items by truncating them and converting to numbers.
groupBy the value (the default).
convert to an array of arrays using values.
const { flow, partialRight: pr, map, truncate, groupBy, values } = _;
const truncate6 = s => truncate(s, { length: 6, omission: '' });
const fn = flow(
pr(map, flow(truncate6, Number)),
groupBy,
values,
);
const firstArray = [ '141848abc', '141848efg', '141848hij', '142851klm', '142851opq', '143275rst', '143275uvw'];
const result = fn(firstArray);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
Use reduce to create an object of arrays, indexed by number, and push to the associated array on each iteration (creating the array at the key first if needed), then get the values of the object:
const directory = "X/";
const files = fs.readdirSync(directory);
const output = Object.values(
files.reduce((a, file) => {
const num = Number(file.slice(0, 6));
if (!a[num]) a[num] = [];
a[num].push(num);
return a;
}, {})
);
It's pretty weird to have an array of identical values, though - you might consider a different data structure like
{
'141848': 3,
'142851': 2
}
to keep track of the number of occurrences of each number:
const output = files.reduce((a, file) => {
const num = file.slice(0, 6);
a[num] = (a[num] || 0) + 1;
return a;
}, {})
To obtain exactly the result you desire, you need a nested find, something like this should works:
const directory = "X/";
let files = fs.readdirSync(directory);
let first6Array = files.reduce((acc, value)=> {
let n = +value.substr(0, 6); // assumes it can't be NaN
let arr = acc.find(nested => nested.find(item => item === n));
if (arr) {
arr.push(n);
} else {
acc.push([n]);
}
return acc;
}, []);
console.log(first6Array);
Notice that an hashmap instead, with the value and the number of occurrence, would be better, also in term of performance, but I don't think it mind since you have really few elements.
Also, it assumes the first six characters are actually numbers, otherwise the conversion would fail and you'll get NaN.
It would be safer adding a check to skip this scenario:
let n = +value.substr(0, 6);
if (isNaN(n)) {
return acc;
}
// etc

JSONata (or JS) - group and sum JSON array / objects

I want to 'groupsum' an array of objects with JSONata, from the following array:
payload = [
{"id":"Irr-1","name":"Irrigation","timedif_s":7.743},
{"id":"Irr-2","name":"Irrigation","timedif_s":2.749},
{"id":"Lig-2","name":"Lights","timedif_s":1.475},
{"id":"Irr-1","name":"Irrigation""timedif_s":1.07},]
The results must look like:
[{"name":"Irrigation", "sumtimedif": 11.562},
{"name":"Lig-1", "sumtimedif": 1.475}]
I have tried:
payload.name{"name":payload.name,"sumtimedif":$sum(timedif_s)}
But I only get back an empty string.
{ empty }
Any advise?
The following JSONata expression will do this:
payload{
name: $sum(timedif_s) /* group by name */
} ~> $each(function($v, $k) {
{"name": $k, "sumtimedif": $v} /* transform each key/value pair */
})
The first part of the expression does the grouping and aggregation, and the second part transforms each group (key/value pair) into the format you want. http://try.jsonata.org/ByxzyQ0x4
I found another good post that helped me: Most efficient method to groupby on a array of objects.
Not sure if this can be done in JSONata, so I reverted to JS, as follows:
var arr = msg.payload ;
arr1 = groupBy(arr, 'name', 'timedif_s' ) ;
msg.payload = arr1 ;
return msg;
//---------------------------------------------
function groupBy(array, col, value) {
var r = [], o = {};
array.forEach(function (a) {
if (!o[a[col]]) {
o[a[col]] = {};
o[a[col]][col] = a[col];
o[a[col]][value] = 0;
r.push(o[a[col]]);
}
o[a[col]][value] += +a[value];
});
return r;
}

JavaScript splitting a file content into key value pairs

I have the below UploadDocument event which has fileReader.result which logs below message to the console
Output in the Console
A,B
aa,dsf
adfa,dfsd
fdsafds,sdf
uploadDocument(file) {
let fileReader = new FileReader();
fileReader.onload =(e) => {
console.log(fileReader.result);
}
fileReader.readAsText(this.file);
How can I split the above content (A,B...) in the console into a key value pairs ( Like a hashmap or Arraylist ) using javascript or Typescript?
I have tried splitting it
var lines = fileReader.result.split(/[\r\n]+/g);
console.log(lines);
Now how can I create A hashmap from this?
The reduce() array method can be used to build objects over each element, so we just need to split the file into multiple lines and then reduce the array of lines into an object.
var map = fileReader.result.split('\n')
.reduce((obj, line) => {
var cols = line.split(',');
// Tolerate empty lines. There may be one at the end of the input.
if (cols.length >= 2) {
obj[cols[0]] = cols[1];
}
return obj;
}, {});
Sample fiddle
One way would be to use reduce which takes an initial value - in this case an new object - and populates its keys and values with the result of splitting each line in the file on the comma with each iteration of the function.
const input = 'A,B\n\
aa,dsf\n\
adfa,dfsd\n\
fdsafds,sdf'
const obj = input.split(/\n/g).reduce((p, c) => {
const s = c.split(',');
p[s[0]] = s[1];
return p;
}, {});
console.log(obj)

Build nested JSON from string of nested keys [duplicate]

This question already has answers here:
Convert a JavaScript string in dot notation into an object reference
(34 answers)
Closed 5 years ago.
I have csv files that I am reading using nodeJS. I convert each file to text before reading.
Each line in the file have data delimited with =.
Each line looks something like
data.location.degree.text=sometexthere
The first portion before the "=" represents an index to a JSON object in my app. My aim is to parse this data and build a JSON representation of it so that the line above becomes
data:{
location:{
degree:{
text: 'sometexthere'
}
}
}
Using javascript/nodejs; How can I convert a string which is supposed to represent a sequence of nested JSON keys, into a JSON object like above?
You could split the path and make a check if the following element exist. If not assign an object to the new property.
Return then the value of the property.
At the end assign the value.
function setValue(object, path, value) {
path = path.replace(/[\[]/gm, '.').replace(/[\]]/gm, ''); //to accept [index]
var keys = path.split('.'),
last = keys.pop();
keys.reduce(function (o, k) { return o[k] = o[k] || {}; }, object)[last] = value;
}
var data = {};
setValue(data, 'location.degree.text', 'sometexthere');
console.log(data);
// result container
var res = {};
// input data
var inp = [
'data.location.degree.text=sometexthere',
'data.otherLocation.degree.otherText=foo',
'data.location.degree.otherText=bar',
'we.can.handle.values.that.are_undefined=',
'we.can.handle.values.that.contain_equals_signs=yes=we=can'
];
// recursive function
var pathToObject = function(resultReference, path)
{
// split path on dots
// e.g. data.location.degree.text=sometexthere
// -> ["data", "location", "degree", "text=sometexthere"]
var splitPathParts = path.split('.');
// if there is only one part, we're at the end of our path expression
// e.g. ["text=sometexthere"]
if (splitPathParts.length === 1){
// split "text=sometexthere" into ["text", "sometexthere"]
var keyAndValue = splitPathParts[0].split('=');
// set foo = bar on our result object reference
resultReference[keyAndValue.shift()] = keyAndValue.join('=');
return;
}
// the first element of the split array is our current key
// e.g. for ["data", "location", "degree", "text=sometexthere"],
// the currentKey would be "data";
var currentKey = splitPathParts.shift();
// if our object does not yet contain the current key, set it to an empty object
resultReference[currentKey] || (resultReference[currentKey] = {});
// recursively call ourselves, passing in
// the nested scope and the rest of the path.
// e.g. { data : {} } and 'location.degree.text=sometexthere'
pathToObject(resultReference[currentKey], splitPathParts.join('.'));
}
for (var i = 0; i < inp.length; i++)
{
pathToObject(res, inp[i]);
}
console.log(res);
ES6 syntax makes things slightly more terse:
'use strict';
const pathToObject = (resultReference, path) => {
let [currentKey, ...restOfPath] = path.split('.');
if (restOfPath.length === 0) {
let [k, ...v] = currentKey.split('=');
resultReference[k] = v.join('=');
return;
}
resultReference[currentKey] || (resultReference[currentKey] = {});
pathToObject(resultReference[currentKey], restOfPath.join('.'));
}
let res = {};
[
'data.location.degree.text=sometexthere',
'data.otherLocation.degree.otherText=foo',
'data.location.degree.otherText=bar',
'we.can.handle.values.that.are_undefined=',
'we.can.handle.values.that.contain_equals_signs=yes=we=can'
].forEach(x => pathToObject(res, x));
console.log(res);

Categories