Skip to content

Commit 3094234

Browse files
committed
fix: preserve numeric precision and leading zeros
- Fix issue with large numbers losing precision - Preserve leading zeros in numeric strings - Refactor StringUtils for better maintainability - Add comprehensive test coverage - Update documentation with clear examples No breaking changes - maintains backward compatibility while fixing precision issues. close #74
1 parent 8e29db3 commit 3094234

3 files changed

Lines changed: 349 additions & 164 deletions

File tree

README.md

Lines changed: 46 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -221,13 +221,49 @@ If the header is not on the first line you can define the header index like:
221221
Empty rows are ignored and not parsed.
222222

223223
#### Format property value by type
224-
If you want that a number will be printed as a Number type, and values *true* or *false* is printed as a boolean Type, use:
224+
The `formatValueByType()` function intelligently converts string values to their appropriate types while preserving data integrity. To enable automatic type conversion:
225+
225226
```js
226-
csvToJson.formatValueByType()
227-
.getJsonFromCsv(fileInputName);
227+
csvToJson.formatValueByType()
228+
.getJsonFromCsv(fileInputName);
229+
```
230+
231+
This conversion follows these rules:
232+
233+
##### Numbers
234+
- Regular integers and decimals are converted to Number type
235+
- Numbers with leading zeros are preserved as strings (e.g., "0012" stays "0012")
236+
- Large integers outside JavaScript's safe range are preserved as strings
237+
- Valid decimal numbers are converted to Number type
238+
239+
For example:
240+
```json
241+
{
242+
"normalInteger": 42, // Converted to number
243+
"decimal": 3.14, // Converted to number
244+
"leadingZeros": "0012345", // Kept as string to preserve leading zeros
245+
"largeNumber": "9007199254740992" // Kept as string to preserve precision
246+
}
247+
```
248+
249+
##### Boolean
250+
Case-insensitive "true" or "false" strings are converted to boolean values:
251+
```json
252+
{
253+
"registered": true, // From "true" or "TRUE" or "True"
254+
"active": false // From "false" or "FALSE" or "False"
255+
}
256+
```
257+
258+
##### Complete Example
259+
Input CSV:
260+
```csv
261+
first_name;last_name;email;gender;age;id;zip;registered
262+
Constantin;Langsdon;clangsdon0@hc360.com;Male;96;00123;123;true
263+
Norah;Raison;nraison1@wired.com;Female;32;987;00456;FALSE
228264
```
229-
For example:
230265

266+
Output JSON:
231267
```json
232268
[
233269
{
@@ -236,38 +272,22 @@ For example:
236272
"email": "clangsdon0@hc360.com",
237273
"gender": "Male",
238274
"age": 96,
239-
"zip": 123,
240-
"registered": true
275+
"id": "00123", // Preserved leading zeros
276+
"zip": 123, // Converted to number
277+
"registered": true // Converted to boolean
241278
},
242279
{
243280
"first_name": "Norah",
244281
"last_name": "Raison",
245282
"email": "nraison1@wired.com",
246283
"gender": "Female",
247284
"age": 32,
248-
"zip": "",
249-
"registered": false
285+
"id": "987",
286+
"zip": "00456", // Preserved leading zeros
287+
"registered": false // Case-insensitive boolean conversion
250288
}
251289
]
252290
```
253-
##### Number
254-
The property **age** is printed as
255-
```json
256-
"age": 32
257-
```
258-
instead of
259-
```json
260-
"age": "32"
261-
```
262-
##### Boolean
263-
The property **registered** is printed as
264-
```json
265-
"registered": true
266-
```
267-
instead of
268-
```json
269-
"registered": "true"
270-
```
271291

272292
#### Encoding
273293
You can read and decode files with the following encoding:

src/util/stringUtils.js

Lines changed: 90 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,109 @@
11
'use strict';
22

