Skip to content

Commit 02336b8

Browse files
authored
Merge pull request #111 from mrodrig/fix-109
Fixes #109
2 parents 3564e45 + b68556b commit 02336b8

File tree

11 files changed

+293
-179
lines changed

11 files changed

+293
-179
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,10 +176,10 @@ $ npm run coverage
176176

177177
Current Coverage is:
178178
```
179-
Statements : 100% ( 272/272 )
180-
Branches : 100% ( 143/143 )
179+
Statements : 100% ( 275/275 )
180+
Branches : 100% ( 149/149 )
181181
Functions : 100% ( 49/49 )
182-
Lines : 100% ( 266/266 )
182+
Lines : 100% ( 269/269 )
183183
```
184184

185185
## Frequently Asked Questions (FAQ)

package-lock.json

Lines changed: 201 additions & 161 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
},
66
"name": "json-2-csv",
77
"description": "A JSON to CSV and CSV to JSON converter that natively supports sub-documents and auto-generates the CSV heading.",
8-
"version": "3.4.1",
8+
"version": "3.4.2",
99
"repository": {
1010
"type": "git",
1111
"url": "https://github.com/mrodrig/json-2-csv.git"
@@ -21,7 +21,7 @@
2121
"types": "./src/converter.d.ts",
2222
"scripts": {
2323
"test": "mocha test/tests.js",
24-
"coverage": "istanbul cover mocha -- -R spec",
24+
"coverage": "istanbul cover _mocha -- -R spec",
2525
"lint": "npm run lint:eslint && npm run lint:tslint",
2626
"lint:eslint": "eslint src bin test",
2727
"lint:tslint": "tslint -c tslint.json 'src/**/*.ts'"
@@ -47,11 +47,11 @@
4747
},
4848
"devDependencies": {
4949
"commander": "2.19.0",
50-
"eslint": "5.11.1",
50+
"eslint": "5.15.3",
5151
"istanbul": "0.4.5",
5252
"mocha": "5.2.0",
5353
"should": "13.2.3",
54-
"tslint": "5.12.1",
54+
"tslint": "5.14.0",
5555
"typescript": "3.3.3"
5656
},
5757
"engines": {

src/converter.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,6 @@ export function json2csv(data: object[],
7474
export function json2csvAsync(data: object[], options?: IFullOptions): Promise<string>;
7575

7676
export function csv2json(csv: string,
77-
callback: (err?: Error, data?: Array<unknown>) => void, options?: ISharedOptions): void;
77+
callback: (err?: Error, data?: unknown[]) => void, options?: ISharedOptions): void;
7878

79-
export function csv2jsonAsync(csv: string, options?: ISharedOptions): Promise<Array<unknown>>;
79+
export function csv2jsonAsync(csv: string, options?: ISharedOptions): Promise<unknown[]>;

src/csv2json.js

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -93,40 +93,65 @@ const Csv2Json = function(options) {
9393
// Next character
9494
charAfter = index < lastCharacterIndex ? line[index + 1] : '';
9595

96-
if (index === lastCharacterIndex) {
97-
// If we reached the end of the line, add the remaining value
96+
if (index === lastCharacterIndex && character === options.delimiter.field) {
97+
// If we reached the end of the line and the current character is a field delimiter...
98+
99+
// Push the value for the field that we were parsing
100+
splitLine.push(
101+
// If the start index is the current index (and since the character is a comma),
102+
// then the value being parsed is an empty value accordingly, add an empty string
103+
stateVariables.startIndex === index
104+
? ''
105+
// Otherwise there is a valid value, but we do not want to include the current character (field delimiter)
106+
: line.substring(stateVariables.startIndex, index)
107+
);
108+
109+
// Since the last character is a comma, there's still an additional implied field value trailing the comma.
110+
// Since this value is empty, we push an extra empty value
111+
splitLine.push('');
112+
} else if (index === lastCharacterIndex) {
113+
// Otherwise if we reached the end of the line (and current character is not a field delimiter)
114+
115+
// Retrieve the remaining value and add it to the split line list of values
98116
splitLine.push(line.substring(stateVariables.startIndex));
99117
} else if (character === options.delimiter.wrap && index === 0) {
100-
// If the line starts with a wrap delimiter
118+
// If the line starts with a wrap delimiter (ie. "*)
119+
101120
stateVariables.insideWrapDelimiter = true;
102121
stateVariables.parsingValue = true;
103122
stateVariables.startIndex = index;
104123
} else if (character === options.delimiter.wrap && charAfter === options.delimiter.field) {
105124
// If we reached a wrap delimiter with a field delimiter after it (ie. *",)
125+
106126
splitLine.push(line.substring(stateVariables.startIndex, index + 1));
107127
stateVariables.startIndex = index + 2; // next value starts after the field delimiter
108128
stateVariables.insideWrapDelimiter = false;
109129
stateVariables.parsingValue = false;
110-
} else if (character === options.delimiter.wrap && charBefore === options.delimiter.field && !stateVariables.insideWrapDelimiter && stateVariables.parsingValue) {
130+
} else if (character === options.delimiter.wrap && charBefore === options.delimiter.field &&
131+
!stateVariables.insideWrapDelimiter && stateVariables.parsingValue) {
111132
// If we reached a wrap delimiter with a field delimiter after it (ie. ,"*)
133+
112134
splitLine.push(line.substring(stateVariables.startIndex, index - 1));
113135
stateVariables.insideWrapDelimiter = true;
114136
stateVariables.parsingValue = true;
115137
stateVariables.startIndex = index;
116138
} else if (character === options.delimiter.wrap && charAfter === options.delimiter.wrap) {
117-
// If we run into an escaped quote
139+
// If we run into an escaped quote (ie. "") skip past the second quote
140+
118141
index += 2;
119142
continue;
120143
} else if (character === options.delimiter.field && charBefore !== options.delimiter.wrap &&
121-
// If we reached a field delimiter and are not inside the wrap delimiters (ie. *,*)
122144
charAfter !== options.delimiter.wrap && !stateVariables.insideWrapDelimiter &&
123145
stateVariables.parsingValue) {
124146
// If we reached a field delimiter and are not inside the wrap delimiters (ie. *,*)
147+
125148
splitLine.push(line.substring(stateVariables.startIndex, index));
126149
stateVariables.startIndex = index + 1;
127150
} else if (character === options.delimiter.field && charBefore === options.delimiter.wrap &&
128-
// If we reached a field delimiter, the previous character was a wrap delimiter, and the next character is not a wrap delimiter (ie. ",*)
129151
charAfter !== options.delimiter.wrap && !stateVariables.parsingValue) {
152+
// If we reached a field delimiter, the previous character was a wrap delimiter, and the
153+
// next character is not a wrap delimiter (ie. ",*)
154+
130155
stateVariables.insideWrapDelimiter = false;
131156
stateVariables.parsingValue = true;
132157
stateVariables.startIndex = index + 1;

test/config/testCsvFilesList.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ const fs = require('fs'),
2626
{key: 'extraLine', file: '../data/csv/extraLine.csv'},
2727
{key: 'noHeader', file: '../data/csv/noHeader.csv'},
2828
{key: 'sortedHeader', file: '../data/csv/sortedHeader.csv'},
29-
{key: 'emptyFieldValues', file: '../data/csv/emptyFieldValues.csv'}
29+
{key: 'emptyFieldValues', file: '../data/csv/emptyFieldValues.csv'},
30+
{key: 'csvEmptyLastValue', file: '../data/csv/csvEmptyLastValue.csv'}
3031
];
3132

3233
function readCsvFile(filePath) {

test/config/testJsonFilesList.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,6 @@ module.exports = {
1919
trimHeader: require('../data/json/trimHeader'),
2020
trimmedHeader: require('../data/json/trimmedHeader'),
2121
specifiedKeys: require('../data/json/specifiedKeys'),
22-
emptyFieldValues: require('../data/json/emptyFieldValues')
22+
emptyFieldValues: require('../data/json/emptyFieldValues'),
23+
csvEmptyLastValue: require('../data/json/csvEmptyLastValue')
2324
};

test/csv2json.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,15 @@ function runTests(jsonTestData, csvTestData) {
137137
done();
138138
});
139139
});
140+
141+
// Test case for #109
142+
it('should properly handle the cases involving an empty field value', (done) => {
143+
converter.csv2json(csvTestData.csvEmptyLastValue, (err, json) => {
144+
if (err) done(err);
145+
json.should.deepEqual(jsonTestData.csvEmptyLastValue);
146+
done();
147+
});
148+
});
140149
});
141150

142151
describe('Error Handling', () => {
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Name,Category,On Sale,On Closeout,Outsourced,Certification Required
2+
Installation,Service,X,,,
3+
"Wireless ""Wi-Fi"" Configuration",Service,X,,X,
4+
New Phone Setup,Service,X,X,X,X
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[
2+
{
3+
"Name": "Installation",
4+
"Category": "Service",
5+
"On Sale": "X",
6+
"On Closeout": "",
7+
"Outsourced": "",
8+
"Certification Required": ""
9+
},
10+
{
11+
"Name": "Wireless \"Wi-Fi\" Configuration",
12+
"Category": "Service",
13+
"On Sale": "X",
14+
"On Closeout": "",
15+
"Outsourced": "X",
16+
"Certification Required": ""
17+
},
18+
{
19+
"Name": "New Phone Setup",
20+
"Category": "Service",
21+
"On Sale": "X",
22+
"On Closeout": "X",
23+
"Outsourced": "X",
24+
"Certification Required": "X"
25+
}
26+
]

0 commit comments

Comments
 (0)