csv-stringify
Advanced tools
Comparing version 5.6.5 to 6.0.0
/// <reference types="node" /> | ||
import * as stream from "stream"; | ||
export = stringify | ||
declare function stringify(callback?: stringify.Callback): stringify.Stringifier | ||
declare function stringify(options: stringify.Options, callback?: stringify.Callback): stringify.Stringifier | ||
declare function stringify(input: stringify.Input, callback?: stringify.Callback): stringify.Stringifier | ||
declare function stringify(input: stringify.Input, options?: stringify.Options, callback?: stringify.Callback): stringify.Stringifier | ||
declare namespace stringify { | ||
type Callback = (err: Error | undefined, output: string) => void | ||
type RecordDelimiter = string | Buffer | 'auto' | 'unix' | 'mac' | 'windows' | 'ascii' | 'unicode' | ||
type Cast<T> = (value: T, context: CastingContext) => string | ||
type PlainObject<T> = Record<string, T> | ||
type Input = any[] | ||
interface ColumnOption { | ||
key: string | ||
header?: string | ||
} | ||
interface CastingContext { | ||
readonly column?: number | string; | ||
readonly header: boolean; | ||
readonly index: number; | ||
readonly records: number; | ||
} | ||
interface Options { | ||
export type Callback = (err: Error | undefined, output: string) => void | ||
export type RecordDelimiter = string | Buffer | 'auto' | 'unix' | 'mac' | 'windows' | 'ascii' | 'unicode' | ||
export type Cast<T> = (value: T, context: CastingContext) => string | ||
export type PlainObject<T> = Record<string, T> | ||
export type Input = any[] | ||
export interface ColumnOption { | ||
key: string | ||
header?: string | ||
} | ||
export interface CastingContext { | ||
readonly column?: number | string; | ||
readonly header: boolean; | ||
readonly index: number; | ||
readonly records: number; | ||
} | ||
export interface Options extends stream.TransformOptions { | ||
/** | ||
* Prepend the byte order mark (BOM) to the output stream. | ||
*/ | ||
bom?: boolean | ||
/** | ||
* Key-value object which defines custom cast for certain data types | ||
*/ | ||
cast?: { | ||
boolean?: Cast<boolean> | ||
date?: Cast<Date> | ||
number?: Cast<number> | ||
/** | ||
* Prepend the byte order mark (BOM) to the output stream. | ||
* Custom formatter for generic object values | ||
*/ | ||
bom?: boolean | ||
/** | ||
* Key-value object which defines custom cast for certain data types | ||
*/ | ||
cast?: { | ||
boolean?: Cast<boolean> | ||
date?: Cast<Date> | ||
number?: Cast<number> | ||
/** | ||
* Custom formatter for generic object values | ||
*/ | ||
object?: Cast<Record<string, any>> | ||
string?: Cast<string> | ||
} | ||
/** | ||
* List of fields, applied when `transform` returns an object | ||
* order matters | ||
* read the transformer documentation for additionnal information | ||
* columns are auto discovered in the first record when the user write objects | ||
* can refer to nested properties of the input JSON | ||
* see the "header" option on how to print columns names on the first line | ||
*/ | ||
columns?: string[] | PlainObject<string> | ColumnOption[] | ||
/** | ||
* Set the field delimiter, one character only, defaults to a comma. | ||
*/ | ||
delimiter?: string | Buffer | ||
/** | ||
* Add the value of "options.RecordDelimiter" on the last line, default to true. | ||
*/ | ||
eof?: boolean | ||
/** | ||
* Defaults to the escape read option. | ||
*/ | ||
escape?: string | Buffer | ||
/** | ||
* Display the column names on the first line if the columns option is provided or discovered. | ||
*/ | ||
header?: boolean | ||
/** | ||
* The quote characters, defaults to the ", an empty quote value will preserve the original field. | ||
*/ | ||
quote?: string | Buffer | boolean | ||
/** | ||
* Boolean, default to false, quote all the non-empty fields even if not required. | ||
*/ | ||
quoted?: boolean | ||
object?: Cast<Record<string, any>> | ||
string?: Cast<string> | ||
} | ||
/** | ||
* List of fields, applied when `transform` returns an object | ||
* order matters | ||
* read the transformer documentation for additionnal information | ||
* columns are auto discovered in the first record when the user write objects | ||
* can refer to nested properties of the input JSON | ||
* see the "header" option on how to print columns names on the first line | ||
*/ | ||
columns?: string[] | PlainObject<string> | ColumnOption[] | ||
/** | ||
* Set the field delimiter, one character only, defaults to a comma. | ||
*/ | ||
delimiter?: string | Buffer | ||
/** | ||
* Add the value of "options.RecordDelimiter" on the last line, default to true. | ||
*/ | ||
eof?: boolean | ||
/** | ||
* Defaults to the escape read option. | ||
*/ | ||
escape?: string | Buffer | ||
/** | ||
* Display the column names on the first line if the columns option is provided or discovered. | ||
*/ | ||
header?: boolean | ||
/** | ||
* The quote characters, defaults to the ", an empty quote value will preserve the original field. | ||
*/ | ||
quote?: string | Buffer | boolean | ||
/** | ||
* Boolean, default to false, quote all the non-empty fields even if not required. | ||
*/ | ||
quoted?: boolean | ||
/** | ||
* Boolean, no default, quote empty fields and overrides `quoted_string` on empty strings when defined. | ||
*/ | ||
quoted_empty?: boolean | ||
/** | ||
* String or RegExp, no default, quote all fields matching a regular expression. | ||
*/ | ||
quoted_match?: string | RegExp | ||
/** | ||
* Boolean, default to false, quote all fields of type string even if not required. | ||
*/ | ||
quoted_string?: boolean | ||
/** | ||
* String used to delimit record rows or a special value | ||
* special values are 'auto', 'unix', 'mac', 'windows', 'ascii', 'unicode' | ||
* defaults to 'auto' (discovered in source or 'unix' if no source is specified). | ||
*/ | ||
record_delimiter?: RecordDelimiter | ||
} | ||
class Stringifier extends stream.Transform { | ||
constructor(options: Options) | ||
readonly options: Options | ||
} | ||
/** | ||
* Boolean, no default, quote empty fields and overrides `quoted_string` on empty strings when defined. | ||
*/ | ||
quoted_empty?: boolean | ||
/** | ||
* String or RegExp, no default, quote all fields matching a regular expression. | ||
*/ | ||
quoted_match?: string | RegExp | ||
/** | ||
* Boolean, default to false, quote all fields of type string even if not required. | ||
*/ | ||
quoted_string?: boolean | ||
/** | ||
* String used to delimit record rows or a special value | ||
* special values are 'auto', 'unix', 'mac', 'windows', 'ascii', 'unicode' | ||
* defaults to 'auto' (discovered in source or 'unix' if no source is specified). | ||
*/ | ||
record_delimiter?: RecordDelimiter | ||
} | ||
export class Stringifier extends stream.Transform { | ||
constructor(options: Options) | ||
readonly options: Options | ||
} | ||
declare function stringify(callback?: Callback): Stringifier | ||
declare function stringify(options: Options, callback?: Callback): Stringifier | ||
declare function stringify(input: Input, callback?: Callback): Stringifier | ||
declare function stringify(input: Input, options?: Options, callback?: Callback): Stringifier | ||
// export default stringify | ||
export { stringify } |
656
lib/index.js
@@ -9,45 +9,151 @@ | ||
const { Transform } = require('stream') | ||
const bom_utf8 = Buffer.from([239, 187, 191]) | ||
import { Transform } from 'stream'; | ||
const bom_utf8 = Buffer.from([239, 187, 191]); | ||
class CsvError extends Error { | ||
constructor(code, message, ...contexts) { | ||
if(Array.isArray(message)) message = message.join(' '); | ||
super(message); | ||
if(Error.captureStackTrace !== undefined){ | ||
Error.captureStackTrace(this, CsvError); | ||
} | ||
this.code = code; | ||
for(const context of contexts){ | ||
for(const key in context){ | ||
const value = context[key]; | ||
this[key] = Buffer.isBuffer(value) ? value.toString() : value == null ? value : JSON.parse(JSON.stringify(value)); | ||
} | ||
} | ||
} | ||
} | ||
const isObject = function(obj){ | ||
return typeof obj === 'object' && obj !== null && ! Array.isArray(obj); | ||
}; | ||
const underscore = function(str){ | ||
return str.replace(/([A-Z])/g, function(_, match){ | ||
return '_' + match.toLowerCase(); | ||
}); | ||
}; | ||
// Lodash implementation of `get` | ||
const charCodeOfDot = '.'.charCodeAt(0); | ||
const reEscapeChar = /\\(\\)?/g; | ||
const rePropName = RegExp( | ||
// Match anything that isn't a dot or bracket. | ||
'[^.[\\]]+' + '|' + | ||
// Or match property names within brackets. | ||
'\\[(?:' + | ||
// Match a non-string expression. | ||
'([^"\'][^[]*)' + '|' + | ||
// Or match strings (supports escaping characters). | ||
'(["\'])((?:(?!\\2)[^\\\\]|\\\\.)*?)\\2' + | ||
')\\]'+ '|' + | ||
// Or match "" as the space between consecutive dots or empty brackets. | ||
'(?=(?:\\.|\\[\\])(?:\\.|\\[\\]|$))' | ||
, 'g'); | ||
const reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/; | ||
const reIsPlainProp = /^\w*$/; | ||
const getTag = function(value){ | ||
if(!value) | ||
value === undefined ? '[object Undefined]' : '[object Null]'; | ||
return Object.prototype.toString.call(value); | ||
}; | ||
const isSymbol = function(value){ | ||
const type = typeof value; | ||
return type === 'symbol' || (type === 'object' && value && getTag(value) === '[object Symbol]'); | ||
}; | ||
const isKey = function(value, object){ | ||
if(Array.isArray(value)){ | ||
return false; | ||
} | ||
const type = typeof value; | ||
if(type === 'number' || type === 'symbol' || type === 'boolean' || !value || isSymbol(value)){ | ||
return true; | ||
} | ||
return reIsPlainProp.test(value) || !reIsDeepProp.test(value) || | ||
(object != null && value in Object(object)); | ||
}; | ||
const stringToPath = function(string){ | ||
const result = []; | ||
if(string.charCodeAt(0) === charCodeOfDot){ | ||
result.push(''); | ||
} | ||
string.replace(rePropName, function(match, expression, quote, subString){ | ||
let key = match; | ||
if(quote){ | ||
key = subString.replace(reEscapeChar, '$1'); | ||
}else if(expression){ | ||
key = expression.trim(); | ||
} | ||
result.push(key); | ||
}); | ||
return result; | ||
}; | ||
const castPath = function(value, object){ | ||
if(Array.isArray(value)){ | ||
return value; | ||
} else { | ||
return isKey(value, object) ? [value] : stringToPath(value); | ||
} | ||
}; | ||
const toKey = function(value){ | ||
if(typeof value === 'string' || isSymbol(value)) | ||
return value; | ||
const result = `${value}`; | ||
// eslint-disable-next-line | ||
return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result; | ||
}; | ||
const get = function(object, path){ | ||
path = castPath(path, object); | ||
let index = 0; | ||
const length = path.length; | ||
while(object != null && index < length){ | ||
object = object[toKey(path[index++])]; | ||
} | ||
return (index && index === length) ? object : undefined; | ||
}; | ||
class Stringifier extends Transform { | ||
constructor(opts = {}){ | ||
super({...{writableObjectMode: true}, ...opts}) | ||
const options = {} | ||
let err | ||
super({...{writableObjectMode: true}, ...opts}); | ||
const options = {}; | ||
let err; | ||
// Merge with user options | ||
for(let opt in opts){ | ||
options[underscore(opt)] = opts[opt] | ||
for(const opt in opts){ | ||
options[underscore(opt)] = opts[opt]; | ||
} | ||
if(err = this.normalize(options)) throw err | ||
if((err = this.normalize(options)) !== undefined) throw err; | ||
switch(options.record_delimiter){ | ||
case 'auto': | ||
options.record_delimiter = null | ||
break | ||
case 'unix': | ||
options.record_delimiter = "\n" | ||
break | ||
case 'mac': | ||
options.record_delimiter = "\r" | ||
break | ||
case 'windows': | ||
options.record_delimiter = "\r\n" | ||
break | ||
case 'ascii': | ||
options.record_delimiter = "\u001e" | ||
break | ||
case 'unicode': | ||
options.record_delimiter = "\u2028" | ||
break | ||
case 'auto': | ||
options.record_delimiter = null; | ||
break; | ||
case 'unix': | ||
options.record_delimiter = "\n"; | ||
break; | ||
case 'mac': | ||
options.record_delimiter = "\r"; | ||
break; | ||
case 'windows': | ||
options.record_delimiter = "\r\n"; | ||
break; | ||
case 'ascii': | ||
options.record_delimiter = "\u001e"; | ||
break; | ||
case 'unicode': | ||
options.record_delimiter = "\u2028"; | ||
break; | ||
} | ||
// Expose options | ||
this.options = options | ||
this.options = options; | ||
// Internal state | ||
this.state = { | ||
stop: false | ||
} | ||
}; | ||
// Information | ||
this.info = { | ||
records: 0 | ||
} | ||
}; | ||
} | ||
@@ -57,3 +163,3 @@ normalize(options){ | ||
if(options.bom === undefined || options.bom === null || options.bom === false){ | ||
options.bom = false | ||
options.bom = false; | ||
}else if(options.bom !== true){ | ||
@@ -63,9 +169,9 @@ return new CsvError('CSV_OPTION_BOOLEAN_INVALID_TYPE', [ | ||
`got ${JSON.stringify(options.bom)}` | ||
]) | ||
]); | ||
} | ||
// Normalize option `delimiter` | ||
if(options.delimiter === undefined || options.delimiter === null){ | ||
options.delimiter = ',' | ||
options.delimiter = ','; | ||
}else if(Buffer.isBuffer(options.delimiter)){ | ||
options.delimiter = options.delimiter.toString() | ||
options.delimiter = options.delimiter.toString(); | ||
}else if(typeof options.delimiter !== 'string'){ | ||
@@ -75,13 +181,13 @@ return new CsvError('CSV_OPTION_DELIMITER_INVALID_TYPE', [ | ||
`got ${JSON.stringify(options.delimiter)}` | ||
]) | ||
]); | ||
} | ||
// Normalize option `quote` | ||
if(options.quote === undefined || options.quote === null){ | ||
options.quote = '"' | ||
options.quote = '"'; | ||
}else if(options.quote === true){ | ||
options.quote = '"' | ||
options.quote = '"'; | ||
}else if(options.quote === false){ | ||
options.quote = '' | ||
options.quote = ''; | ||
}else if (Buffer.isBuffer(options.quote)){ | ||
options.quote = options.quote.toString() | ||
options.quote = options.quote.toString(); | ||
}else if(typeof options.quote !== 'string'){ | ||
@@ -91,7 +197,7 @@ return new CsvError('CSV_OPTION_QUOTE_INVALID_TYPE', [ | ||
`got ${JSON.stringify(options.quote)}` | ||
]) | ||
]); | ||
} | ||
// Normalize option `quoted` | ||
if(options.quoted === undefined || options.quoted === null){ | ||
options.quoted = false | ||
options.quoted = false; | ||
}else{ | ||
@@ -102,3 +208,3 @@ // todo | ||
if(options.quoted_empty === undefined || options.quoted_empty === null){ | ||
options.quoted_empty = undefined | ||
options.quoted_empty = undefined; | ||
}else{ | ||
@@ -109,12 +215,12 @@ // todo | ||
if(options.quoted_match === undefined || options.quoted_match === null || options.quoted_match === false){ | ||
options.quoted_match = null | ||
options.quoted_match = null; | ||
}else if(!Array.isArray(options.quoted_match)){ | ||
options.quoted_match = [options.quoted_match] | ||
options.quoted_match = [options.quoted_match]; | ||
} | ||
if(options.quoted_match){ | ||
for(let quoted_match of options.quoted_match){ | ||
const isString = typeof quoted_match === 'string' | ||
const isRegExp = quoted_match instanceof RegExp | ||
for(const quoted_match of options.quoted_match){ | ||
const isString = typeof quoted_match === 'string'; | ||
const isRegExp = quoted_match instanceof RegExp; | ||
if(!isString && !isRegExp){ | ||
return Error(`Invalid Option: quoted_match must be a string or a regex, got ${JSON.stringify(quoted_match)}`) | ||
return Error(`Invalid Option: quoted_match must be a string or a regex, got ${JSON.stringify(quoted_match)}`); | ||
} | ||
@@ -125,3 +231,3 @@ } | ||
if(options.quoted_string === undefined || options.quoted_string === null){ | ||
options.quoted_string = false | ||
options.quoted_string = false; | ||
}else{ | ||
@@ -132,3 +238,3 @@ // todo | ||
if(options.eof === undefined || options.eof === null){ | ||
options.eof = true | ||
options.eof = true; | ||
}else{ | ||
@@ -139,14 +245,14 @@ // todo | ||
if(options.escape === undefined || options.escape === null){ | ||
options.escape = '"' | ||
options.escape = '"'; | ||
}else if(Buffer.isBuffer(options.escape)){ | ||
options.escape = options.escape.toString() | ||
options.escape = options.escape.toString(); | ||
}else if(typeof options.escape !== 'string'){ | ||
return Error(`Invalid Option: escape must be a buffer or a string, got ${JSON.stringify(options.escape)}`) | ||
return Error(`Invalid Option: escape must be a buffer or a string, got ${JSON.stringify(options.escape)}`); | ||
} | ||
if (options.escape.length > 1){ | ||
return Error(`Invalid Option: escape must be one character, got ${options.escape.length} characters`) | ||
return Error(`Invalid Option: escape must be one character, got ${options.escape.length} characters`); | ||
} | ||
// Normalize option `header` | ||
if(options.header === undefined || options.header === null){ | ||
options.header = false | ||
options.header = false; | ||
}else{ | ||
@@ -156,6 +262,6 @@ // todo | ||
// Normalize option `columns` | ||
options.columns = this.normalize_columns(options.columns) | ||
options.columns = this.normalize_columns(options.columns); | ||
// Normalize option `quoted` | ||
if(options.quoted === undefined || options.quoted === null){ | ||
options.quoted = false | ||
options.quoted = false; | ||
}else{ | ||
@@ -166,3 +272,3 @@ // todo | ||
if(options.cast === undefined || options.cast === null){ | ||
options.cast = {} | ||
options.cast = {}; | ||
}else{ | ||
@@ -174,3 +280,3 @@ // todo | ||
// Cast boolean to string by default | ||
options.cast.bigint = value => '' + value | ||
options.cast.bigint = value => '' + value; | ||
} | ||
@@ -180,3 +286,3 @@ // Normalize option cast.boolean | ||
// Cast boolean to string by default | ||
options.cast.boolean = value => value ? '1' : '' | ||
options.cast.boolean = value => value ? '1' : ''; | ||
} | ||
@@ -186,3 +292,3 @@ // Normalize option cast.date | ||
// Cast date to timestamp string by default | ||
options.cast.date = value => '' + value.getTime() | ||
options.cast.date = value => '' + value.getTime(); | ||
} | ||
@@ -192,3 +298,3 @@ // Normalize option cast.number | ||
// Cast number to string using native casting by default | ||
options.cast.number = value => '' + value | ||
options.cast.number = value => '' + value; | ||
} | ||
@@ -198,3 +304,3 @@ // Normalize option cast.object | ||
// Stringify object as JSON by default | ||
options.cast.object = value => JSON.stringify(value) | ||
options.cast.object = value => JSON.stringify(value); | ||
} | ||
@@ -204,11 +310,11 @@ // Normalize option cast.string | ||
// Leave string untouched | ||
options.cast.string = function(value){return value} | ||
options.cast.string = function(value){return value;}; | ||
} | ||
// Normalize option `record_delimiter` | ||
if(options.record_delimiter === undefined || options.record_delimiter === null){ | ||
options.record_delimiter = '\n' | ||
options.record_delimiter = '\n'; | ||
}else if(Buffer.isBuffer(options.record_delimiter)){ | ||
options.record_delimiter = options.record_delimiter.toString() | ||
options.record_delimiter = options.record_delimiter.toString(); | ||
}else if(typeof options.record_delimiter !== 'string'){ | ||
return Error(`Invalid Option: record_delimiter must be a buffer or a string, got ${JSON.stringify(options.record_delimiter)}`) | ||
return Error(`Invalid Option: record_delimiter must be a buffer or a string, got ${JSON.stringify(options.record_delimiter)}`); | ||
} | ||
@@ -218,8 +324,8 @@ } | ||
if(this.state.stop === true){ | ||
return | ||
return; | ||
} | ||
// Chunk validation | ||
if(!Array.isArray(chunk) && typeof chunk !== 'object'){ | ||
this.state.stop = true | ||
return callback(Error(`Invalid Record: expect an array or an object, got ${JSON.stringify(chunk)}`)) | ||
this.state.stop = true; | ||
return callback(Error(`Invalid Record: expect an array or an object, got ${JSON.stringify(chunk)}`)); | ||
} | ||
@@ -229,8 +335,8 @@ // Detect columns from the first record | ||
if(Array.isArray(chunk)){ | ||
if(this.options.header === true && !this.options.columns){ | ||
this.state.stop = true | ||
return callback(Error('Undiscoverable Columns: header option requires column option or object records')) | ||
if(this.options.header === true && this.options.columns === undefined){ | ||
this.state.stop = true; | ||
return callback(Error('Undiscoverable Columns: header option requires column option or object records')); | ||
} | ||
}else if(this.options.columns === undefined || this.options.columns === null){ | ||
this.options.columns = this.normalize_columns(Object.keys(chunk)) | ||
}else if(this.options.columns === undefined){ | ||
this.options.columns = this.normalize_columns(Object.keys(chunk)); | ||
} | ||
@@ -240,28 +346,28 @@ } | ||
if(this.info.records === 0){ | ||
this.bom() | ||
this.headers() | ||
this.bom(); | ||
this.headers(); | ||
} | ||
// Emit and stringify the record if an object or an array | ||
try{ | ||
this.emit('record', chunk, this.info.records) | ||
this.emit('record', chunk, this.info.records); | ||
}catch(err){ | ||
this.state.stop = true | ||
return this.emit('error', err) | ||
this.state.stop = true; | ||
return this.emit('error', err); | ||
} | ||
// Convert the record into a string | ||
let chunk_string | ||
let chunk_string; | ||
if(this.options.eof){ | ||
chunk_string = this.stringify(chunk) | ||
chunk_string = this.stringify(chunk); | ||
if(chunk_string === undefined){ | ||
return | ||
return; | ||
}else{ | ||
chunk_string = chunk_string + this.options.record_delimiter | ||
chunk_string = chunk_string + this.options.record_delimiter; | ||
} | ||
}else{ | ||
chunk_string = this.stringify(chunk) | ||
chunk_string = this.stringify(chunk); | ||
if(chunk_string === undefined){ | ||
return | ||
return; | ||
}else{ | ||
if(this.options.header || this.info.records){ | ||
chunk_string = this.options.record_delimiter + chunk_string | ||
chunk_string = this.options.record_delimiter + chunk_string; | ||
} | ||
@@ -271,19 +377,19 @@ } | ||
// Emit the csv | ||
this.info.records++ | ||
this.push(chunk_string) | ||
callback() | ||
this.info.records++; | ||
this.push(chunk_string); | ||
callback(); | ||
} | ||
_flush(callback){ | ||
if(this.info.records === 0){ | ||
this.bom() | ||
this.headers() | ||
this.bom(); | ||
this.headers(); | ||
} | ||
callback() | ||
callback(); | ||
} | ||
stringify(chunk, chunkIsHeader=false){ | ||
if(typeof chunk !== 'object'){ | ||
return chunk | ||
return chunk; | ||
} | ||
const {columns, header} = this.options | ||
const record = [] | ||
const {columns} = this.options; | ||
const record = []; | ||
// Record is an array | ||
@@ -294,154 +400,142 @@ if(Array.isArray(chunk)){ | ||
if(columns){ | ||
chunk.splice(columns.length) | ||
chunk.splice(columns.length); | ||
} | ||
// Cast record elements | ||
for(let i=0; i<chunk.length; i++){ | ||
const field = chunk[i] | ||
const field = chunk[i]; | ||
const [err, value] = this.__cast(field, { | ||
index: i, column: i, records: this.info.records, header: chunkIsHeader | ||
}) | ||
}); | ||
if(err){ | ||
this.emit('error', err) | ||
return | ||
this.emit('error', err); | ||
return; | ||
} | ||
record[i] = [value, field] | ||
record[i] = [value, field]; | ||
} | ||
// Record is a literal object | ||
// `columns` is always defined: it is either provided or discovered. | ||
}else{ | ||
if(columns){ | ||
for(let i=0; i<columns.length; i++){ | ||
const field = get(chunk, columns[i].key) | ||
const [err, value] = this.__cast(field, { | ||
index: i, column: columns[i].key, records: this.info.records, header: chunkIsHeader | ||
}) | ||
if(err){ | ||
this.emit('error', err) | ||
return | ||
} | ||
record[i] = [value, field] | ||
for(let i=0; i<columns.length; i++){ | ||
const field = get(chunk, columns[i].key); | ||
const [err, value] = this.__cast(field, { | ||
index: i, column: columns[i].key, records: this.info.records, header: chunkIsHeader | ||
}); | ||
if(err){ | ||
this.emit('error', err); | ||
return; | ||
} | ||
}else{ | ||
for(let column of chunk){ | ||
const field = chunk[column] | ||
const [err, value] = this.__cast(field, { | ||
index: i, column: columns[i].key, records: this.info.records, header: chunkIsHeader | ||
}) | ||
if(err){ | ||
this.emit('error', err) | ||
return | ||
} | ||
record.push([value, field]) | ||
} | ||
record[i] = [value, field]; | ||
} | ||
} | ||
let csvrecord = '' | ||
let csvrecord = ''; | ||
for(let i=0; i<record.length; i++){ | ||
let options, err | ||
let [value, field] = record[i] | ||
let options, err; | ||
// eslint-disable-next-line | ||
let [value, field] = record[i]; | ||
if(typeof value === "string"){ | ||
options = this.options | ||
options = this.options; | ||
}else if(isObject(value)){ | ||
// let { value, ...options } = value | ||
options = value | ||
value = options.value | ||
delete options.value | ||
options = value; | ||
value = options.value; | ||
delete options.value; | ||
if(typeof value !== "string" && value !== undefined && value !== null){ | ||
this.emit("error", Error(`Invalid Casting Value: returned value must return a string, null or undefined, got ${JSON.stringify(value)}`)) | ||
return | ||
this.emit("error", Error(`Invalid Casting Value: returned value must return a string, null or undefined, got ${JSON.stringify(value)}`)); | ||
return; | ||
} | ||
options = {...this.options, ...options} | ||
if(err = this.normalize(options)){ | ||
this.emit("error", err) | ||
return | ||
options = {...this.options, ...options}; | ||
if((err = this.normalize(options)) !== undefined){ | ||
this.emit("error", err); | ||
return; | ||
} | ||
}else if(value === undefined || value === null){ | ||
options = this.options | ||
options = this.options; | ||
}else{ | ||
this.emit("error", Error(`Invalid Casting Value: returned value must return a string, an object, null or undefined, got ${JSON.stringify(value)}`)) | ||
return | ||
this.emit("error", Error(`Invalid Casting Value: returned value must return a string, an object, null or undefined, got ${JSON.stringify(value)}`)); | ||
return; | ||
} | ||
const {delimiter, escape, quote, quoted, quoted_empty, quoted_string, quoted_match, record_delimiter} = options | ||
const {delimiter, escape, quote, quoted, quoted_empty, quoted_string, quoted_match, record_delimiter} = options; | ||
if(value){ | ||
if(typeof value !== 'string'){ | ||
this.emit("error", Error(`Formatter must return a string, null or undefined, got ${JSON.stringify(value)}`)) | ||
return null | ||
this.emit("error", Error(`Formatter must return a string, null or undefined, got ${JSON.stringify(value)}`)); | ||
return null; | ||
} | ||
const containsdelimiter = delimiter.length && value.indexOf(delimiter) >= 0 | ||
const containsQuote = (quote !== '') && value.indexOf(quote) >= 0 | ||
const containsEscape = value.indexOf(escape) >= 0 && (escape !== quote) | ||
const containsRecordDelimiter = value.indexOf(record_delimiter) >= 0 | ||
const quotedString = quoted_string && typeof field === 'string' | ||
let quotedMatch = quoted_match && quoted_match.filter( quoted_match => { | ||
const containsdelimiter = delimiter.length && value.indexOf(delimiter) >= 0; | ||
const containsQuote = (quote !== '') && value.indexOf(quote) >= 0; | ||
const containsEscape = value.indexOf(escape) >= 0 && (escape !== quote); | ||
const containsRecordDelimiter = value.indexOf(record_delimiter) >= 0; | ||
const quotedString = quoted_string && typeof field === 'string'; | ||
let quotedMatch = quoted_match && quoted_match.filter(quoted_match => { | ||
if(typeof quoted_match === 'string'){ | ||
return value.indexOf(quoted_match) !== -1 | ||
return value.indexOf(quoted_match) !== -1; | ||
}else{ | ||
return quoted_match.test(value) | ||
return quoted_match.test(value); | ||
} | ||
}) | ||
quotedMatch = quotedMatch && quotedMatch.length > 0 | ||
const shouldQuote = containsQuote === true || containsdelimiter || containsRecordDelimiter || quoted || quotedString || quotedMatch | ||
}); | ||
quotedMatch = quotedMatch && quotedMatch.length > 0; | ||
const shouldQuote = containsQuote === true || containsdelimiter || containsRecordDelimiter || quoted || quotedString || quotedMatch; | ||
if(shouldQuote === true && containsEscape === true){ | ||
const regexp = escape === '\\' | ||
? new RegExp(escape + escape, 'g') | ||
: new RegExp(escape, 'g') | ||
value = value.replace(regexp, escape + escape) | ||
? new RegExp(escape + escape, 'g') | ||
: new RegExp(escape, 'g'); | ||
value = value.replace(regexp, escape + escape); | ||
} | ||
if(containsQuote === true){ | ||
const regexp = new RegExp(quote,'g') | ||
value = value.replace(regexp, escape + quote) | ||
const regexp = new RegExp(quote,'g'); | ||
value = value.replace(regexp, escape + quote); | ||
} | ||
if(shouldQuote === true){ | ||
value = quote + value + quote | ||
value = quote + value + quote; | ||
} | ||
csvrecord += value | ||
csvrecord += value; | ||
}else if(quoted_empty === true || (field === '' && quoted_string === true && quoted_empty !== false)){ | ||
csvrecord += quote + quote | ||
csvrecord += quote + quote; | ||
} | ||
if(i !== record.length - 1){ | ||
csvrecord += delimiter | ||
csvrecord += delimiter; | ||
} | ||
} | ||
return csvrecord | ||
return csvrecord; | ||
} | ||
bom(){ | ||
if(this.options.bom !== true){ | ||
return | ||
return; | ||
} | ||
this.push(bom_utf8) | ||
this.push(bom_utf8); | ||
} | ||
headers(){ | ||
if(this.options.header === false){ | ||
return | ||
return; | ||
} | ||
if(this.options.columns === undefined){ | ||
return | ||
return; | ||
} | ||
let headers = this.options.columns.map(column => column.header) | ||
let headers = this.options.columns.map(column => column.header); | ||
if(this.options.eof){ | ||
headers = this.stringify(headers, true) + this.options.record_delimiter | ||
headers = this.stringify(headers, true) + this.options.record_delimiter; | ||
}else{ | ||
headers = this.stringify(headers) | ||
headers = this.stringify(headers); | ||
} | ||
this.push(headers) | ||
this.push(headers); | ||
} | ||
__cast(value, context){ | ||
const type = typeof value | ||
const type = typeof value; | ||
try{ | ||
if(type === 'string'){ // Fine for 99% of the cases | ||
return [undefined, this.options.cast.string(value, context)] | ||
return [undefined, this.options.cast.string(value, context)]; | ||
}else if(type === 'bigint'){ | ||
return [undefined, this.options.cast.bigint(value, context)] | ||
return [undefined, this.options.cast.bigint(value, context)]; | ||
}else if(type === 'number'){ | ||
return [undefined, this.options.cast.number(value, context)] | ||
return [undefined, this.options.cast.number(value, context)]; | ||
}else if(type === 'boolean'){ | ||
return [undefined, this.options.cast.boolean(value, context)] | ||
return [undefined, this.options.cast.boolean(value, context)]; | ||
}else if(value instanceof Date){ | ||
return [undefined, this.options.cast.date(value, context)] | ||
return [undefined, this.options.cast.date(value, context)]; | ||
}else if(type === 'object' && value !== null){ | ||
return [undefined, this.options.cast.object(value, context)] | ||
return [undefined, this.options.cast.object(value, context)]; | ||
}else{ | ||
return [undefined, value, value] | ||
return [undefined, value, value]; | ||
} | ||
}catch(err){ | ||
return [err] | ||
return [err]; | ||
} | ||
@@ -451,19 +545,19 @@ } | ||
if(columns === undefined || columns === null){ | ||
return undefined | ||
return undefined; | ||
} | ||
if(typeof columns !== 'object'){ | ||
throw Error('Invalid option "columns": expect an array or an object') | ||
throw Error('Invalid option "columns": expect an array or an object'); | ||
} | ||
if(!Array.isArray(columns)){ | ||
const newcolumns = [] | ||
for(let k in columns){ | ||
const newcolumns = []; | ||
for(const k in columns){ | ||
newcolumns.push({ | ||
key: k, | ||
header: columns[k] | ||
}) | ||
}); | ||
} | ||
columns = newcolumns | ||
columns = newcolumns; | ||
}else{ | ||
const newcolumns = [] | ||
for(let column of columns){ | ||
const newcolumns = []; | ||
for(const column of columns){ | ||
if(typeof column === 'string'){ | ||
@@ -473,18 +567,18 @@ newcolumns.push({ | ||
header: column | ||
}) | ||
}); | ||
}else if(typeof column === 'object' && column !== undefined && !Array.isArray(column)){ | ||
if(!column.key){ | ||
throw Error('Invalid column definition: property "key" is required') | ||
throw Error('Invalid column definition: property "key" is required'); | ||
} | ||
if(column.header === undefined){ | ||
column.header = column.key | ||
column.header = column.key; | ||
} | ||
newcolumns.push(column) | ||
newcolumns.push(column); | ||
}else{ | ||
throw Error('Invalid column definition: expect a string or an object') | ||
throw Error('Invalid column definition: expect a string or an object'); | ||
} | ||
} | ||
columns = newcolumns | ||
columns = newcolumns; | ||
} | ||
return columns | ||
return columns; | ||
} | ||
@@ -494,12 +588,12 @@ } | ||
const stringify = function(){ | ||
let data, options, callback | ||
for(let i in arguments){ | ||
const argument = arguments[i] | ||
const type = typeof argument | ||
let data, options, callback; | ||
for(const i in arguments){ | ||
const argument = arguments[i]; | ||
const type = typeof argument; | ||
if(data === undefined && (Array.isArray(argument))){ | ||
data = argument | ||
data = argument; | ||
}else if(options === undefined && isObject(argument)){ | ||
options = argument | ||
options = argument; | ||
}else if(callback === undefined && type === 'function'){ | ||
callback = argument | ||
callback = argument; | ||
}else{ | ||
@@ -509,149 +603,39 @@ throw new CsvError('CSV_INVALID_ARGUMENT', [ | ||
`got ${JSON.stringify(argument)} at index ${i}` | ||
]) | ||
]); | ||
} | ||
} | ||
const stringifier = new Stringifier(options) | ||
const stringifier = new Stringifier(options); | ||
if(callback){ | ||
const chunks = [] | ||
const chunks = []; | ||
stringifier.on('readable', function(){ | ||
let chunk | ||
let chunk; | ||
while((chunk = this.read()) !== null){ | ||
chunks.push(chunk) | ||
chunks.push(chunk); | ||
} | ||
}) | ||
}); | ||
stringifier.on('error', function(err){ | ||
callback(err) | ||
}) | ||
callback(err); | ||
}); | ||
stringifier.on('end', function(){ | ||
callback(undefined, chunks.join('')) | ||
}) | ||
callback(undefined, chunks.join('')); | ||
}); | ||
} | ||
if(data !== undefined){ | ||
// Give a chance for events to be registered later | ||
const writer = function(){ | ||
for(const record of data){ | ||
stringifier.write(record); | ||
} | ||
stringifier.end(); | ||
}; | ||
// Support Deno, Rollup doesnt provide a shim for setImmediate | ||
if(typeof setImmediate === 'function'){ | ||
setImmediate(function(){ | ||
for(let record of data){ | ||
stringifier.write(record) | ||
} | ||
stringifier.end() | ||
}) | ||
setImmediate(writer); | ||
}else{ | ||
for(let record of data){ | ||
stringifier.write(record) | ||
} | ||
stringifier.end() | ||
setTimeout(writer, 0); | ||
} | ||
} | ||
return stringifier | ||
} | ||
return stringifier; | ||
}; | ||
class CsvError extends Error { | ||
constructor(code, message, ...contexts) { | ||
if(Array.isArray(message)) message = message.join(' ') | ||
super(message) | ||
if(Error.captureStackTrace !== undefined){ | ||
Error.captureStackTrace(this, CsvError) | ||
} | ||
this.code = code | ||
for(const context of contexts){ | ||
for(const key in context){ | ||
const value = context[key] | ||
this[key] = Buffer.isBuffer(value) ? value.toString() : value == null ? value : JSON.parse(JSON.stringify(value)) | ||
} | ||
} | ||
} | ||
} | ||
stringify.Stringifier = Stringifier | ||
stringify.CsvError = CsvError | ||
module.exports = stringify | ||
const isObject = function(obj){ | ||
return typeof obj === 'object' && obj !== null && ! Array.isArray(obj) | ||
} | ||
const underscore = function(str){ | ||
return str.replace(/([A-Z])/g, function(_, match){ | ||
return '_' + match.toLowerCase() | ||
}) | ||
} | ||
// Lodash implementation of `get` | ||
const charCodeOfDot = '.'.charCodeAt(0) | ||
const reEscapeChar = /\\(\\)?/g | ||
const rePropName = RegExp( | ||
// Match anything that isn't a dot or bracket. | ||
'[^.[\\]]+' + '|' + | ||
// Or match property names within brackets. | ||
'\\[(?:' + | ||
// Match a non-string expression. | ||
'([^"\'][^[]*)' + '|' + | ||
// Or match strings (supports escaping characters). | ||
'(["\'])((?:(?!\\2)[^\\\\]|\\\\.)*?)\\2' + | ||
')\\]'+ '|' + | ||
// Or match "" as the space between consecutive dots or empty brackets. | ||
'(?=(?:\\.|\\[\\])(?:\\.|\\[\\]|$))' | ||
, 'g') | ||
const reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/ | ||
const reIsPlainProp = /^\w*$/ | ||
const getTag = function(value){ | ||
if(!value) | ||
value === undefined ? '[object Undefined]' : '[object Null]' | ||
return Object.prototype.toString.call(value) | ||
} | ||
const isKey = function(value, object){ | ||
if(Array.isArray(value)){ | ||
return false | ||
} | ||
const type = typeof value | ||
if(type === 'number' || type === 'symbol' || type === 'boolean' || !value || isSymbol(value)){ | ||
return true | ||
} | ||
return reIsPlainProp.test(value) || !reIsDeepProp.test(value) || | ||
(object != null && value in Object(object)) | ||
} | ||
const isSymbol = function(value){ | ||
const type = typeof value | ||
return type === 'symbol' || (type === 'object' && value && getTag(value) === '[object Symbol]') | ||
} | ||
const stringToPath = function(string){ | ||
const result = [] | ||
if(string.charCodeAt(0) === charCodeOfDot){ | ||
result.push('') | ||
} | ||
string.replace(rePropName, function(match, expression, quote, subString){ | ||
let key = match | ||
if(quote){ | ||
key = subString.replace(reEscapeChar, '$1') | ||
}else if(expression){ | ||
key = expression.trim() | ||
} | ||
result.push(key) | ||
}) | ||
return result | ||
} | ||
const castPath = function(value, object){ | ||
if(Array.isArray(value)){ | ||
return value | ||
} else { | ||
return isKey(value, object) ? [value] : stringToPath(value) | ||
} | ||
} | ||
const toKey = function(value){ | ||
if(typeof value === 'string' || isSymbol(value)) | ||
return value | ||
const result = `${value}` | ||
return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result | ||
} | ||
const get = function(object, path){ | ||
path = castPath(path, object) | ||
let index = 0 | ||
const length = path.length | ||
while(object != null && index < length){ | ||
object = object[toKey(path[index++])] | ||
} | ||
return (index && index === length) ? object : undefined | ||
} | ||
// export default stringify | ||
export { stringify, CsvError, Stringifier }; |
@@ -1,6 +0,12 @@ | ||
import * as csvStringify from './index' | ||
export = stringify | ||
import { Input, Options } from './index' | ||
declare function stringify(input: csvStringify.Input, options?: csvStringify.Options): string | ||
declare namespace stringify {} | ||
declare function stringify(input: Input, options?: Options): string | ||
// export default stringify; | ||
export { stringify }; | ||
export { | ||
RecordDelimiter, Cast, PlainObject, Input, ColumnOption, CastingContext, | ||
Options | ||
} from './index'; |
const stringify = require('.') | ||
const {StringDecoder} = require('string_decoder') | ||
import { Stringifier } from './index.js'; | ||
import { StringDecoder } from 'string_decoder'; | ||
module.exports = function(records, options={}){ | ||
const data = [] | ||
const stringify = function(records, options={}){ | ||
const data = []; | ||
if(Buffer.isBuffer(records)){ | ||
const decoder = new StringDecoder() | ||
records = decoder.write(records) | ||
const decoder = new StringDecoder(); | ||
records = decoder.write(records); | ||
} | ||
function onData(record){ | ||
if(record){ | ||
data.push(record.toString()) | ||
data.push(record.toString()); | ||
} | ||
} | ||
let stringifier = new stringify.Stringifier(options) | ||
const stringifier = new Stringifier(options); | ||
stringifier.on('data', onData); | ||
for(let record of records){ | ||
stringifier.write(record) | ||
for(const record of records){ | ||
stringifier.write(record); | ||
} | ||
stringifier.end() | ||
stringifier.off('data', onData); | ||
return data.join('') | ||
} | ||
stringifier.end(); | ||
stringifier.removeListener('data', onData); | ||
return data.join(''); | ||
}; | ||
// export default stringify | ||
export { stringify }; |
112
package.json
{ | ||
"version": "5.6.5", | ||
"version": "6.0.0", | ||
"name": "csv-stringify", | ||
@@ -10,66 +10,82 @@ "description": "CSV stringifier implementing the Node.js `stream.Transform` API", | ||
], | ||
"license": "MIT", | ||
"repository": { | ||
"type": "git", | ||
"url": "http://www.github.com/adaltas/node-csv-stringify" | ||
}, | ||
"homepage": "https://csv.js.org/stringify/", | ||
"author": "David Worms <david@adaltas.com> (https://www.adaltas.com)", | ||
"coffeelintConfig": { | ||
"indentation": { | ||
"level": "error", | ||
"value": 2 | ||
}, | ||
"line_endings": { | ||
"level": "error", | ||
"value": "unix" | ||
}, | ||
"max_line_length": { | ||
"level": "ignore" | ||
} | ||
}, | ||
"devDependencies": { | ||
"@babel/cli": "^7.14.8", | ||
"@babel/core": "^7.15.0", | ||
"@babel/preset-env": "^7.15.0", | ||
"@rollup/plugin-eslint": "^8.0.1", | ||
"@rollup/plugin-node-resolve": "^13.0.6", | ||
"@types/mocha": "^9.0.0", | ||
"@types/node": "^16.7.8", | ||
"@types/node": "^16.11.7", | ||
"@types/should": "^13.0.0", | ||
"babelify": "^10.0.0", | ||
"browserify": "^17.0.0", | ||
"coffeescript": "~2.5.1", | ||
"csv-generate": "^3.4.3", | ||
"coffeescript": "~2.6.1", | ||
"csv-generate": "^4.0.0", | ||
"each": "^1.2.2", | ||
"eslint": "^8.2.0", | ||
"express": "^4.17.1", | ||
"mocha": "~9.1.1", | ||
"mocha": "~9.1.3", | ||
"rollup": "^2.60.0", | ||
"rollup-plugin-node-builtins": "^2.1.2", | ||
"rollup-plugin-node-globals": "^1.4.0", | ||
"should": "~13.2.3", | ||
"ts-node": "^10.2.1", | ||
"typescript": "^4.4.2" | ||
"ts-node": "^10.4.0", | ||
"typescript": "^4.4.4" | ||
}, | ||
"exports": { | ||
".": { | ||
"import": "./lib/index.js", | ||
"require": "./dist/cjs/index.cjs" | ||
}, | ||
"./sync": { | ||
"import": "./lib/sync.js", | ||
"require": "./dist/cjs/sync.cjs" | ||
} | ||
}, | ||
"files": [ | ||
"/lib" | ||
"dist", | ||
"lib", | ||
"samples" | ||
], | ||
"main": "./lib", | ||
"homepage": "https://csv.js.org/stringify/", | ||
"license": "MIT", | ||
"main": "./dist/cjs/index.cjs", | ||
"mocha": { | ||
"throw-deprecation": true, | ||
"inline-diffs": true, | ||
"loader": "./test/loaders/all.mjs", | ||
"recursive": true, | ||
"reporter": "spec", | ||
"require": [ | ||
"should", | ||
"coffeescript/register", | ||
"ts-node/register" | ||
"should" | ||
], | ||
"inline-diffs": true, | ||
"timeout": 40000, | ||
"reporter": "spec", | ||
"recursive": true | ||
"throw-deprecation": true, | ||
"timeout": 40000 | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/adaltas/node-csv.git", | ||
"directory": "packages/csv-stringify" | ||
}, | ||
"scripts": { | ||
"build:babel": "cd lib && babel *.js -d es5 && cd ..", | ||
"build:browserify": "browserify lib/index.js --transform babelify --standalone stringify > lib/browser/index.js && browserify lib/sync.js --transform babelify --standalone stringify > lib/browser/sync.js", | ||
"build": "npm run build:babel && npm run build:browserify", | ||
"preversion": "cp lib/*.ts lib/es5 && git add lib/es5/*.ts", | ||
"build": "npm run build:rollup && npm run build:ts", | ||
"build:rollup": "npx rollup -c", | ||
"build:ts": "cp lib/*.ts dist/cjs && cp lib/*.ts dist/esm", | ||
"lint": "npm run lint:lib && npm run lint:samples && npm run lint:test", | ||
"lint:lib": "eslint --fix lib/*.js", | ||
"lint:samples": "eslint --fix samples/*.js", | ||
"lint:test": "coffeelint --fix test/*.coffee", | ||
"preversion": "npm run build && git add dist", | ||
"pretest": "npm run build", | ||
"test": "mocha test/**/*.{coffee,ts}" | ||
"test": "mocha 'test/**/*.{coffee,ts}'", | ||
"test:legacy": "mocha --loader=./test/loaders/legacy/all.mjs 'test/**/*.{coffee,ts}'" | ||
}, | ||
"types": "./lib/index.d.ts", | ||
"gitHead": "0fd5209b6862655c384cda052abf38019b959e70" | ||
"type": "module", | ||
"types": "dist/esm/index.d.ts", | ||
"typesVersions": { | ||
"*": { | ||
".": [ | ||
"dist/esm/index.d.ts" | ||
], | ||
"sync": [ | ||
"dist/esm/sync.d.ts" | ||
] | ||
} | ||
}, | ||
"gitHead": "d95c35dc7fd698e8e3278f539c8f1d43fb77640b" | ||
} |
[![Build Status](https://api.travis-ci.org/adaltas/node-csv-stringify.svg)](https://travis-ci.org/#!/adaltas/node-csv-stringify) [![NPM](https://img.shields.io/npm/dm/csv-stringify)](https://www.npmjs.com/package/csv-stringify) [![NPM](https://img.shields.io/npm/v/csv-stringify)](https://www.npmjs.com/package/csv-stringify) | ||
# CSV stringifier for Node.js and the web | ||
This package is a stringifier converting records into a CSV text and | ||
[![Build Status](https://img.shields.io/github/workflow/status/adaltas/node-csv/Node.js)](https://github.com/adaltas/node-csv/actions) | ||
[![NPM](https://img.shields.io/npm/dm/csv-stringify)](https://www.npmjs.com/package/csv-stringify) | ||
[![NPM](https://img.shields.io/npm/v/csv-stringify)](https://www.npmjs.com/package/csv-stringify) | ||
The [`csv-stringify` package](https://csv.js.org/stringify/) is a stringifier converting records into a CSV text and | ||
implementing the Node.js [`stream.Transform` | ||
@@ -13,6 +17,6 @@ API](https://nodejs.org/api/stream.html). It also provides the easier | ||
* [Project homepage](http://csv.js.org/stringify/) | ||
* [API](http://csv.js.org/stringify/api/) | ||
* [Options](http://csv.js.org/stringify/options/) | ||
* [Examples](http://csv.js.org/stringify/examples/) | ||
* [Project homepage](https://csv.js.org/stringify/) | ||
* [API](https://csv.js.org/stringify/api/) | ||
* [Options](https://csv.js.org/stringify/options/) | ||
* [Examples](https://csv.js.org/stringify/examples/) | ||
@@ -32,18 +36,20 @@ ## Main features | ||
The module is built on the Node.js Stream API. For the sake of simplicity, a | ||
simple callback API is also provided. To give you a quick look, here's an | ||
example of the callback API: | ||
Run `npm install csv` to install the full csv module or run `npm install csv-stringify` if you are only interested by the CSV stringifier. | ||
```javascript | ||
const stringify = require('csv-stringify') | ||
const assert = require('assert') | ||
// import stringify from 'csv-stringify' | ||
// import assert from 'assert/strict' | ||
The module is built on the Node.js Stream API. Use the callback and sync APIs for simplicity or the stream based API for scalability. | ||
const input = [ [ '1', '2', '3', '4' ], [ 'a', 'b', 'c', 'd' ] ] | ||
stringify(input, function(err, output) { | ||
const expected = '1,2,3,4\na,b,c,d\n' | ||
assert.strictEqual(output, expected, `output.should.eql ${expected}`) | ||
console.log("Passed.", output) | ||
}) | ||
## Example | ||
The [API](https://csv.js.org/stringify/api/) is available in multiple flavors. This example illustrates the sync API. | ||
```js | ||
import { stringify } from 'csv-stringify/sync'; | ||
import assert from 'assert'; | ||
const output = stringify([ | ||
[ '1', '2', '3', '4' ], | ||
[ 'a', 'b', 'c', 'd' ] | ||
]); | ||
assert.equal(output, '1,2,3,4\na,b,c,d\n'); | ||
``` | ||
@@ -68,2 +74,4 @@ | ||
The project is sponsored by [Adaltas](https://www.adaltas.com), an Big Data consulting firm based in Paris, France. | ||
* David Worms: <https://github.com/wdavidw> | ||
@@ -73,3 +81,3 @@ | ||
[stream_transform]: http://nodejs.org/api/stream.html#stream_class_stream_transform | ||
[examples]: http://csv.js.org/stringify/examples/ | ||
[examples]: https://csv.js.org/stringify/examples/ | ||
[csv]: https://github.com/adaltas/node-csv |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
1553837
41
39557
80
0
Yes
17
1