fast-json-stringify
Advanced tools
Comparing version 5.12.0 to 5.13.0
@@ -51,2 +51,10 @@ 'use strict' | ||
{ | ||
name: 'unsafe short string', | ||
schema: { | ||
type: 'string', | ||
format: 'unsafe' | ||
}, | ||
input: 'hello world' | ||
}, | ||
{ | ||
name: 'short string with double quote', | ||
@@ -66,2 +74,10 @@ schema: { | ||
{ | ||
name: 'unsafe long string without double quotes', | ||
schema: { | ||
type: 'string', | ||
format: 'unsafe' | ||
}, | ||
input: longSimpleString | ||
}, | ||
{ | ||
name: 'long string', | ||
@@ -74,2 +90,10 @@ schema: { | ||
{ | ||
name: 'unsafe long string', | ||
schema: { | ||
type: 'string', | ||
format: 'unsafe' | ||
}, | ||
input: longString | ||
}, | ||
{ | ||
name: 'number', | ||
@@ -76,0 +100,0 @@ schema: { |
18
index.js
@@ -728,4 +728,20 @@ 'use strict' | ||
return `json += serializer.asTime(${input})` | ||
} else if (schema.format === 'unsafe') { | ||
return `json += serializer.asUnsafeString(${input})` | ||
} else { | ||
return `json += serializer.asString(${input})` | ||
return ` | ||
if (typeof ${input} !== 'string') { | ||
if (${input} === null) { | ||
json += '""' | ||
} else if (${input} instanceof Date) { | ||
json += '"' + ${input}.toISOString() + '"' | ||
} else if (${input} instanceof RegExp) { | ||
json += serializer.asString(${input}.source) | ||
} else { | ||
json += serializer.asString(${input}.toString()) | ||
} | ||
} else { | ||
json += serializer.asString(${input}) | ||
} | ||
` | ||
} | ||
@@ -732,0 +748,0 @@ } |
@@ -102,19 +102,6 @@ 'use strict' | ||
asString (str) { | ||
if (typeof str !== 'string') { | ||
if (str === null) { | ||
return '""' | ||
} | ||
if (str instanceof Date) { | ||
return '"' + str.toISOString() + '"' | ||
} | ||
if (str instanceof RegExp) { | ||
str = str.source | ||
} else { | ||
str = str.toString() | ||
} | ||
} | ||
if (str.length < 42) { | ||
return this.asStringSmall(str) | ||
} else if (STR_ESCAPE.test(str) === false) { | ||
} else if (str.length < 5000 && STR_ESCAPE.test(str) === false) { | ||
// Only use the regular expression for shorter input. The overhead is otherwise too much. | ||
return '"' + str + '"' | ||
@@ -126,2 +113,6 @@ } else { | ||
asUnsafeString (str) { | ||
return '"' + str + '"' | ||
} | ||
// magically escape strings for json | ||
@@ -142,9 +133,6 @@ // relying on their charCodeAt | ||
point = str.charCodeAt(i) | ||
if (point < 32) { | ||
if (point < 32 || (point >= 0xD800 && point <= 0xDFFF)) { | ||
// The current character is non-printable characters or a surrogate. | ||
return JSON.stringify(str) | ||
} | ||
if (point >= 0xD800 && point <= 0xDFFF) { | ||
// The current character is a surrogate. | ||
return JSON.stringify(str) | ||
} | ||
if ( | ||
@@ -151,0 +139,0 @@ point === 0x22 || // '"' |
{ | ||
"name": "fast-json-stringify", | ||
"version": "5.12.0", | ||
"version": "5.13.0", | ||
"description": "Stringify your JSON at max speed", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -633,2 +633,20 @@ # fast-json-stringify | ||
<a name="unsafe"></a> | ||
#### Unsafe string | ||
By default, the library escapes all strings. With the 'unsafe' format, the string isn't escaped. This has a potentially dangerous security issue. You can use it only if you are sure that your data doesn't need escaping. The advantage is a significant performance improvement. | ||
Example: | ||
```javascript | ||
const stringify = fastJson({ | ||
title: 'Example Schema', | ||
type: 'object', | ||
properties: { | ||
'code': { | ||
type: 'string', | ||
format 'unsafe' | ||
} | ||
} | ||
}) | ||
``` | ||
##### Benchmarks | ||
@@ -635,0 +653,0 @@ |
@@ -22,2 +22,8 @@ 'use strict' | ||
buildTest({ | ||
title: 'string', | ||
type: 'string', | ||
format: 'unsafe' | ||
}, 'hello world') | ||
buildTest({ | ||
title: 'basic', | ||
@@ -24,0 +30,0 @@ type: 'object', |
@@ -51,1 +51,35 @@ 'use strict' | ||
}) | ||
test('unsafe string', (t) => { | ||
t.plan(2) | ||
const schema = { | ||
type: 'string', | ||
format: 'unsafe' | ||
} | ||
const input = 'abcd' | ||
const stringify = build(schema) | ||
const output = stringify(input) | ||
t.equal(output, `"${input}"`) | ||
t.equal(JSON.parse(output), input) | ||
}) | ||
test('unsafe unescaped string', (t) => { | ||
t.plan(2) | ||
const schema = { | ||
type: 'string', | ||
format: 'unsafe' | ||
} | ||
const input = 'abcd "abcd"' | ||
const stringify = build(schema) | ||
const output = stringify(input) | ||
t.equal(output, `"${input}"`) | ||
t.throws(function () { | ||
JSON.parse(output) | ||
}) | ||
}) |
@@ -211,2 +211,5 @@ import Ajv, { Options as AjvOptions } from "ajv" | ||
type StringCoercible = string | Date | RegExp; | ||
type IntegerCoercible = number | BigInt; | ||
/** | ||
@@ -221,4 +224,4 @@ * Build a stringify function using a schema of the documents that should be stringified | ||
declare function build(schema: build.AnySchema, options?: build.Options): <TDoc = any>(doc: TDoc) => any; | ||
declare function build(schema: build.StringSchema, options?: build.Options): <TDoc extends string = string>(doc: TDoc) => string; | ||
declare function build(schema: build.IntegerSchema | build.NumberSchema, options?: build.Options): <TDoc extends number = number>(doc: TDoc) => string; | ||
declare function build(schema: build.StringSchema, options?: build.Options): <TDoc extends StringCoercible = StringCoercible>(doc: TDoc) => string; | ||
declare function build(schema: build.IntegerSchema | build.NumberSchema, options?: build.Options): <TDoc extends IntegerCoercible = IntegerCoercible>(doc: TDoc) => string; | ||
declare function build(schema: build.NullSchema, options?: build.Options): <TDoc extends null = null>(doc: TDoc) => "null"; | ||
@@ -225,0 +228,0 @@ declare function build(schema: build.BooleanSchema, options?: build.Options): <TDoc extends boolean = boolean>(doc: TDoc) => string; |
@@ -6,57 +6,58 @@ import Ajv from 'ajv' | ||
// Number schemas | ||
const schema1: Schema = { | ||
build({ | ||
type: 'number' | ||
} | ||
const schema2: Schema = { | ||
})(25) | ||
build({ | ||
type: 'integer' | ||
} | ||
})(-5) | ||
build({ | ||
type: 'integer' | ||
})(5n) | ||
build(schema1)(25) | ||
build(schema2)(-5) | ||
build({ | ||
type: 'number' | ||
}, { rounding: 'ceil' }) | ||
build({ | ||
type: 'number' | ||
}, { rounding: 'floor' }) | ||
build({ | ||
type: 'number' | ||
}, { rounding: 'round' }) | ||
build({ | ||
type: 'number' | ||
}, { rounding: 'trunc' }) | ||
expectError(build({ | ||
type: 'number' | ||
}, { rounding: 'invalid' })) | ||
build(schema2, { rounding: 'ceil' }) | ||
build(schema2, { rounding: 'floor' }) | ||
build(schema2, { rounding: 'round' }) | ||
build(schema2, { rounding: 'trunc' }) | ||
expectError(build(schema2, { rounding: 'invalid' })) | ||
// String schema | ||
const schema3: Schema = { | ||
type: 'string' | ||
} | ||
build({ | ||
type: 'string' | ||
})('foobar') | ||
build(schema3)('foobar') | ||
// Boolean schema | ||
const schema4: Schema = { | ||
build({ | ||
type: 'boolean' | ||
} | ||
})(true) | ||
build(schema4)(true) | ||
// Null schema | ||
const schema5: Schema = { | ||
build({ | ||
type: 'null' | ||
} | ||
})(null) | ||
build(schema5)(null) | ||
// Array schemas | ||
const schema6: Schema = { | ||
build({ | ||
type: 'array', | ||
items: { type: 'number' } | ||
} | ||
const schema7: Schema = { | ||
})([25]) | ||
build({ | ||
type: 'array', | ||
items: [{ type: 'string'}, {type: 'integer'}] | ||
} | ||
})(['hello', 42]) | ||
build(schema6)([25]) | ||
build(schema7)(['hello', 42]) | ||
// Object schemas | ||
const schema8: Schema = { | ||
build({ | ||
type: 'object' | ||
} | ||
const schema9: Schema = { | ||
})({}) | ||
build({ | ||
type: 'object', | ||
@@ -74,10 +75,20 @@ properties: { | ||
} | ||
} | ||
})({ foo: 'bar' }) | ||
build({ | ||
type: 'object', | ||
properties: { | ||
foo: { type: 'string' }, | ||
bar: { type: 'integer' } | ||
}, | ||
required: ['foo'], | ||
patternProperties: { | ||
'baz*': { type: 'null' } | ||
}, | ||
additionalProperties: { | ||
type: 'boolean' | ||
} | ||
}, { rounding: 'floor' })({ foo: 'bar' }) | ||
build(schema8)({}) | ||
build(schema9)({ foo: 'bar' }) | ||
build(schema9, { rounding: 'floor' })({ foo: 'bar' }) | ||
// Reference schemas | ||
const schema10: Schema = { | ||
build({ | ||
title: 'Example Schema', | ||
@@ -114,8 +125,6 @@ definitions: { | ||
} | ||
} | ||
})({ nickname: '', num: { int: 5 }, other: null }) | ||
build(schema10)({ nickname: '', num: { int: 5 }, other: null }) | ||
// Conditional/Combined schemas | ||
const schema11: Schema = { | ||
build({ | ||
title: 'Conditional/Combined Schema', | ||
@@ -146,20 +155,33 @@ type: 'object', | ||
} | ||
} | ||
})({ something: 'a string', somethingElse: 42 }) | ||
build(schema11)({ something: 'a string', somethingElse: 42 }) | ||
// String schema with format | ||
// String schema with format | ||
const schema12: Schema = { | ||
build({ | ||
type: 'string', | ||
format: 'date-time' | ||
} | ||
})(new Date()) | ||
build(schema12)(new Date()) | ||
/* | ||
This overload doesn't work yet - | ||
TypeScript chooses the generic for the schema | ||
before it chooses the overload for the options | ||
parameter. | ||
let str: string, ajv: Ajv | ||
str = build(schema1, { debugMode: true }).code | ||
ajv = build(schema1, { debugMode: true }).ajv | ||
str = build(schema1, { mode: 'debug' }).code | ||
ajv = build(schema1, { mode: 'debug' }).ajv | ||
str = build(schema1, { mode: 'standalone' }) | ||
str = build({ | ||
type: 'number' | ||
}, { debugMode: true }).code | ||
ajv = build({ | ||
type: 'number' | ||
}, { debugMode: true }).ajv | ||
str = build({ | ||
type: 'number' | ||
}, { mode: 'debug' }).code | ||
ajv = build({ | ||
type: 'number' | ||
}, { mode: 'debug' }).ajv | ||
str = build({ | ||
type: 'number' | ||
}, { mode: 'standalone' }) | ||
*/ | ||
@@ -166,0 +188,0 @@ const debugCompiled = build({ |
363587
13648
740
160
2
5
2
16