csv-to-ndjson 1.0.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -5
- package/index.js +117 -28
- package/package.json +11 -13
package/README.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
# csv-to-ndjson [](https://github.com/SimonJang/csv-to-ndjson/actions/workflows/ci.yml?query=branch%3Amaster+event%3Apush)
|
|
2
2
|
|
|
3
3
|
> Convert a CSV file to [ndjson](http://ndjson.org/) format stream or file.
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
## Install
|
|
7
7
|
|
|
8
|
+
Requires Node.js 26 or later.
|
|
9
|
+
|
|
8
10
|
```
|
|
9
11
|
$ npm install csv-to-ndjson
|
|
10
12
|
```
|
|
@@ -15,20 +17,35 @@ $ npm install csv-to-ndjson
|
|
|
15
17
|
```js
|
|
16
18
|
const csvToNdjson = require('csv-to-ndjson');
|
|
17
19
|
|
|
18
|
-
csvToNdjson('financialdata.csv', {
|
|
19
|
-
delimiter: ';'
|
|
20
|
+
const ndjsonStream = csvToNdjson('financialdata.csv', {
|
|
21
|
+
delimiter: ';',
|
|
20
22
|
header: ['Q1', 'Q2', 'Q3', 'Q4']
|
|
21
|
-
})
|
|
23
|
+
});
|
|
24
|
+
ndjsonStream.pipe(process.stdout);
|
|
22
25
|
// => Returns a readable stream of ndjson
|
|
23
26
|
|
|
24
27
|
csvToNdjson('financialdata.csv', {
|
|
25
28
|
delimiter: ';',
|
|
26
29
|
destination: 'financialdata.json',
|
|
27
30
|
header: ['Q1', 'Q2', 'Q3', 'Q4']
|
|
28
|
-
}).then(
|
|
31
|
+
}).then(() => {
|
|
32
|
+
// The destination file has been written.
|
|
33
|
+
});
|
|
29
34
|
// => Returns a promise when the file is written
|
|
30
35
|
```
|
|
31
36
|
|
|
37
|
+
Both the returned stream and destination file contain one JSON object per line.
|
|
38
|
+
Each record ends with LF (`\n`) on every platform, including Windows.
|
|
39
|
+
|
|
40
|
+
## Migrating to 2.0.0
|
|
41
|
+
|
|
42
|
+
- Node.js 26 or later is required. Earlier Node.js versions are no longer supported.
|
|
43
|
+
- On Windows, output now uses LF (`\n`) instead of CRLF (`\r\n`). Update consumers
|
|
44
|
+
that compare exact bytes or split records using the platform's line ending.
|
|
45
|
+
- CSV parsing errors now follow csv-parse 7. If you inspect error codes or messages,
|
|
46
|
+
update those checks; inconsistent field counts use
|
|
47
|
+
`CSV_RECORD_INCONSISTENT_FIELDS_LENGTH`.
|
|
48
|
+
|
|
32
49
|
## API
|
|
33
50
|
|
|
34
51
|
### csvToNdjson(input, [options])
|
package/index.js
CHANGED
|
@@ -1,13 +1,36 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
const
|
|
4
|
-
const csv = require('csv');
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
const Transform = require('node:stream').Transform;
|
|
5
4
|
const isValidPath = require('is-valid-path');
|
|
6
|
-
const
|
|
5
|
+
const {parse} = require('csv-parse');
|
|
7
6
|
|
|
8
|
-
const validateHeader = header =>
|
|
7
|
+
const validateHeader = header => {
|
|
8
|
+
for (let index = 0; index < header.length; index++) {
|
|
9
|
+
if (typeof header[index] !== 'string') {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return true;
|
|
15
|
+
};
|
|
9
16
|
const delimiters = [',', ';'];
|
|
10
|
-
const pattern =
|
|
17
|
+
const pattern = /\.csv$/i;
|
|
18
|
+
|
|
19
|
+
const teardown = streams => {
|
|
20
|
+
for (const stream of streams) {
|
|
21
|
+
if (typeof stream.unpipe === 'function') {
|
|
22
|
+
stream.unpipe();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
for (const stream of streams) {
|
|
27
|
+
if (typeof stream.destroy === 'function') {
|
|
28
|
+
stream.destroy();
|
|
29
|
+
} else if (typeof stream.end === 'function') {
|
|
30
|
+
stream.end();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
11
34
|
|
|
12
35
|
module.exports = (path, options) => {
|
|
13
36
|
let getHeader = false;
|
|
@@ -46,32 +69,98 @@ module.exports = (path, options) => {
|
|
|
46
69
|
}
|
|
47
70
|
}
|
|
48
71
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
72
|
+
const source = fs.createReadStream(path);
|
|
73
|
+
const parser = parse({
|
|
74
|
+
bom: true,
|
|
75
|
+
delimiter: options && options.delimiter ? options.delimiter : ',',
|
|
76
|
+
relax_column_count_less: true,
|
|
77
|
+
on_record(record, {error}) {
|
|
78
|
+
// Preserve the legacy parser's exception for a single empty field.
|
|
79
|
+
if (error && !(record.length === 1 && record[0] === '')) {
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
return record;
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
const stream = new Transform({objectMode: true});
|
|
86
|
+
stream._transform = (record, encoding, callback) => {
|
|
87
|
+
try {
|
|
88
|
+
const result = Object.create(null);
|
|
54
89
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
90
|
+
if (!getHeader) {
|
|
91
|
+
getHeader = true;
|
|
92
|
+
csvHeader = record.map(item => item.toLowerCase());
|
|
93
|
+
callback();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
for (const attribute of csvHeader) {
|
|
98
|
+
result[attribute] = record[csvHeader.indexOf(attribute)];
|
|
99
|
+
}
|
|
100
|
+
callback(null, JSON.stringify(result) + '\n');
|
|
101
|
+
} catch (error) {
|
|
102
|
+
callback(error);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
60
105
|
|
|
61
|
-
|
|
62
|
-
|
|
106
|
+
if (options && options.destination) {
|
|
107
|
+
const destination = fs.createWriteStream(options.destination);
|
|
108
|
+
const completion = new Promise((resolve, reject) => {
|
|
109
|
+
let settled = false;
|
|
110
|
+
const fail = error => {
|
|
111
|
+
if (!settled) {
|
|
112
|
+
settled = true;
|
|
113
|
+
teardown([source, parser, stream, destination]);
|
|
114
|
+
reject(error);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
const finish = () => {
|
|
118
|
+
if (!settled) {
|
|
119
|
+
settled = true;
|
|
120
|
+
resolve();
|
|
63
121
|
}
|
|
64
|
-
|
|
65
|
-
}));
|
|
122
|
+
};
|
|
66
123
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
124
|
+
source.on('error', fail);
|
|
125
|
+
parser.on('error', fail);
|
|
126
|
+
stream.on('error', fail);
|
|
127
|
+
destination.on('error', fail);
|
|
128
|
+
destination.on('finish', finish);
|
|
129
|
+
});
|
|
71
130
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
console.error(err);
|
|
75
|
-
throw new Error('Tranforming CSV to ndjson failed');
|
|
131
|
+
source.pipe(parser).pipe(stream).pipe(destination);
|
|
132
|
+
return completion;
|
|
76
133
|
}
|
|
134
|
+
|
|
135
|
+
let completed = false;
|
|
136
|
+
let failed = false;
|
|
137
|
+
const forwardFailure = error => {
|
|
138
|
+
if (!failed) {
|
|
139
|
+
failed = true;
|
|
140
|
+
teardown([source, parser, stream]);
|
|
141
|
+
stream.emit('error', error);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
const closeOnTransformFailure = () => {
|
|
145
|
+
if (!failed) {
|
|
146
|
+
failed = true;
|
|
147
|
+
teardown([source, parser, stream]);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
const closeUpstreamOnCancellation = () => {
|
|
151
|
+
if (!completed && !failed) {
|
|
152
|
+
failed = true;
|
|
153
|
+
teardown([source, parser]);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
source.on('error', forwardFailure);
|
|
158
|
+
parser.on('error', forwardFailure);
|
|
159
|
+
stream.on('end', () => {
|
|
160
|
+
completed = true;
|
|
161
|
+
});
|
|
162
|
+
stream.on('error', closeOnTransformFailure);
|
|
163
|
+
stream.on('close', closeUpstreamOnCancellation);
|
|
164
|
+
source.pipe(parser).pipe(stream);
|
|
165
|
+
return stream;
|
|
77
166
|
};
|
package/package.json
CHANGED
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "csv-to-ndjson",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Convert a CSV file to ndjson format stream",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"repository":
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/SimonJang/csv-to-ndjson.git"
|
|
9
|
+
},
|
|
10
|
+
"packageManager": "npm@12.0.1",
|
|
7
11
|
"author": {
|
|
8
12
|
"name": "Simon",
|
|
9
13
|
"email": "simon.jang.contact@gmail.com",
|
|
10
14
|
"url": "github.com/SimonJang"
|
|
11
15
|
},
|
|
12
16
|
"engines": {
|
|
13
|
-
"node": ">=
|
|
17
|
+
"node": ">=26"
|
|
14
18
|
},
|
|
15
19
|
"scripts": {
|
|
16
|
-
"test": "
|
|
20
|
+
"test": "node --test",
|
|
21
|
+
"prepublishOnly": "npm test"
|
|
17
22
|
},
|
|
18
23
|
"files": [
|
|
19
24
|
"index.js"
|
|
@@ -26,14 +31,7 @@
|
|
|
26
31
|
"file"
|
|
27
32
|
],
|
|
28
33
|
"dependencies": {
|
|
29
|
-
"csv": "^
|
|
30
|
-
"is-valid-path": "^0.1.1"
|
|
31
|
-
"pify": "^3.0.0"
|
|
32
|
-
},
|
|
33
|
-
"devDependencies": {
|
|
34
|
-
"ava": "^0.20.0",
|
|
35
|
-
"get-stream": "^3.0.0",
|
|
36
|
-
"tempy": "^0.1.0",
|
|
37
|
-
"xo": "^0.18.2"
|
|
34
|
+
"csv-parse": "^7.0.2",
|
|
35
|
+
"is-valid-path": "^0.1.1"
|
|
38
36
|
}
|
|
39
37
|
}
|