/** * Converts the given JSON object array to a CSV string. * @param data - the array of JSON objects to convert. */ export const convertToCSV = (data: any) => { const replacer = (key: any, value: any) => (value === null ? '' : value) const headerFields = Object.keys(data[0]) let csvTest let invalidString = false const headers = headerFields.map((field) => { let firstFoundType: string | null = null let hasMixedTypes: boolean = false let rowNumError: number = -1 const longestValueForField = data .map((row: any, index: number) => { if (row[field] || row[field] === '') { if (firstFoundType) { let currentFieldType = row[field] === '' || typeof row[field] === 'string' ? 'chars' : 'number' if (!hasMixedTypes) { hasMixedTypes = currentFieldType !== firstFoundType rowNumError = hasMixedTypes ? index + 1 : -1 } } else { if (row[field] === '') { firstFoundType = 'chars' } else { firstFoundType = typeof row[field] === 'string' ? 'chars' : 'number' } } let byteSize if (typeof row[field] === 'string') { byteSize = getByteSize(row[field]) } return byteSize } }) .sort((a: number, b: number) => b - a)[0] if (longestValueForField && longestValueForField > 32765) { invalidString = true } if (hasMixedTypes) { console.error( `Row (${rowNumError}), Column (${field}) has mixed types: ERROR` ) } return `${field}:${firstFoundType === 'chars' ? '$char' : ''}${ longestValueForField ? longestValueForField : firstFoundType === 'chars' ? '1' : 'best' }.` }) if (invalidString) { return 'ERROR: LARGE STRING LENGTH' } csvTest = data.map((row: any) => { const fields = Object.keys(row).map((fieldName, index) => { let value const currentCell = row[fieldName] if (typeof currentCell === 'number') return currentCell // stringify with replacer converts null values to empty strings value = currentCell === null ? '' : currentCell // if there any present, it should have preceding (") for escaping value = value.replace(/"/g, `""`) // also wraps the value in double quotes value = `"${value}"` if ( value.substring(1, value.length - 1).search(/(\t|\n|\r|,|\'|\")/gm) < 0 ) { // Remove wrapping quotes for values that don't contain special characters value = value.substring(1, value.length - 1) } value = value.replace(/\r\n/gm, '\n') if (value === '' && headers[index].includes('best')) { value = '.' } return value }) return fields.join(',') }) let finalCSV = headers.join(',').replace(/,/g, ' ') + '\r\n' + csvTest.join('\r\n') return finalCSV } const getByteSize = (str: string) => { let byteSize = str.length for (let i = str.length - 1; i >= 0; i--) { const code = str.charCodeAt(i) if (code > 0x7f && code <= 0x7ff) byteSize++ else if (code > 0x7ff && code <= 0xffff) byteSize += 2 if (code >= 0xdc00 && code <= 0xdfff) i-- //trail surrogate } return byteSize }