Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/super fields (for feature request #78) #79

Merged
merged 5 commits into from Oct 8, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
34 changes: 31 additions & 3 deletions README.md
Expand Up @@ -50,16 +50,44 @@ or [use it from the CLI](https://github.com/zemirco/json2csv#command-line-interf

- `options` - **Required**; Options hash.
- `data` - **Required**; Array of JSON objects.
- `fields` - Array of Strings, JSON attribute names to use as columns. Defaults to toplevel JSON attributes.
- `fields` - Array of Objects/Strings. Defaults to toplevel JSON attributes. See example below.
- `fieldNames` Array of Strings, names for the fields at the same indexes.
Must be the same length as `fields` array.
Must be the same length as `fields` array. (Optional. Maintained for backwards compatibility. Use `fields` config object for more features)
- `del` - String, delimiter of columns. Defaults to `,` if not specified.
- `defaultValue` - String, default value to use when missing data. Defaults to `` if not specified.
- `defaultValue` - String, default value to use when missing data. Defaults to `<empty>` if not specified. (Overridden by `fields[].default`)
- `quotes` - String, quotes around cell values and column names. Defaults to `"` if not specified.
- `eol` - String, it gets added to each row of data. Defaults to `` if not specified.
- `newLine` - String, overrides the default OS line ending (i.e. `\n` on Unix and `\r\n` on Windows).
- `callback` - **Required**; `function (error, csvString) {}`.

#### Example `fields` option
``` javascript
{
fields: [

// Supports label -> simple path
{
label: 'some label', // (optional, column will be labeled 'path.to.something' if not defined)
value: 'path.to.something', // data.path.to.something
default: 'NULL' // default if value is not found (optional, overrides `defaultValue` for column)
},

// Supports label -> derived value
{
label: 'some label', // Supports duplicate labels (required, else your column will be labeled [function])
value: function(row) {
return row.path1 + row.path2;
},
default: 'NULL' // default if value fn returns falsy
},

// Support pathname -> pathvalue
'simplepath' // equivalent to {value:'simplepath'}
'path.to.value' // also equivalent to {label:'path.to.value', value:'path.to.value'}
]
}
```

### Example 1

```javascript
Expand Down
40 changes: 24 additions & 16 deletions lib/json2csv.js
Expand Up @@ -40,30 +40,33 @@ module.exports = function (params, callback) {
* @param {Function} callback Callback function returning error when invalid field is found
*/
function checkParams(params, callback) {
params.data = params.data || [];

// if data is an Object, not in array [{}], then just create 1 item array.
// So from now all data in array of object format.
if (!Array.isArray(params.data)) {
var ar = [];
ar[0] = params.data;
params.data = ar;
params.data = [params.data];
}

if (!params.fields && params.data && params.data.length) {
var firstData = params.data[0];

if (typeof firstData === 'object' && firstData !== null) {
params.fields = Object.keys(firstData);
} else {
return callback(new Error('params should be a valid object.'));
}
// Set params.fields default to first data element's keys
if (!params.fields && (params.data.length === 0 || typeof params.data[0] !== 'object')) {
return callback(new Error('params should include "fields" and/or non-empty "data" array of objects'));
}
params.fields = params.fields || Object.keys(params.data[0]);


//#check fieldNames
if (params.fieldNames && params.fieldNames.length !== params.fields.length) {
return callback(new Error('fieldNames and fields should be of the same length, if fieldNames is provided.'));
}

params.fieldNames = params.fieldNames || params.fields;
// Get fieldNames from fields
params.fieldNames = params.fields.map(function (field, i) {
if (params.fieldNames && typeof field === 'string') {
return params.fieldNames[i];
}
return (typeof field === 'string') ? field : (field.label || field.value);
});

//#check delimiter
params.del = params.del || ',';
Expand All @@ -78,9 +81,7 @@ function checkParams(params, callback) {
params.defaultValue = params.defaultValue;

//#check hasCSVColumnTitle, if it is not explicitly set to false then true.
if (params.hasCSVColumnTitle !== false) {
params.hasCSVColumnTitle = true;
}
params.hasCSVColumnTitle = params.hasCSVColumnTitle !== false;

callback(null);
}
Expand Down Expand Up @@ -147,7 +148,14 @@ function createColumnContent(params, str) {
var eol = params.newLine || os.EOL || '\n';

params.fields.forEach(function (fieldElement) {
var val = lodashGet(dataElement, fieldElement, params.defaultValue);
var val;

if (fieldElement && (typeof fieldElement === 'string' || typeof fieldElement.value === 'string')) {
var path = (typeof fieldElement === 'string') ? fieldElement : fieldElement.value;
val = lodashGet(dataElement, path, fieldElement.default || params.defaultValue);
} else if (fieldElement && typeof fieldElement.value === 'function') {
val = fieldElement.value(dataElement) || fieldElement.default;
}

if (val !== undefined) {
var stringifiedElement = JSON.stringify(val);
Expand Down
3 changes: 3 additions & 0 deletions test/fixtures/csv/fancyfields.csv
@@ -0,0 +1,3 @@
"PATH1","PATH1+PATH2","NEST1","bird.nest2","nonexistent"
"hello ","hello world!","chirp","cheep","overrides default"
"good ","good bye!","meep","meep","col specific default value"
3 changes: 2 additions & 1 deletion test/helpers/load-fixtures.js
Expand Up @@ -17,7 +17,8 @@ var fixtures = [
'withSimpleQuotes',
'nested',
'defaultValue',
'embeddedjson'
'embeddedjson',
'fancyfields'
];

/*eslint-disable no-console*/
Expand Down
51 changes: 49 additions & 2 deletions test/index.js
Expand Up @@ -247,11 +247,11 @@ async.parallel(loadFixtures(csvFixtures), function (err) {

test('should error if params is not an object', function (t) {
json2csv({
data: 'none an object',
data: 'not an object',
field: ['carModel'],
fieldNames: ['test', 'blah']
}, function (error, csv) {
t.equal(error.message, 'params should be a valid object.');
t.equal(error.message, 'params should include "fields" and/or non-empty "data" array of objects');
t.notOk(csv);
t.end();
});
Expand All @@ -278,5 +278,52 @@ async.parallel(loadFixtures(csvFixtures), function (err) {
t.end();
});
});

test('should process fancy fields option', function (t) {
json2csv({
data: [{
path1: 'hello ',
path2: 'world!',
bird: {
nest1: 'chirp',
nest2: 'cheep'
},
fake: {
path: 'overrides default'
}
}, {
path1: 'good ',
path2: 'bye!',
bird: {
nest1: 'meep',
nest2: 'meep'
}
}],
fields: [{
label: 'PATH1',
value: 'path1'
}, {
label: 'PATH1+PATH2',
value: function (row) {
return row.path1+row.path2;
}
}, {
label: 'NEST1',
value: 'bird.nest1'
},
'bird.nest2',
{
label: 'nonexistent',
value: 'fake.path',
default: 'col specific default value'
}
],
defaultValue: 'NULL'
}, function (error, csv) {
t.error(error);
t.equal(csv, csvFixtures.fancyfields);
t.end();
});
});

});