Skip to content

Commit

Permalink
Merge pull request #111 from AthennaIO/develop
Browse files Browse the repository at this point in the history
feat(parser): add csv parser
  • Loading branch information
jlenon7 authored Feb 5, 2024
2 parents 0699d58 + bed3e6a commit f0458cc
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 8 deletions.
20 changes: 17 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@athenna/common",
"version": "4.30.0",
"version": "4.31.0",
"description": "The Athenna common helpers to use in any Node.js ESM project.",
"license": "MIT",
"author": "João Lenon <[email protected]>",
Expand Down Expand Up @@ -60,6 +60,7 @@
"chalk": "^5.3.0",
"change-case": "^4.1.2",
"collect.js": "^4.36.1",
"csv-parser": "^3.0.0",
"execa": "^8.0.1",
"fastify": "^4.25.2",
"got": "^12.6.1",
Expand Down
63 changes: 60 additions & 3 deletions src/helpers/Parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import ms from 'ms'
import bytes from 'bytes'
import yaml from 'js-yaml'
import csvParser from 'csv-parser'

import { Is } from '#src/helpers/Is'
import { String } from '#src/helpers/String'
Expand All @@ -20,6 +21,21 @@ import { getReasonPhrase, getStatusCode } from 'http-status-codes'
import { InvalidNumberException } from '#src/exceptions/InvalidNumberException'

export class Parser {
/**
* Parse using Node.js streams, useful for
* parsing multiple values in files.
*/
public static stream() {
return {
/**
* Parse a csv chunk to json object.
*/
csvToJson: (options?: csvParser.Options | string[]) => {
return csvParser(options)
}
}
}

/**
* Parse a string to array.
*/
Expand Down Expand Up @@ -50,14 +66,14 @@ export class Parser {
return `${values[0]}${options?.pairSeparator || ' and '}${values[1]}`
}

const normalized = Options.create(options, {
options = Options.create(options, {
separator: ', ',
lastSeparator: ' and '
})

return (
values.slice(0, -1).join(normalized.separator) +
normalized.lastSeparator +
values.slice(0, -1).join(options.separator) +
options.lastSeparator +
values[values.length - 1]
)
}
Expand Down Expand Up @@ -162,6 +178,47 @@ export class Parser {
return ms(value, { long })
}

/**
* Parses a json to a csv string.
*/
public static jsonToCsv(
value: Record<string, any>,
options: { headers?: string[] } = {}
) {
options = Options.create(options, {
headers: Object.keys(value)
})

let csv = ''

if (!Is.Empty(options.headers)) {
csv +=
Parser.arrayToString(options.headers, {
separator: ',',
pairSeparator: ',',
lastSeparator: ','
}) + '\n'
}

const values = []

Object.keys(value).forEach(key => {
if (!Is.Empty(options.headers) && !options.headers.includes(key)) {
return
}

values.push(value[key])
})

csv += Parser.arrayToString(values, {
separator: ',',
pairSeparator: ',',
lastSeparator: ','
})

return csv
}

/**
* Parses the status code number to it reason in string.
*/
Expand Down
3 changes: 3 additions & 0 deletions tests/fixtures/resources/data.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
id,name
1,lenon
2,victor
45 changes: 44 additions & 1 deletion tests/unit/ParserTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* file that was distributed with this source code.
*/

import { Parser } from '#src'
import { File, Parser } from '#src'
import { Test, type Context } from '@athenna/test'
import { InvalidNumberException } from '#src/exceptions/InvalidNumberException'

Expand Down Expand Up @@ -279,4 +279,47 @@ export default class ParserTest {

assert.deepEqual(object, { version: 3 })
}

@Test()
public async shouldBeAbleToParseObjectToCsv({ assert }: Context) {
const csv = Parser.jsonToCsv({ id: 1, name: 'lenon' })

assert.deepEqual(csv, 'id,name\n1,lenon')
}

@Test()
public async shouldBeAbleToParseObjectToCsvWithoutHeaders({ assert }: Context) {
const csv = Parser.jsonToCsv({ id: 1, name: 'lenon' }, { headers: [] })

assert.deepEqual(csv, '1,lenon')
}

@Test()
public async shouldBeAbleToParseObjectToCsvOnlyWithDefinedHeaders({ assert }: Context) {
const csv = Parser.jsonToCsv({ id: 1, name: 'lenon' }, { headers: ['id'] })

assert.deepEqual(csv, 'id\n1')
}

@Test()
public async shouldBeAbleToParseACsvFileToJson({ assert }: Context) {
const getCsvData = async () => {
return new Promise(resolve => {
const data = []

new File(Path.fixtures('resources/data.csv'))
.createReadStream()
.pipe(Parser.stream().csvToJson())
.on('data', user => data.push(user))
.on('end', () => resolve(data))
})
}

const data = await getCsvData()

assert.deepEqual(data, [
{ id: '1', name: 'lenon' },
{ id: '2', name: 'victor' }
])
}
}

0 comments on commit f0458cc

Please sign in to comment.