Skip to content

Commit a0d0b4b

Browse files
authored
add float truncation (#278)
* add float truncation * reduce string concatenation * add rounding option * add types
1 parent 5682018 commit a0d0b4b

File tree

5 files changed

+81
-13
lines changed

5 files changed

+81
-13
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ fast-json-stringify obj x 13,537,123 ops/sec ±0.19% (95 runs sampled)
4040
- <a href="#anyof">`AnyOf`</a>
4141
- <a href="#ref">`Reuse - $ref`</a>
4242
- <a href="#long">`Long integers`</a>
43+
- <a href="#integer">`Integers`</a>
4344
- <a href="#nullable">`Nullable`</a>
4445
- <a href="#security">`Security Notice`</a>
4546
- <a href="#acknowledgements">`Acknowledgements`</a>
@@ -533,6 +534,16 @@ const obj = {
533534
console.log(stringify(obj)) // '{"id":18446744073709551615}'
534535
```
535536

537+
<a name="integer"></a>
538+
#### Integers
539+
The `type: integer` property will be truncated if a floating point is provided.
540+
You can customize this behaviour with the `rounding` option that will accept [`round`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round), [`ceil`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/ceil) or [`floor`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/floor):
541+
542+
```js
543+
const stringify = fastJson(schema, { rounding: 'ceil' })
544+
```
545+
546+
536547
<a name="nullable"></a>
537548
#### Nullable
538549

index.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,10 @@ declare namespace build {
145145
* Configure Ajv, which is used to evaluate conditional schemas and combined (anyOf) schemas
146146
*/
147147
ajv?: AjvOptions
148+
/**
149+
* Optionally configure how the integer will be rounded
150+
*/
151+
rounding?: 'ceil' | 'floor' | 'round'
148152
}
149153
}
150154

index.js

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,15 @@ function build (schema, options) {
5454
}
5555
}
5656

57+
var intParseFunctionName = 'trunc'
58+
if (options.rounding) {
59+
if (['floor', 'ceil', 'round'].includes(options.rounding)) {
60+
intParseFunctionName = options.rounding
61+
} else {
62+
throw new Error(`Unsupported integer rounding method ${options.rounding}`)
63+
}
64+
}
65+
5766
/* eslint no-new-func: "off" */
5867
let code = `
5968
'use strict'
@@ -69,23 +78,16 @@ function build (schema, options) {
6978
${$asTime.toString()}
7079
${$asNumber.toString()}
7180
${$asNumberNullable.toString()}
81+
${$asInteger.toString()}
7282
${$asIntegerNullable.toString()}
7383
${$asNull.toString()}
7484
${$asBoolean.toString()}
7585
${$asBooleanNullable.toString()}
76-
`
7786
78-
// only handle longs if the module is used
79-
if (isLong) {
80-
code += `
81-
var isLong = ${isLong.toString()}
82-
${$asInteger.toString()}
83-
`
84-
} else {
85-
code += `
86-
var $asInteger = $asNumber
87+
var isLong = ${isLong ? isLong.toString() : false}
88+
89+
function parseInteger(int) { return Math.${intParseFunctionName}(int) }
8790
`
88-
}
8991

9092
let location = {
9193
schema,
@@ -226,7 +228,8 @@ const stringSerializerMap = {
226228
}
227229

228230
function getStringSerializer (format) {
229-
return stringSerializerMap[format] || '$asString'
231+
return stringSerializerMap[format] ||
232+
'$asString'
230233
}
231234

232235
function $pad2Zeros (num) {
@@ -243,8 +246,12 @@ function $asInteger (i) {
243246
return i.toString()
244247
} else if (typeof i === 'bigint') {
245248
return i.toString()
246-
} else {
249+
} else if (Number.isInteger(i)) {
247250
return $asNumber(i)
251+
} else {
252+
// if the output is NaN the type is coerced to int 0
253+
/* eslint no-undef: "off" */
254+
return $asNumber(parseInteger(i) || 0)
248255
}
249256
}
250257

test/integer.test.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,51 @@ test('render an integer as JSON', (t) => {
2323
t.ok(validate(JSON.parse(output)), 'valid schema')
2424
})
2525

26+
test('render a float as an integer', (t) => {
27+
t.plan(2)
28+
try {
29+
build({
30+
title: 'float as integer',
31+
type: 'integer'
32+
}, { rounding: 'foobar' })
33+
} catch (error) {
34+
t.ok(error)
35+
t.equals(error.message, 'Unsupported integer rounding method foobar')
36+
}
37+
})
38+
39+
test('render a float as an integer', (t) => {
40+
const cases = [
41+
{ input: Math.PI, output: '3' },
42+
{ input: 5.0, output: '5' },
43+
{ input: 1.99999, output: '1' },
44+
{ input: -45.05, output: '-45' },
45+
{ input: 0.95, output: '1', rounding: 'ceil' },
46+
{ input: 0.2, output: '1', rounding: 'ceil' },
47+
{ input: 45.95, output: '45', rounding: 'floor' },
48+
{ input: -45.05, output: '-46', rounding: 'floor' },
49+
{ input: 45.44, output: '45', rounding: 'round' },
50+
{ input: 45.95, output: '46', rounding: 'round' }
51+
]
52+
53+
t.plan(cases.length * 2)
54+
cases.forEach(checkInteger)
55+
56+
function checkInteger ({ input, output, rounding }) {
57+
const schema = {
58+
title: 'float as integer',
59+
type: 'integer'
60+
}
61+
62+
const validate = validator(schema)
63+
const stringify = build(schema, { rounding })
64+
const str = stringify(input)
65+
66+
t.equal(str, output)
67+
t.ok(validate(JSON.parse(str)), 'valid schema')
68+
}
69+
})
70+
2671
test('render an object with an integer as JSON', (t) => {
2772
t.plan(2)
2873

test/types/test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ const schema9: Schema = {
6666

6767
build(schema8)({})
6868
build(schema9)({ foo: 'bar' })
69+
build(schema9, { rounding: 'floor' })({ foo: 'bar' })
6970

7071
// Reference schemas
7172
const schema10: Schema = {

0 commit comments

Comments
 (0)