Repository: mholt/PapaParse
Pull request #632 (2019-02-09 13:04:56)

Fixes issue #609
Introduces a new `unparse` option `columns` to specify the columns/keys manually.

Some points i'm not sure about:

- Default value is currently null 
I think `[]` is not a good choice because then we need to check `_columns.length > 0` in order to know if the user specified a custom value. If someone des a `array.map()` for the keys that results into `[]` then the keys of the first object are used (which is unexpected).
However, maybe `null` looks a bit strange as default value

- I'm not sure about the last test... is the expected value correct?

janisdd: Ok, done.

Patch:
diff --git a/docs/docs.html b/docs/docs.html
index dd37e50..8777802 100644
--- a/docs/docs.html
+++ b/docs/docs.html
@@ -261,7 +261,8 @@
 	delimiter: ",",
 	header: true,
 	newline: "\r\n",
-	skipEmptyLines: false, //or 'greedy'
+	skipEmptyLines: false, //or 'greedy',
+	columns: null //or array of strings
 }
 					</code></pre>
 				</div>
@@ -324,6 +325,14 @@
 									If <code>true</code>, lines that are completely empty (those which evaluate to an empty string) will be skipped. If set to <code>'greedy'</code>, lines that don't have any content (those which have only whitespace after parsing) will also be skipped.
 								</td>
 							</tr>
+							<tr>
+								<td>
+									<code>columns</code>
+								</td>
+								<td>
+									If <code>data</code> is an array of objects this option can be used to manually specify the keys (columns) you expect in the objects. If not set the keys of the first objects are used as column.
+								</td>
+							</tr>
 						</table>
 					</div>
 					<div class="clear"></div>
diff --git a/papaparse.js b/papaparse.js
index 5ed703e..5c7b5ed 100755
--- a/papaparse.js
+++ b/papaparse.js
@@ -276,6 +276,9 @@ License: MIT
 		/** whether to skip empty lines */
 		var _skipEmptyLines = false;
 
+		/** the columns (keys) we expect when we unparse objects */
+		var _columns = null;
+
 		unpackConfig();
 
 		var quoteCharRegex = new RegExp(escapeRegExp(_quoteChar), 'g');
@@ -288,7 +291,7 @@ License: MIT
 			if (!_input.length || Array.isArray(_input[0]))
 				return serialize(null, _input, _skipEmptyLines);
 			else if (typeof _input[0] === 'object')
-				return serialize(objectKeys(_input[0]), _input, _skipEmptyLines);
+				return serialize(_columns || objectKeys(_input[0]), _input, _skipEmptyLines);
 		}
 		else if (typeof _input === 'object')
 		{
@@ -343,6 +346,13 @@ License: MIT
 
 			if (typeof _config.header === 'boolean')
 				_writeHeader = _config.header;
+
+			if (Array.isArray(_config.columns)) {
+
+				if (_config.columns.length === 0) throw new Error('Option columns is empty');
+
+				_columns = _config.columns;
+			}
 		}
 
 


Test patch:
diff --git a/tests/test-cases.js b/tests/test-cases.js
index b0610dd..5ea71e0 100644
--- a/tests/test-cases.js
+++ b/tests/test-cases.js
@@ -1732,6 +1732,13 @@ var UNPARSE_TESTS = [
 		input: [{a: null, b: ' '}, {}, {a: '1', b: '2'}],
 		config: {skipEmptyLines: 'greedy', header: true},
 		expected: 'a,b\r\n1,2'
+	},
+	{
+		description: "Column option used to manually specify keys",
+		notes: "Should not throw any error when attempting to serialize key not present in object. Columns are different than keys of the first object. When an object is missing a key then the serialized value should be an empty string.",
+		input: [{a: 1, b: '2'}, {}, {a: 3, d: 'd', c: 4,}],
+		config: {columns: ['a', 'b', 'c']},
+		expected: 'a,b,c\r\n1,2,\r\n\r\n3,,4'
 	}
 ];