UNPKG

@sap/cds-dk

Version:

Command line client and development toolkit for the SAP Cloud Application Programming Model

97 lines (90 loc) 3.46 kB
const { elementsSorter } = require('./as-json') /** * Serializes the given JSON data to CSV format * @param {object} data JSON data to serialize * @param {object} csn the model * @param {{ headerOnly: boolean, separator: boolean }} options * @returns {Promise<string>} CSV string */ module.exports = async (data={}, csn, { headerOnly=false, separator=',' }={}) => { // We might need multiple passes if data for associations/compositions is created on the fly. // So loop as long as there is some non-string data left. while (Object.values(data).some(d => typeof d === 'object')) { Object.entries(data) .filter(([, value]) => typeof value === 'object') .map(([name]) => csn.definitions[name]) .forEach(entity => { const entityData = data[entity.name] const header = [] const records = [] for (let i = 0; i < entityData.length; i++) { let record = [] const elements = Object.values(entity.elements).sort(elementsSorter(csn)) for (const element of elements) { const name = element.name let value = entityData[i][name] if (value === undefined) continue if (element._type === 'cds.Composition') { const t = element.target if (data[t] === undefined) { data[t] = value // Add object for a new entity. Next pass will process it. } } else if (element._type === 'cds.Association') { if (element.on) { continue // skip unmanaged associations with explicit on condition } if (i === 0) { header.push(...Object.keys(value).map(foreignKey => name + '_' + foreignKey)) } record.push(...Object.values(value)) } else if (element.is_struct && element.type !== 'cds.Map') { const {headers: structHeaders, records: structRecords} = resolveStruct(value, name); if (i === 0) { header.push(...structHeaders) } record.push(...structRecords) } else { if (i === 0) header.push(name) record.push(processValue(value)) } } records.push(record.join(separator)) } let res = header.join(separator) if (!headerOnly) res += '\n' + records.join('\n') data[entity.name] = res }) function escape(value) { if (typeof value === 'string' && (value.includes('"') || value.includes(separator))) { return `"${value.replace(/"/g, '""')}"` // escape double quotes, then quote the value } return value } function processValue(v) { if (Array.isArray(v) || typeof v === 'object') { // `array of`/`many` and `cds.Map` return escape(JSON.stringify(v)) } return escape(v) } function resolveStruct(v, prefix) { return Object.entries(v).reduce((acc, [key, val]) => { //structs can be within structs if (typeof val === 'object' && !Array.isArray(val)) { const {headers, records} = resolveStruct(val, prefix + '_' + key); acc.headers = acc.headers.concat(headers); acc.records = acc.records.concat(records); } else { acc.headers.push(prefix + '_' + key); acc.records.push(processValue(val)); } return acc; }, {headers: [], records: []}); } } return data }