# Transforms A `transform` is a function to preprocess data before it is converted into CSV. Each `transform` receives each data record, performs some processing and returns a transformed record. ## Built-in transforms There is a number of built-in transform provided by this package. ### Installation ### **NPM** You can install json2csv `transforms` as a dependency using NPM. ```bash $ npm install --save @json2csv/transforms ``` ```js import { unwind, flatten } from '@json2csv/transforms'; ``` ### **Yarn** You can install json2csv `transforms` as a dependency using Yarn. ```bash $ yarn add --save @json2csv/transforms ``` ```js import { unwind, flatten } from '@json2csv/transforms'; ``` ### **CDN** json2csv `transforms` are packaged as an ES6 modules. If your browser supports modules, you can load json2csv `transforms` directly on the browser from the CDN. You can import the latest version: ```html ``` You can also select a specific version: ```html ``` ### Unwind The `unwind` transform deconstructs an array field from the input item to output a row for each element. It's similar to MongoDB's \$unwind aggregation. The transform needs to be instantiated and takes an options object as arguments containing: * `paths` [<String[]>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) List of the paths to the fields to be unwound. Optional. If omitted, all array fields are automatically detected and unwound. * `blankOut` [<Boolean>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean) Flag indicating whether to unwind using blank values instead of repeating data or not. Defaults to `false`. #### Examples ##### Simple unwind #### **Parser** ```js import { Parser } from '@json2csv/plainjs'; import { unwind } from '@json2csv/transforms'; const data = [ { "carModel": "Audi", "price": 0, "colors": ["blue","green","yellow"] }, { "carModel": "BMW", "price": 15000, "colors": ["red","blue"] }, { "carModel": "Mercedes", "price": 20000, "colors": "yellow" }, { "carModel": "Porsche", "price": 30000, "colors": ["green","teal","aqua"] }, { "carModel": "Tesla", "price": 50000, "colors": []} ]; try { const opts = { transforms: [ unwind({ paths: ['colors'] }) ] }; const parser = new Parser(opts); const csv = parser.parse(data); console.log(csv); } catch (err) { console.error(err); } ``` #### **Stream Parser** ```js import { StreamParser } from '@json2csv/plainjs'; import { unwind } from '@json2csv/transforms'; const data = [ { "carModel": "Audi", "price": 0, "colors": ["blue","green","yellow"] }, { "carModel": "BMW", "price": 15000, "colors": ["red","blue"] }, { "carModel": "Mercedes", "price": 20000, "colors": "yellow" }, { "carModel": "Porsche", "price": 30000, "colors": ["green","teal","aqua"] }, { "carModel": "Tesla", "price": 50000, "colors": []} ]; const opts = { transforms: [ unwind({ paths: ['colors'] }) ] }; const parser = new StreamParser(opts, { objectMode: true }); let csv = ''; parser.onData = (chunk) => (csv += chunk.toString()); parser.onEnd = () => console.log(csv); parser.onError = (err) => console.error(err); data.forEach(record => parser.write(record)); ``` #### **Node Transform** ```js import { createReadStream, createWriteStream } from 'fs'; import { Transform } from '@json2csv/node'; import { unwind } from '@json2csv/transforms'; const data = [ { "carModel": "Audi", "price": 0, "colors": ["blue","green","yellow"] }, { "carModel": "BMW", "price": 15000, "colors": ["red","blue"] }, { "carModel": "Mercedes", "price": 20000, "colors": "yellow" }, { "carModel": "Porsche", "price": 30000, "colors": ["green","teal","aqua"] }, { "carModel": "Tesla", "price": 50000, "colors": []} ]; const input = createReadStream(inputPath, { encoding: 'utf8' }); const output = createWriteStream(outputPath, { encoding: 'utf8' }); const opts = { transforms: [ unwind({ paths: ['colors'] }) ] }; const parser = new Transform(opts); const processor = input.pipe(parser).pipe(output); ``` #### **Node Async Parser** ```js import { AsyncParser } from '@json2csv/node'; import { unwind } from '@json2csv/transforms'; import { addCounter } from './custom-transforms'; const opts = { transforms: [ unwind({ paths: ['colors'] }) ] }; const parser = new AsyncParser(opts); let csv = await parser.parse(data).promise(); ``` #### **WHATWG Transform Stream** ```js import { TransformStream } from '@json2csv/whatwg'; import { unwind } from '@json2csv/transforms'; const opts = { transforms: [ unwind({ paths: ['colors'] }) ] }; const parser = new TransformStream(opts); await sourceStream.pipeThrough(parser).pipeTo(writableStream); ``` #### **CLI** At the moment, only built-in transforms are supported by the CLI interface. ```bash $ json2csv -i data.json --unwind "color" ``` ```js // Unwind a single field unwind({ paths: ['fieldToUnwind'] }); // Unwind a single field and blank out repeated data unwind({ paths: ['fieldToUnwind'], blankOut: true }); ``` ### Flatten Flatten nested JavaScript objects into a single level object. The transform needs to be instantiated and takes an options object as arguments containing: * `objects` [<Boolean>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean) Flag indicating whether to flatten JSON objects or not. Defaults to `true`. * `arrays`[<Boolean>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean) Flag indicating whether to flatten Arrays or not. Defaults to `false`. * `separator` [<String>](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) Separator to use between the keys of the nested JSON properties being flattened. Defaults to `.`. ```js // Default flatten(); // Custom separator '_' flatten({ separator: '_' }); // Flatten only arrays flatten({ objects: false, arrays: true }); ``` ## Custom transforms Users can create their own transforms as simple functions. ```js function doNothing(item) { // apply tranformations or create new object return transformedItem; } ``` or using ES6 ```js const doNothing = (item) => { // apply tranformations or create new object return transformedItem; }; ``` For example, let's add a line counter to our CSV, capitalize the car field and change the price to be in Ks (1000s). ```js function addCounter() { let counter = 1; return (item) => ({ counter: counter++, ...item, car: item.car.toUpperCase(), price: item.price / 1000, }); } ``` The reason to wrap the actual transform in a factory function is so the counter always starts from one and you can reuse it. But it's not strictly necessary. ## How to use transforms Transforms are added to the `transforms` option when creating a parser. They are applied in the order in which they are declared. #### **Parser** ```js import { Parser } from '@json2csv/plainjs'; import { unwind, flatten } from '@json2csv/transforms'; import { addCounter } from './custom-transforms'; try { const opts = { transforms: [ unwind({ paths: ['fieldToUnwind','fieldToUnwind.subfieldToUnwind'], blankOut: true }), flatten({ objects: true, arrays: true, separator: '_'}), addCounter() ] }; const parser = new Parser(opts); const csv = parser.parse(myData); console.log(csv); } catch (err) { console.error(err); } ``` #### **Stream Parser** ```js import { StreamParser } from '@json2csv/plainjs'; import { unwind, flatten } from '@json2csv/transforms'; import { addCounter } from './custom-transforms'; const opts = { transforms: [ unwind({ paths: ['fieldToUnwind','fieldToUnwind.subfieldToUnwind'], blankOut: true }), flatten({ objects: true, arrays: true, separator: '_'}), addCounter() ] }; const parser = new StreamParser(opts); let csv = ''; parser.onData = (chunk) => (csv += chunk.toString()); parser.onEnd = () => console.log(csv); parser.onError = (err) => console.error(err); // You can also listen for events on the conversion and see how the header or the lines are coming out. parser.onHeader = (header) => console.log(header); parser.onLine = (line) => console.log(line); ``` #### **Node Transform** ```js import { createReadStream, createWriteStream } from 'fs'; import { Transform } from '@json2csv/node'; import { unwind, flatten } from '@json2csv/transforms'; import { addCounter } from './custom-transforms'; const input = createReadStream(inputPath, { encoding: 'utf8' }); const output = createWriteStream(outputPath, { encoding: 'utf8' }); const opts = { transforms: [ unwind({ paths: ['fieldToUnwind','fieldToUnwind.subfieldToUnwind'], blankOut: true }), flatten({ objects: true, arrays: true, separator: '_'}), addCounter() ] }; const parser = new Transform(opts); const processor = input.pipe(parser).pipe(output); // You can also listen for events on the conversion and see how the header or the lines are coming out. parser .on('header', (header) => console.log(header)) .on('line', (line) => console.log(line)); ``` #### **Node Async Parser** ```js import { AsyncParser } from '@json2csv/node'; import { unwind, flatten } from '@json2csv/transforms'; import { addCounter } from './custom-transforms'; const opts = { transforms: [ unwind({ paths: ['fieldToUnwind','fieldToUnwind.subfieldToUnwind'], blankOut: true }), flatten({ objects: true, arrays: true, separator: '_'}), addCounter() ] }; const parser = new AsyncParser(opts); let csv = await parser.parse(data).promise(); ``` #### **WHATWG Transform Stream** ```js import { TransformStream } from '@json2csv/whatwg'; import { unwind, flatten } from '@json2csv/transforms'; import { addCounter } from './custom-transforms'; const opts = { transforms: [ unwind({ paths: ['fieldToUnwind','fieldToUnwind.subfieldToUnwind'], blankOut: true }), flatten({ objects: true, arrays: true, separator: '_'}), addCounter() ] }; const parser = new TransformStream(opts); await sourceStream.pipeThrough(parser).pipeTo(writableStream); // You can also listen for events on the conversion and see how the header or the lines are coming out. parser .addEventListener('header', (event) => console.log(event.detail)) .addEventListener('line', (event) => console.log(event.detail)); ``` #### **CLI** At the moment, only built-in transforms are supported by the CLI interface. ```bash $ json2csv -i input.json \ --unwind "fieldToUnwind","fieldToUnwind.subfieldToUnwind" \ --unwind-blank \ --flatten-objects \ --flatten-arrays \ --flatten-separator "_" ```