33
class StringUtils {
4+
// Regular expressions as constants for better maintainability
5+
static PATTERNS = {
6+
INTEGER: /^-?\d+$/,
7+
FLOAT: /^-?\d*\.\d+$/,
8+
WHITESPACE: /\s/g
9+
};
410

5-
trimPropertyName(isTrimHeaderFieldWhiteSpace,value) {
6-
if(isTrimHeaderFieldWhiteSpace) {
7-
return value.replace(/\s/g, '');
11+
static BOOLEAN_VALUES = {
12+
TRUE: 'true',
13+
FALSE: 'false'
14+
};
15+
16+
/**
17+
* Removes whitespace from property names based on configuration
18+
* @param {boolean} shouldTrimAll - If true, removes all whitespace, otherwise only trims edges
19+
* @param {string} propertyName - The property name to process
20+
* @returns {string} The processed property name
21+
*/
22+
trimPropertyName(shouldTrimAll, propertyName) {
23+
if (!propertyName) {
24+
return '';
825
}
9-
return value.trim();
10-
26+
return shouldTrimAll ?
27+
propertyName.replace(StringUtils.PATTERNS.WHITESPACE, '') :
28+
propertyName.trim();
1129
}
1230

31+
/**
32+
* Converts a string value to its appropriate type while preserving data integrity
33+
* @param {string} value - The input value to convert
34+
* @returns {string|number|boolean} The converted value
35+
*/
1336
getValueFormatByType(value) {
14-
if(value === undefined || value === ''){
37+
if (this.isEmpty(value)) {
1538
return String();
1639
}
17-
//is Number
18-
let isNumber = !isNaN(value);
19-
if (isNumber) {
20-
return Number(value);
40+
41+
if (this.isBoolean(value)) {
42+
return this.convertToBoolean(value);
43+
}
44+
45+
if (this.isInteger(value)) {
46+
return this.convertInteger(value);
2147
}
22-
// is Boolean
23-
if(value === "true" || value === "false"){
24-
return JSON.parse(value.toLowerCase());
48+
49+
if (this.isFloat(value)) {
50+
return this.convertFloat(value);
2551
}
52+
2653
return String(value);
2754
}
2855

29-
hasContent(values) {
30-
if (values.length > 0) {
31-
for (let i = 0; i < values.length; i++) {
32-
if (values[i]) {
33-
return true;
34-
}
35-
}
56+
/**
57+
* Checks if a value array contains any non-empty values
58+
* @param {Array} values - Array to check for content
59+
* @returns {boolean} True if array has any non-empty values
60+
*/
61+
hasContent(values = []) {
62+
return Array.isArray(values) &&
63+
values.some(value => Boolean(value));
64+
}
65+
66+
// Private helper methods for type checking and conversion
67+
isEmpty(value) {
68+
return value === undefined || value === '';
69+
}
70+
71+
isBoolean(value) {
72+
const normalizedValue = value.toLowerCase();
73+
return normalizedValue === StringUtils.BOOLEAN_VALUES.TRUE ||
74+
normalizedValue === StringUtils.BOOLEAN_VALUES.FALSE;
75+
}
76+
77+
isInteger(value) {
78+
return StringUtils.PATTERNS.INTEGER.test(value);
79+
}
80+
81+
isFloat(value) {
82+
return StringUtils.PATTERNS.FLOAT.test(value);
83+
}
84+
85+
hasLeadingZero(value) {
86+
const isPositiveWithLeadingZero = value.length > 1 && value[0] === '0';
87+
const isNegativeWithLeadingZero = value.length > 2 && value[0] === '-' && value[1] === '0';
88+
return isPositiveWithLeadingZero || isNegativeWithLeadingZero;
89+
}
90+
91+
convertToBoolean(value) {
92+
return JSON.parse(value.toLowerCase());
93+
}
94+
95+
convertInteger(value) {
96+
if (this.hasLeadingZero(value)) {
97+
return String(value);
3698
}
37-
return false;
99+
100+
const num = Number(value);
101+
return Number.isSafeInteger(num) ? num : String(value);
102+
}
103+
104+
convertFloat(value) {
105+
const num = Number(value);
106+
return Number.isFinite(num) ? num : String(value);
38107
}
39108
}
40109

0 commit comments

Comments
 (0)