Skip to content

Commit

Permalink
feat: Add decimal option to fromCSV.
Browse files Browse the repository at this point in the history
  • Loading branch information
jheer committed May 9, 2021
1 parent c81168e commit 0f56260
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 20 deletions.
9 changes: 8 additions & 1 deletion docs/api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,8 @@ This method performs parsing only. To both load and parse a CSV file, use [loadC
* *text*: A string in a delimited-value format.
* *options*: A CSV format options object:
* *delimiter*: A single-character delimiter string between column values.
* *delimiter*: A single-character delimiter string between column values (default `','`).
* *decimal*: A single-character numeric decimal separator (default `'.'`).
* *header*: Boolean flag (default `true`) to specify the presence of a header row. If `true`, indicates the CSV contains a header row with column names. If `false`, indicates the CSV does not contain a header row and the columns are given the names `'col1'`, `'col2'`, and so on.
* *autoType*: Boolean flag (default `true`) for automatic type inference.
* *autoMax*: Maximum number of initial rows (default `1000`) to use for type inference.
Expand All @@ -148,8 +149,14 @@ aq.fromCSV('a,b\n1,2\n3,4')
aq.fromCSV('a,b\n00152,2\n30219,4', { parse: { a: String } })
```
```js
// parse semi-colon delimited text with comma as decimal separator
aq.fromCSV('a;b\nu;-1,23\nv;3,45e5', { delimiter: ';', decimal: ',' })
```
```js
// create table from an input CSV loaded from 'url'
// alternatively, use the loadCSV method
aq.fromCSV(await fetch(url).then(res => res.text()))
```
Expand Down
10 changes: 7 additions & 3 deletions src/format/from-csv.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const DEFAULT_NAME = 'col';
* Options for CSV parsing.
* @typedef {object} CSVParseOptions
* @property {string} [delimiter=','] Single-character delimiter between values.
* @property {string} [decimal='.'] Single-character numeric decimal separator.
* @property {boolean} [header=true] Flag to specify presence of header row.
* If true, assumes the CSV contains a header row with column names.
* If false, indicates the CSV does not contain a header row, and the
Expand Down Expand Up @@ -61,7 +62,7 @@ export default function(text, options = {}) {
// initialize parsers
const parsers = options.autoType === false
? Array(n).fill(identity)
: getParsers(names, values, options.parse);
: getParsers(names, values, options);

// apply parsers
parsers.forEach((parse, i) => {
Expand All @@ -84,8 +85,11 @@ export default function(text, options = {}) {
return new ColumnTable(columns, names);
}

function getParsers(names, values, opt = {}) {
function getParsers(names, values, options) {
const { parse = {} } = options;
return names.map(
(name, i) => isFunction(opt[name]) ? opt[name] : valueParser(values[i])
(name, i) => isFunction(parse[name])
? parse[name]
: valueParser(values[i], options)
);
}
40 changes: 24 additions & 16 deletions src/util/parse-values.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,33 @@
import identity from './identity';
import isISODateString from './is-iso-date-string';

const TYPES = [
[ // boolean
v => (v === 'true') || (v === 'false'),
v => v === 'false' ? false : true
],
[ // number
v => v === 'NaN' || (v = +v) === v,
v => +v
],
[ // iso date
isISODateString,
v => new Date(Date.parse(v))
]
const parseBoolean = [ // boolean
v => (v === 'true') || (v === 'false'),
v => v === 'false' ? false : true
];

export default function(values) {
const n = TYPES.length;
const parseNumber = [ // number
v => v === 'NaN' || (v = +v) === v,
v => +v
];

const parseDate = [ // iso date
isISODateString,
v => new Date(Date.parse(v))
];

function numberParser(options) {
const { decimal } = options;
return decimal && decimal !== '.'
? parseNumber.map(f => s => f(s && s.replace(decimal, '.')))
: parseNumber;
}

export default function(values, options) {
const types = [parseBoolean, numberParser(options), parseDate];
const n = types.length;
for (let i = 0; i < n; ++i) {
const [test, parser] = TYPES[i];
const [test, parser] = types[i];
if (check(values, test)) {
return parser;
}
Expand Down
9 changes: 9 additions & 0 deletions test/format/from-csv-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,13 @@ tape('fromCSV parses delimited text with parse option', t => {
const d = { ...data(), str: ['aa', 'bb', 'cc'] };
tableEqual(t, table, d, 'csv parsed data with custom parse');
t.end();
});

tape('fromCSV parses delimited text with decimal option', t => {
tableEqual(t,
fromCSV('a;b\nu;-1,23\nv;4,56e5\nw;', { delimiter: ';', decimal: ',' }),
{ a: ['u', 'v', 'w'], b: [-1.23, 4.56e5, null] },
'csv parsed data with decimal option'
);
t.end();
});

0 comments on commit 0f56260

Please sign in to comment.