fast-json-stringify 5.2.0 → 5.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{build-schema-validator.js → build/build-schema-validator.js} +1 -1
- package/{example.js → examples/example.js} +1 -1
- package/{server.js → examples/server.js} +1 -1
- package/index.d.ts +9 -8
- package/index.js +195 -216
- package/{ref-resolver.js → lib/ref-resolver.js} +0 -0
- package/{schema-validator.js → lib/schema-validator.js} +0 -0
- package/{serializer.js → lib/serializer.js} +0 -36
- package/{standalone.js → lib/standalone.js} +0 -0
- package/{validator.js → lib/validator.js} +0 -0
- package/package.json +7 -3
- package/test/array.test.js +25 -0
- package/test/date.test.js +19 -0
- package/test/debug-mode.test.js +18 -5
- package/test/types/schema-inference.test-d.ts +48 -0
|
@@ -23,4 +23,4 @@ const validationCode = standaloneCode(ajv, validate)
|
|
|
23
23
|
|
|
24
24
|
const moduleCode = `/* CODE GENERATED BY '${path.basename(__filename)}' DO NOT EDIT! */\n${validationCode}`
|
|
25
25
|
|
|
26
|
-
fs.writeFileSync(path.join(__dirname, 'schema-validator.js'), moduleCode)
|
|
26
|
+
fs.writeFileSync(path.join(__dirname, '../lib/schema-validator.js'), moduleCode)
|
package/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Ajv, { Options as AjvOptions } from "ajv"
|
|
2
|
+
|
|
2
3
|
declare namespace build {
|
|
3
4
|
interface BaseSchema {
|
|
4
5
|
/**
|
|
@@ -189,13 +190,13 @@ interface StandaloneOption extends build.Options {
|
|
|
189
190
|
declare function build(schema: build.AnySchema, options: DebugOption): { code: string, ajv: Ajv };
|
|
190
191
|
declare function build(schema: build.AnySchema, options: DeprecateDebugOption): { code: string, ajv: Ajv };
|
|
191
192
|
declare function build(schema: build.AnySchema, options: StandaloneOption): string;
|
|
192
|
-
declare function build(schema: build.AnySchema, options?: build.Options): (doc:
|
|
193
|
-
declare function build(schema: build.StringSchema, options?: build.Options): (doc:
|
|
194
|
-
declare function build(schema: build.IntegerSchema | build.NumberSchema, options?: build.Options): (doc:
|
|
195
|
-
declare function build(schema: build.NullSchema, options?: build.Options): (doc:
|
|
196
|
-
declare function build(schema: build.BooleanSchema, options?: build.Options): (doc:
|
|
197
|
-
declare function build(schema: build.ArraySchema | build.TupleSchema, options?: build.Options): (doc:
|
|
198
|
-
declare function build(schema: build.ObjectSchema, options?: build.Options): (doc:
|
|
199
|
-
declare function build(schema: build.Schema, options?: build.Options):
|
|
193
|
+
declare function build(schema: build.AnySchema, options?: build.Options): <TDoc = any>(doc: TDoc) => any;
|
|
194
|
+
declare function build(schema: build.StringSchema, options?: build.Options): <TDoc extends string = string>(doc: TDoc) => string;
|
|
195
|
+
declare function build(schema: build.IntegerSchema | build.NumberSchema, options?: build.Options): <TDoc extends number = number>(doc: TDoc) => string;
|
|
196
|
+
declare function build(schema: build.NullSchema, options?: build.Options): <TDoc extends null = null>(doc: TDoc) => "null";
|
|
197
|
+
declare function build(schema: build.BooleanSchema, options?: build.Options): <TDoc extends boolean = boolean>(doc: TDoc) => string;
|
|
198
|
+
declare function build(schema: build.ArraySchema | build.TupleSchema, options?: build.Options): <TDoc extends any[]= any[]>(doc: TDoc) => string;
|
|
199
|
+
declare function build(schema: build.ObjectSchema, options?: build.Options): <TDoc extends object = object>(doc: TDoc) => string;
|
|
200
|
+
declare function build(schema: build.Schema, options?: build.Options): <TDoc = object | any[] | string | number | boolean | null> (doc: TDoc) => string;
|
|
200
201
|
|
|
201
202
|
export = build;
|
package/index.js
CHANGED
|
@@ -4,13 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
const merge = require('@fastify/deepmerge')()
|
|
6
6
|
const clone = require('rfdc')({ proto: true })
|
|
7
|
-
const fjsCloned = Symbol('fast-json-stringify.cloned')
|
|
8
7
|
const { randomUUID } = require('crypto')
|
|
9
8
|
|
|
10
|
-
const validate = require('./schema-validator')
|
|
11
|
-
const Serializer = require('./serializer')
|
|
12
|
-
const Validator = require('./validator')
|
|
13
|
-
const RefResolver = require('./ref-resolver')
|
|
9
|
+
const validate = require('./lib/schema-validator')
|
|
10
|
+
const Serializer = require('./lib/serializer')
|
|
11
|
+
const Validator = require('./lib/validator')
|
|
12
|
+
const RefResolver = require('./lib/ref-resolver')
|
|
14
13
|
|
|
15
14
|
let largeArraySize = 2e4
|
|
16
15
|
let largeArrayMechanism = 'default'
|
|
@@ -71,8 +70,7 @@ function resolveRef (location, ref) {
|
|
|
71
70
|
return { schema, schemaId, jsonPointer }
|
|
72
71
|
}
|
|
73
72
|
|
|
74
|
-
const
|
|
75
|
-
const objectReferenceSerializersMap = new Map()
|
|
73
|
+
const contextFunctionsNamesBySchema = new Map()
|
|
76
74
|
|
|
77
75
|
let rootSchemaId = null
|
|
78
76
|
let refResolver = null
|
|
@@ -80,8 +78,7 @@ let validator = null
|
|
|
80
78
|
let contextFunctions = null
|
|
81
79
|
|
|
82
80
|
function build (schema, options) {
|
|
83
|
-
|
|
84
|
-
objectReferenceSerializersMap.clear()
|
|
81
|
+
contextFunctionsNamesBySchema.clear()
|
|
85
82
|
|
|
86
83
|
contextFunctions = []
|
|
87
84
|
options = options || {}
|
|
@@ -147,12 +144,17 @@ function build (schema, options) {
|
|
|
147
144
|
}
|
|
148
145
|
|
|
149
146
|
if (options.mode === 'debug') {
|
|
150
|
-
return {
|
|
147
|
+
return {
|
|
148
|
+
validator,
|
|
149
|
+
serializer,
|
|
150
|
+
code: dependenciesName.join('\n'),
|
|
151
|
+
ajv: validator.ajv
|
|
152
|
+
}
|
|
151
153
|
}
|
|
152
154
|
|
|
153
155
|
if (options.mode === 'standalone') {
|
|
154
156
|
// lazy load
|
|
155
|
-
const buildStandaloneCode = require('./standalone')
|
|
157
|
+
const buildStandaloneCode = require('./lib/standalone')
|
|
156
158
|
return buildStandaloneCode(options, validator, contextFunctionCode)
|
|
157
159
|
}
|
|
158
160
|
|
|
@@ -164,8 +166,7 @@ function build (schema, options) {
|
|
|
164
166
|
validator = null
|
|
165
167
|
rootSchemaId = null
|
|
166
168
|
contextFunctions = null
|
|
167
|
-
|
|
168
|
-
objectReferenceSerializersMap.clear()
|
|
169
|
+
contextFunctionsNamesBySchema.clear()
|
|
169
170
|
|
|
170
171
|
return stringifyFunc
|
|
171
172
|
}
|
|
@@ -538,25 +539,18 @@ function toJSON (variableName) {
|
|
|
538
539
|
function buildObject (location) {
|
|
539
540
|
const schema = location.schema
|
|
540
541
|
|
|
541
|
-
if (
|
|
542
|
-
return
|
|
542
|
+
if (contextFunctionsNamesBySchema.has(schema)) {
|
|
543
|
+
return contextFunctionsNamesBySchema.get(schema)
|
|
543
544
|
}
|
|
544
545
|
|
|
545
546
|
const functionName = generateFuncName()
|
|
546
|
-
|
|
547
|
+
contextFunctionsNamesBySchema.set(schema, functionName)
|
|
547
548
|
|
|
548
549
|
const schemaId = location.schemaId === rootSchemaId ? '' : location.schemaId
|
|
549
550
|
let functionCode = `
|
|
550
551
|
function ${functionName} (input) {
|
|
551
552
|
// ${schemaId + location.jsonPointer}
|
|
552
553
|
`
|
|
553
|
-
if (schema.nullable) {
|
|
554
|
-
functionCode += `
|
|
555
|
-
if (input === null) {
|
|
556
|
-
return 'null';
|
|
557
|
-
}
|
|
558
|
-
`
|
|
559
|
-
}
|
|
560
554
|
|
|
561
555
|
functionCode += `
|
|
562
556
|
var obj = ${toJSON('input')}
|
|
@@ -581,33 +575,23 @@ function buildObject (location) {
|
|
|
581
575
|
}
|
|
582
576
|
|
|
583
577
|
function buildArray (location) {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
// default to any items type
|
|
587
|
-
if (!schema.items) {
|
|
588
|
-
schema.items = {}
|
|
589
|
-
}
|
|
578
|
+
const schema = location.schema
|
|
590
579
|
|
|
591
580
|
let itemsLocation = mergeLocation(location, 'items')
|
|
581
|
+
itemsLocation.schema = itemsLocation.schema || {}
|
|
592
582
|
|
|
593
|
-
if (schema
|
|
594
|
-
|
|
595
|
-
location.schema = clone(location.schema)
|
|
596
|
-
schema = location.schema
|
|
597
|
-
schema[fjsCloned] = true
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
location = resolveRef(location, schema.items.$ref)
|
|
601
|
-
itemsLocation = location
|
|
602
|
-
schema.items = location.schema
|
|
583
|
+
if (itemsLocation.schema.$ref) {
|
|
584
|
+
itemsLocation = resolveRef(itemsLocation, itemsLocation.schema.$ref)
|
|
603
585
|
}
|
|
604
586
|
|
|
605
|
-
|
|
606
|
-
|
|
587
|
+
const itemsSchema = itemsLocation.schema
|
|
588
|
+
|
|
589
|
+
if (contextFunctionsNamesBySchema.has(schema)) {
|
|
590
|
+
return contextFunctionsNamesBySchema.get(schema)
|
|
607
591
|
}
|
|
608
592
|
|
|
609
593
|
const functionName = generateFuncName()
|
|
610
|
-
|
|
594
|
+
contextFunctionsNamesBySchema.set(schema, functionName)
|
|
611
595
|
|
|
612
596
|
const schemaId = location.schemaId === rootSchemaId ? '' : location.schemaId
|
|
613
597
|
let functionCode = `
|
|
@@ -615,14 +599,6 @@ function buildArray (location) {
|
|
|
615
599
|
// ${schemaId + location.jsonPointer}
|
|
616
600
|
`
|
|
617
601
|
|
|
618
|
-
if (schema.nullable) {
|
|
619
|
-
functionCode += `
|
|
620
|
-
if (obj === null) {
|
|
621
|
-
return 'null';
|
|
622
|
-
}
|
|
623
|
-
`
|
|
624
|
-
}
|
|
625
|
-
|
|
626
602
|
functionCode += `
|
|
627
603
|
if (!Array.isArray(obj)) {
|
|
628
604
|
throw new TypeError(\`The value '$\{obj}' does not match schema definition.\`)
|
|
@@ -632,8 +608,8 @@ function buildArray (location) {
|
|
|
632
608
|
|
|
633
609
|
if (!schema.additionalItems) {
|
|
634
610
|
functionCode += `
|
|
635
|
-
if (arrayLength > ${
|
|
636
|
-
throw new Error(\`Item at ${
|
|
611
|
+
if (arrayLength > ${itemsSchema.length}) {
|
|
612
|
+
throw new Error(\`Item at ${itemsSchema.length} does not match schema definition.\`)
|
|
637
613
|
}
|
|
638
614
|
`
|
|
639
615
|
}
|
|
@@ -650,9 +626,9 @@ function buildArray (location) {
|
|
|
650
626
|
let jsonOutput = ''
|
|
651
627
|
`
|
|
652
628
|
|
|
653
|
-
if (Array.isArray(
|
|
654
|
-
for (let i = 0; i <
|
|
655
|
-
const item =
|
|
629
|
+
if (Array.isArray(itemsSchema)) {
|
|
630
|
+
for (let i = 0; i < itemsSchema.length; i++) {
|
|
631
|
+
const item = itemsSchema[i]
|
|
656
632
|
const tmpRes = buildValue(mergeLocation(itemsLocation, i), `obj[${i}]`)
|
|
657
633
|
functionCode += `
|
|
658
634
|
if (${i} < arrayLength) {
|
|
@@ -672,7 +648,7 @@ function buildArray (location) {
|
|
|
672
648
|
|
|
673
649
|
if (schema.additionalItems) {
|
|
674
650
|
functionCode += `
|
|
675
|
-
for (let i = ${
|
|
651
|
+
for (let i = ${itemsSchema.length}; i < arrayLength; i++) {
|
|
676
652
|
let json = JSON.stringify(obj[i])
|
|
677
653
|
jsonOutput += json
|
|
678
654
|
if (i < arrayLength - 1) {
|
|
@@ -743,6 +719,137 @@ function generateFuncName () {
|
|
|
743
719
|
return 'anonymous' + genFuncNameCounter++
|
|
744
720
|
}
|
|
745
721
|
|
|
722
|
+
function buildMultiTypeSerializer (location, input) {
|
|
723
|
+
const schema = location.schema
|
|
724
|
+
const types = schema.type.sort(t1 => t1 === 'null' ? -1 : 1)
|
|
725
|
+
|
|
726
|
+
let code = ''
|
|
727
|
+
|
|
728
|
+
const locationClone = clone(location)
|
|
729
|
+
types.forEach((type, index) => {
|
|
730
|
+
const statement = index === 0 ? 'if' : 'else if'
|
|
731
|
+
locationClone.schema.type = type
|
|
732
|
+
const nestedResult = buildSingleTypeSerializer(locationClone, input)
|
|
733
|
+
switch (type) {
|
|
734
|
+
case 'null':
|
|
735
|
+
code += `
|
|
736
|
+
${statement} (${input} === null)
|
|
737
|
+
${nestedResult}
|
|
738
|
+
`
|
|
739
|
+
break
|
|
740
|
+
case 'string': {
|
|
741
|
+
code += `
|
|
742
|
+
${statement}(
|
|
743
|
+
typeof ${input} === "string" ||
|
|
744
|
+
${input} === null ||
|
|
745
|
+
${input} instanceof Date ||
|
|
746
|
+
${input} instanceof RegExp ||
|
|
747
|
+
(
|
|
748
|
+
typeof ${input} === "object" &&
|
|
749
|
+
typeof ${input}.toString === "function" &&
|
|
750
|
+
${input}.toString !== Object.prototype.toString &&
|
|
751
|
+
!(${input} instanceof Date)
|
|
752
|
+
)
|
|
753
|
+
)
|
|
754
|
+
${nestedResult}
|
|
755
|
+
`
|
|
756
|
+
break
|
|
757
|
+
}
|
|
758
|
+
case 'array': {
|
|
759
|
+
code += `
|
|
760
|
+
${statement}(Array.isArray(${input}))
|
|
761
|
+
${nestedResult}
|
|
762
|
+
`
|
|
763
|
+
break
|
|
764
|
+
}
|
|
765
|
+
case 'integer': {
|
|
766
|
+
code += `
|
|
767
|
+
${statement}(Number.isInteger(${input}) || ${input} === null)
|
|
768
|
+
${nestedResult}
|
|
769
|
+
`
|
|
770
|
+
break
|
|
771
|
+
}
|
|
772
|
+
default: {
|
|
773
|
+
code += `
|
|
774
|
+
${statement}(typeof ${input} === "${type}" || ${input} === null)
|
|
775
|
+
${nestedResult}
|
|
776
|
+
`
|
|
777
|
+
break
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
})
|
|
781
|
+
code += `
|
|
782
|
+
else throw new Error(\`The value $\{JSON.stringify(${input})} does not match schema definition.\`)
|
|
783
|
+
`
|
|
784
|
+
|
|
785
|
+
return code
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
function buildSingleTypeSerializer (location, input) {
|
|
789
|
+
const schema = location.schema
|
|
790
|
+
|
|
791
|
+
switch (schema.type) {
|
|
792
|
+
case 'null':
|
|
793
|
+
return 'json += \'null\''
|
|
794
|
+
case 'string': {
|
|
795
|
+
if (schema.format === 'date-time') {
|
|
796
|
+
return `json += serializer.asDateTime(${input})`
|
|
797
|
+
} else if (schema.format === 'date') {
|
|
798
|
+
return `json += serializer.asDate(${input})`
|
|
799
|
+
} else if (schema.format === 'time') {
|
|
800
|
+
return `json += serializer.asTime(${input})`
|
|
801
|
+
} else {
|
|
802
|
+
return `json += serializer.asString(${input})`
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
case 'integer':
|
|
806
|
+
return `json += serializer.asInteger(${input})`
|
|
807
|
+
case 'number':
|
|
808
|
+
return `json += serializer.asNumber(${input})`
|
|
809
|
+
case 'boolean':
|
|
810
|
+
return `json += serializer.asBoolean(${input})`
|
|
811
|
+
case 'object': {
|
|
812
|
+
const funcName = buildObject(location)
|
|
813
|
+
return `json += ${funcName}(${input})`
|
|
814
|
+
}
|
|
815
|
+
case 'array': {
|
|
816
|
+
const funcName = buildArray(location)
|
|
817
|
+
return `json += ${funcName}(${input})`
|
|
818
|
+
}
|
|
819
|
+
case undefined:
|
|
820
|
+
return `json += JSON.stringify(${input})`
|
|
821
|
+
default:
|
|
822
|
+
throw new Error(`${schema.type} unsupported`)
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
function buildConstSerializer (location, input) {
|
|
827
|
+
const schema = location.schema
|
|
828
|
+
const type = schema.type
|
|
829
|
+
|
|
830
|
+
const hasNullType = Array.isArray(type) && type.includes('null')
|
|
831
|
+
|
|
832
|
+
let code = ''
|
|
833
|
+
|
|
834
|
+
if (hasNullType) {
|
|
835
|
+
code += `
|
|
836
|
+
if (${input} === null) {
|
|
837
|
+
json += 'null'
|
|
838
|
+
} else {
|
|
839
|
+
`
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
code += `json += '${JSON.stringify(schema.const)}'`
|
|
843
|
+
|
|
844
|
+
if (hasNullType) {
|
|
845
|
+
code += `
|
|
846
|
+
}
|
|
847
|
+
`
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
return code
|
|
851
|
+
}
|
|
852
|
+
|
|
746
853
|
function buildValue (location, input) {
|
|
747
854
|
let schema = location.schema
|
|
748
855
|
|
|
@@ -770,188 +877,60 @@ function buildValue (location, input) {
|
|
|
770
877
|
}
|
|
771
878
|
|
|
772
879
|
const type = schema.type
|
|
773
|
-
const nullable = schema.nullable === true || (Array.isArray(type) && type.includes('null'))
|
|
774
880
|
|
|
775
881
|
let code = ''
|
|
776
|
-
let funcName
|
|
777
882
|
|
|
778
|
-
if (
|
|
779
|
-
|
|
883
|
+
if (type === undefined && (schema.anyOf || schema.oneOf)) {
|
|
884
|
+
const type = schema.anyOf ? 'anyOf' : 'oneOf'
|
|
885
|
+
const anyOfLocation = mergeLocation(location, type)
|
|
886
|
+
|
|
887
|
+
for (let index = 0; index < location.schema[type].length; index++) {
|
|
888
|
+
const optionLocation = mergeLocation(anyOfLocation, index)
|
|
889
|
+
const schemaRef = optionLocation.schemaId + optionLocation.jsonPointer
|
|
890
|
+
const nestedResult = buildValue(optionLocation, input)
|
|
780
891
|
code += `
|
|
781
|
-
|
|
892
|
+
${index === 0 ? 'if' : 'else if'}(validator.validate("${schemaRef}", ${input}))
|
|
893
|
+
${nestedResult}
|
|
782
894
|
`
|
|
783
|
-
return code
|
|
784
895
|
}
|
|
785
|
-
|
|
896
|
+
|
|
897
|
+
code += `
|
|
898
|
+
else throw new Error(\`The value $\{JSON.stringify(${input})} does not match schema definition.\`)
|
|
899
|
+
`
|
|
786
900
|
return code
|
|
787
901
|
}
|
|
788
902
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
if (schema.format === 'date-time') {
|
|
795
|
-
funcName = nullable ? 'serializer.asDateTimeNullable.bind(serializer)' : 'serializer.asDateTime.bind(serializer)'
|
|
796
|
-
} else if (schema.format === 'date') {
|
|
797
|
-
funcName = nullable ? 'serializer.asDateNullable.bind(serializer)' : 'serializer.asDate.bind(serializer)'
|
|
798
|
-
} else if (schema.format === 'time') {
|
|
799
|
-
funcName = nullable ? 'serializer.asTimeNullable.bind(serializer)' : 'serializer.asTime.bind(serializer)'
|
|
800
|
-
} else {
|
|
801
|
-
funcName = nullable ? 'serializer.asStringNullable.bind(serializer)' : 'serializer.asString.bind(serializer)'
|
|
802
|
-
}
|
|
803
|
-
code += `json += ${funcName}(${input})`
|
|
804
|
-
break
|
|
805
|
-
}
|
|
806
|
-
case 'integer':
|
|
807
|
-
funcName = nullable ? 'serializer.asIntegerNullable.bind(serializer)' : 'serializer.asInteger.bind(serializer)'
|
|
808
|
-
code += `json += ${funcName}(${input})`
|
|
809
|
-
break
|
|
810
|
-
case 'number':
|
|
811
|
-
funcName = nullable ? 'serializer.asNumberNullable.bind(serializer)' : 'serializer.asNumber.bind(serializer)'
|
|
812
|
-
code += `json += ${funcName}(${input})`
|
|
813
|
-
break
|
|
814
|
-
case 'boolean':
|
|
815
|
-
funcName = nullable ? 'serializer.asBooleanNullable.bind(serializer)' : 'serializer.asBoolean.bind(serializer)'
|
|
816
|
-
code += `json += ${funcName}(${input})`
|
|
817
|
-
break
|
|
818
|
-
case 'object':
|
|
819
|
-
funcName = buildObject(location)
|
|
820
|
-
code += `json += ${funcName}(${input})`
|
|
821
|
-
break
|
|
822
|
-
case 'array':
|
|
823
|
-
funcName = buildArray(location)
|
|
824
|
-
code += `json += ${funcName}(${input})`
|
|
825
|
-
break
|
|
826
|
-
case undefined:
|
|
827
|
-
if (schema.anyOf || schema.oneOf) {
|
|
828
|
-
// beware: dereferenceOfRefs has side effects and changes schema.anyOf
|
|
829
|
-
const type = schema.anyOf ? 'anyOf' : 'oneOf'
|
|
830
|
-
const anyOfLocation = mergeLocation(location, type)
|
|
831
|
-
|
|
832
|
-
for (let index = 0; index < location.schema[type].length; index++) {
|
|
833
|
-
const optionLocation = mergeLocation(anyOfLocation, index)
|
|
834
|
-
const schemaRef = optionLocation.schemaId + optionLocation.jsonPointer
|
|
835
|
-
const nestedResult = buildValue(optionLocation, input)
|
|
836
|
-
code += `
|
|
837
|
-
${index === 0 ? 'if' : 'else if'}(validator.validate("${schemaRef}", ${input}))
|
|
838
|
-
${nestedResult}
|
|
839
|
-
`
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
code += `
|
|
843
|
-
else throw new Error(\`The value $\{JSON.stringify(${input})} does not match schema definition.\`)
|
|
844
|
-
`
|
|
845
|
-
} else if (isEmpty(schema)) {
|
|
846
|
-
code += `
|
|
847
|
-
json += JSON.stringify(${input})
|
|
848
|
-
`
|
|
903
|
+
const nullable = schema.nullable === true
|
|
904
|
+
if (nullable) {
|
|
905
|
+
code += `
|
|
906
|
+
if (${input} === null) {
|
|
907
|
+
json += 'null'
|
|
849
908
|
} else {
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
`
|
|
853
|
-
}
|
|
854
|
-
break
|
|
855
|
-
default:
|
|
856
|
-
if (Array.isArray(type)) {
|
|
857
|
-
let sortedTypes = type
|
|
858
|
-
const nullable = schema.nullable === true || type.includes('null')
|
|
859
|
-
|
|
860
|
-
if (nullable) {
|
|
861
|
-
sortedTypes = sortedTypes.filter(type => type !== 'null')
|
|
862
|
-
code += `
|
|
863
|
-
if (${input} === null) {
|
|
864
|
-
json += null
|
|
865
|
-
} else {`
|
|
866
|
-
}
|
|
909
|
+
`
|
|
910
|
+
}
|
|
867
911
|
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
code += `
|
|
876
|
-
${statement}(
|
|
877
|
-
typeof ${input} === "string" ||
|
|
878
|
-
${input} === null ||
|
|
879
|
-
${input} instanceof Date ||
|
|
880
|
-
${input} instanceof RegExp ||
|
|
881
|
-
(
|
|
882
|
-
typeof ${input} === "object" &&
|
|
883
|
-
typeof ${input}.toString === "function" &&
|
|
884
|
-
${input}.toString !== Object.prototype.toString &&
|
|
885
|
-
!(${input} instanceof Date)
|
|
886
|
-
)
|
|
887
|
-
)
|
|
888
|
-
${nestedResult}
|
|
889
|
-
`
|
|
890
|
-
break
|
|
891
|
-
}
|
|
892
|
-
case 'array': {
|
|
893
|
-
code += `
|
|
894
|
-
${statement}(Array.isArray(${input}))
|
|
895
|
-
${nestedResult}
|
|
896
|
-
`
|
|
897
|
-
break
|
|
898
|
-
}
|
|
899
|
-
case 'integer': {
|
|
900
|
-
code += `
|
|
901
|
-
${statement}(Number.isInteger(${input}) || ${input} === null)
|
|
902
|
-
${nestedResult}
|
|
903
|
-
`
|
|
904
|
-
break
|
|
905
|
-
}
|
|
906
|
-
case 'object': {
|
|
907
|
-
code += `
|
|
908
|
-
${statement}(typeof ${input} === "object" || ${input} === null)
|
|
909
|
-
${nestedResult}
|
|
910
|
-
`
|
|
911
|
-
break
|
|
912
|
-
}
|
|
913
|
-
default: {
|
|
914
|
-
code += `
|
|
915
|
-
${statement}(typeof ${input} === "${type}" || ${input} === null)
|
|
916
|
-
${nestedResult}
|
|
917
|
-
`
|
|
918
|
-
break
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
})
|
|
922
|
-
code += `
|
|
923
|
-
else throw new Error(\`The value $\{JSON.stringify(${input})} does not match schema definition.\`)
|
|
924
|
-
`
|
|
912
|
+
if (schema.const !== undefined) {
|
|
913
|
+
code += buildConstSerializer(location, input)
|
|
914
|
+
} else if (Array.isArray(type)) {
|
|
915
|
+
code += buildMultiTypeSerializer(location, input)
|
|
916
|
+
} else {
|
|
917
|
+
code += buildSingleTypeSerializer(location, input)
|
|
918
|
+
}
|
|
925
919
|
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
}
|
|
929
|
-
`
|
|
930
|
-
}
|
|
931
|
-
} else {
|
|
932
|
-
throw new Error(`${type} unsupported`)
|
|
920
|
+
if (nullable) {
|
|
921
|
+
code += `
|
|
933
922
|
}
|
|
923
|
+
`
|
|
934
924
|
}
|
|
935
925
|
|
|
936
926
|
return code
|
|
937
927
|
}
|
|
938
928
|
|
|
939
|
-
function isEmpty (schema) {
|
|
940
|
-
// eslint-disable-next-line
|
|
941
|
-
for (var key in schema) {
|
|
942
|
-
if (Object.prototype.hasOwnProperty.call(schema, key) && schema[key] !== undefined) {
|
|
943
|
-
return false
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
return true
|
|
947
|
-
}
|
|
948
|
-
|
|
949
929
|
module.exports = build
|
|
950
930
|
|
|
951
931
|
module.exports.validLargeArrayMechanisms = validLargeArrayMechanisms
|
|
952
932
|
|
|
953
|
-
module.exports.restore = function ({ code, validator }) {
|
|
954
|
-
const serializer = new Serializer()
|
|
933
|
+
module.exports.restore = function ({ code, validator, serializer }) {
|
|
955
934
|
// eslint-disable-next-line
|
|
956
935
|
return (Function.apply(null, ['validator', 'serializer', code])
|
|
957
936
|
.apply(null, [validator, serializer]))
|
|
File without changes
|
|
File without changes
|
|
@@ -18,14 +18,6 @@ module.exports = class Serializer {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
asAny (i) {
|
|
22
|
-
return JSON.stringify(i)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
asNull () {
|
|
26
|
-
return 'null'
|
|
27
|
-
}
|
|
28
|
-
|
|
29
21
|
asInteger (i) {
|
|
30
22
|
if (typeof i === 'bigint') {
|
|
31
23
|
return i.toString()
|
|
@@ -42,10 +34,6 @@ module.exports = class Serializer {
|
|
|
42
34
|
}
|
|
43
35
|
}
|
|
44
36
|
|
|
45
|
-
asIntegerNullable (i) {
|
|
46
|
-
return i === null ? 'null' : this.asInteger(i)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
37
|
asNumber (i) {
|
|
50
38
|
const num = Number(i)
|
|
51
39
|
if (Number.isNaN(num)) {
|
|
@@ -57,18 +45,10 @@ module.exports = class Serializer {
|
|
|
57
45
|
}
|
|
58
46
|
}
|
|
59
47
|
|
|
60
|
-
asNumberNullable (i) {
|
|
61
|
-
return i === null ? 'null' : this.asNumber(i)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
48
|
asBoolean (bool) {
|
|
65
49
|
return bool && 'true' || 'false' // eslint-disable-line
|
|
66
50
|
}
|
|
67
51
|
|
|
68
|
-
asBooleanNullable (bool) {
|
|
69
|
-
return bool === null ? 'null' : this.asBoolean(bool)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
52
|
asDateTime (date) {
|
|
73
53
|
if (date === null) return '""'
|
|
74
54
|
if (date instanceof Date) {
|
|
@@ -80,10 +60,6 @@ module.exports = class Serializer {
|
|
|
80
60
|
throw new Error(`The value "${date}" cannot be converted to a date-time.`)
|
|
81
61
|
}
|
|
82
62
|
|
|
83
|
-
asDateTimeNullable (date) {
|
|
84
|
-
return date === null ? 'null' : this.asDateTime(date)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
63
|
asDate (date) {
|
|
88
64
|
if (date === null) return '""'
|
|
89
65
|
if (date instanceof Date) {
|
|
@@ -95,10 +71,6 @@ module.exports = class Serializer {
|
|
|
95
71
|
throw new Error(`The value "${date}" cannot be converted to a date.`)
|
|
96
72
|
}
|
|
97
73
|
|
|
98
|
-
asDateNullable (date) {
|
|
99
|
-
return date === null ? 'null' : this.asDate(date)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
74
|
asTime (date) {
|
|
103
75
|
if (date === null) return '""'
|
|
104
76
|
if (date instanceof Date) {
|
|
@@ -110,10 +82,6 @@ module.exports = class Serializer {
|
|
|
110
82
|
throw new Error(`The value "${date}" cannot be converted to a time.`)
|
|
111
83
|
}
|
|
112
84
|
|
|
113
|
-
asTimeNullable (date) {
|
|
114
|
-
return date === null ? 'null' : this.asTime(date)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
85
|
asString (str) {
|
|
118
86
|
const quotes = '"'
|
|
119
87
|
if (str instanceof Date) {
|
|
@@ -133,10 +101,6 @@ module.exports = class Serializer {
|
|
|
133
101
|
}
|
|
134
102
|
}
|
|
135
103
|
|
|
136
|
-
asStringNullable (str) {
|
|
137
|
-
return str === null ? 'null' : this.asString(str)
|
|
138
|
-
}
|
|
139
|
-
|
|
140
104
|
// magically escape strings for json
|
|
141
105
|
// relying on their charCodeAt
|
|
142
106
|
// everything below 32 needs JSON.stringify()
|
|
File without changes
|
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fast-json-stringify",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.3.0",
|
|
4
4
|
"description": "Stringify your JSON at max speed",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"benchmark": "node ./benchmark/bench-cmp-lib.js",
|
|
12
12
|
"lint:fix": "standard --fix",
|
|
13
13
|
"test:lint": "standard",
|
|
14
|
-
"test:typescript": "tsc --project ./test/types/tsconfig.json",
|
|
14
|
+
"test:typescript": "tsc --project ./test/types/tsconfig.json && tsd",
|
|
15
15
|
"test:unit": "tap -J test/*.test.js test/**/*.test.js",
|
|
16
16
|
"test": "npm run test:lint && npm run test:unit && npm run test:typescript"
|
|
17
17
|
},
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"simple-git": "^3.7.1",
|
|
46
46
|
"standard": "^17.0.0",
|
|
47
47
|
"tap": "^16.0.1",
|
|
48
|
+
"tsd": "^0.22.0",
|
|
48
49
|
"typescript": "^4.0.2",
|
|
49
50
|
"webpack": "^5.40.0"
|
|
50
51
|
},
|
|
@@ -61,5 +62,8 @@
|
|
|
61
62
|
"schema-validator.js"
|
|
62
63
|
]
|
|
63
64
|
},
|
|
64
|
-
"runkitExampleFilename": "example.js"
|
|
65
|
+
"runkitExampleFilename": "./examples/example.js",
|
|
66
|
+
"tsd": {
|
|
67
|
+
"directory": "test/types"
|
|
68
|
+
}
|
|
65
69
|
}
|
package/test/array.test.js
CHANGED
|
@@ -327,6 +327,31 @@ test('object array with anyOf and symbol', (t) => {
|
|
|
327
327
|
t.equal(value, '[{"name":"name-0","option":"Foo"},{"name":"name-1","option":"Bar"}]')
|
|
328
328
|
})
|
|
329
329
|
|
|
330
|
+
test('different arrays with same item schemas', (t) => {
|
|
331
|
+
t.plan(1)
|
|
332
|
+
|
|
333
|
+
const schema = {
|
|
334
|
+
type: 'object',
|
|
335
|
+
properties: {
|
|
336
|
+
array1: {
|
|
337
|
+
type: 'array',
|
|
338
|
+
items: [{ type: 'string' }],
|
|
339
|
+
additionalItems: false
|
|
340
|
+
},
|
|
341
|
+
array2: {
|
|
342
|
+
type: 'array',
|
|
343
|
+
items: { $ref: '#/properties/array1/items' },
|
|
344
|
+
additionalItems: true
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const stringify = build(schema)
|
|
350
|
+
const data = { array1: ['bar'], array2: ['foo', 'bar'] }
|
|
351
|
+
|
|
352
|
+
t.equal(stringify(data), '{"array1":["bar"],"array2":["foo","bar"]}')
|
|
353
|
+
})
|
|
354
|
+
|
|
330
355
|
const largeArray = new Array(2e4).fill({ a: 'test', b: 1 })
|
|
331
356
|
buildTest({
|
|
332
357
|
title: 'large array with default mechanism',
|
package/test/date.test.js
CHANGED
|
@@ -525,3 +525,22 @@ test('nullable date', (t) => {
|
|
|
525
525
|
|
|
526
526
|
t.same(result, `"${DateTime.fromJSDate(data).toISODate()}"`)
|
|
527
527
|
})
|
|
528
|
+
|
|
529
|
+
test('non-date format should not affect data serialization (issue #491)', (t) => {
|
|
530
|
+
t.plan(1)
|
|
531
|
+
|
|
532
|
+
const schema = {
|
|
533
|
+
type: 'object',
|
|
534
|
+
properties: {
|
|
535
|
+
hello: {
|
|
536
|
+
type: 'string',
|
|
537
|
+
format: 'int64',
|
|
538
|
+
pattern: '^[0-9]*$'
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const stringify = build(schema)
|
|
544
|
+
const data = { hello: 123n }
|
|
545
|
+
t.equal(stringify(data), '{"hello":"123"}')
|
|
546
|
+
})
|
package/test/debug-mode.test.js
CHANGED
|
@@ -4,7 +4,8 @@ const test = require('tap').test
|
|
|
4
4
|
const fjs = require('..')
|
|
5
5
|
|
|
6
6
|
const Ajv = require('ajv').default
|
|
7
|
-
const Validator = require('../validator')
|
|
7
|
+
const Validator = require('../lib/validator')
|
|
8
|
+
const Serializer = require('../lib/serializer')
|
|
8
9
|
|
|
9
10
|
function build (opts) {
|
|
10
11
|
return fjs({
|
|
@@ -20,17 +21,18 @@ function build (opts) {
|
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
test('activate debug mode', t => {
|
|
23
|
-
t.plan(
|
|
24
|
+
t.plan(5)
|
|
24
25
|
const debugMode = build({ debugMode: true })
|
|
25
26
|
|
|
26
27
|
t.type(debugMode, 'object')
|
|
27
28
|
t.ok(debugMode.ajv instanceof Ajv)
|
|
28
29
|
t.ok(debugMode.validator instanceof Validator)
|
|
30
|
+
t.ok(debugMode.serializer instanceof Serializer)
|
|
29
31
|
t.type(debugMode.code, 'string')
|
|
30
32
|
})
|
|
31
33
|
|
|
32
34
|
test('activate debug mode truthy', t => {
|
|
33
|
-
t.plan(
|
|
35
|
+
t.plan(5)
|
|
34
36
|
|
|
35
37
|
const debugMode = build({ debugMode: 'yes' })
|
|
36
38
|
|
|
@@ -38,15 +40,17 @@ test('activate debug mode truthy', t => {
|
|
|
38
40
|
t.type(debugMode.code, 'string')
|
|
39
41
|
t.ok(debugMode.ajv instanceof Ajv)
|
|
40
42
|
t.ok(debugMode.validator instanceof Validator)
|
|
43
|
+
t.ok(debugMode.serializer instanceof Serializer)
|
|
41
44
|
})
|
|
42
45
|
|
|
43
46
|
test('to string auto-consistent', t => {
|
|
44
|
-
t.plan(
|
|
47
|
+
t.plan(6)
|
|
45
48
|
const debugMode = build({ debugMode: 1 })
|
|
46
49
|
|
|
47
50
|
t.type(debugMode, 'object')
|
|
48
51
|
t.type(debugMode.code, 'string')
|
|
49
52
|
t.ok(debugMode.ajv instanceof Ajv)
|
|
53
|
+
t.ok(debugMode.serializer instanceof Serializer)
|
|
50
54
|
t.ok(debugMode.validator instanceof Validator)
|
|
51
55
|
|
|
52
56
|
const compiled = fjs.restore(debugMode)
|
|
@@ -55,7 +59,7 @@ test('to string auto-consistent', t => {
|
|
|
55
59
|
})
|
|
56
60
|
|
|
57
61
|
test('to string auto-consistent with ajv', t => {
|
|
58
|
-
t.plan(
|
|
62
|
+
t.plan(6)
|
|
59
63
|
|
|
60
64
|
const debugMode = fjs({
|
|
61
65
|
title: 'object with multiple types field',
|
|
@@ -75,6 +79,7 @@ test('to string auto-consistent with ajv', t => {
|
|
|
75
79
|
t.type(debugMode.code, 'string')
|
|
76
80
|
t.ok(debugMode.ajv instanceof Ajv)
|
|
77
81
|
t.ok(debugMode.validator instanceof Validator)
|
|
82
|
+
t.ok(debugMode.serializer instanceof Serializer)
|
|
78
83
|
|
|
79
84
|
const compiled = fjs.restore(debugMode)
|
|
80
85
|
const tobe = JSON.stringify({ str: 'Foo' })
|
|
@@ -106,3 +111,11 @@ test('to string auto-consistent with ajv-formats', t => {
|
|
|
106
111
|
t.same(compiled({ str: 'foo@bar.com' }), tobe)
|
|
107
112
|
t.throws(() => compiled({ str: 'foo' }))
|
|
108
113
|
})
|
|
114
|
+
|
|
115
|
+
test('debug should restore the same serializer instance', t => {
|
|
116
|
+
t.plan(1)
|
|
117
|
+
|
|
118
|
+
const debugMode = fjs({ type: 'integer' }, { debugMode: 1, rounding: 'ceil' })
|
|
119
|
+
const compiled = fjs.restore(debugMode)
|
|
120
|
+
t.same(compiled(3.95), 4)
|
|
121
|
+
})
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { expectError } from "tsd";
|
|
2
|
+
import build from "../..";
|
|
3
|
+
|
|
4
|
+
// With inference
|
|
5
|
+
interface Schema {
|
|
6
|
+
id: string;
|
|
7
|
+
a?: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const stringify3 = build({
|
|
11
|
+
type: "object",
|
|
12
|
+
properties: { a: { type: "string" } },
|
|
13
|
+
});
|
|
14
|
+
stringify3<Schema>({ id: "123" });
|
|
15
|
+
stringify3<Schema>({ a: 123, id: "123" });
|
|
16
|
+
expectError(stringify3<Schema>({ anotherOne: "bar" }));
|
|
17
|
+
expectError(stringify3<Schema>({ a: "bar" }));
|
|
18
|
+
|
|
19
|
+
// Without inference
|
|
20
|
+
const stringify4 = build({
|
|
21
|
+
type: "object",
|
|
22
|
+
properties: { a: { type: "string" } },
|
|
23
|
+
});
|
|
24
|
+
stringify4({ id: "123" });
|
|
25
|
+
stringify4({ a: 123, id: "123" });
|
|
26
|
+
stringify4({ anotherOne: "bar" });
|
|
27
|
+
stringify4({ a: "bar" });
|
|
28
|
+
|
|
29
|
+
// Without inference - string type
|
|
30
|
+
const stringify5 = build({
|
|
31
|
+
type: "string",
|
|
32
|
+
});
|
|
33
|
+
stringify5("foo");
|
|
34
|
+
expectError(stringify5({ id: "123" }));
|
|
35
|
+
|
|
36
|
+
// Without inference - null type
|
|
37
|
+
const stringify6 = build({
|
|
38
|
+
type: "null",
|
|
39
|
+
});
|
|
40
|
+
stringify6(null);
|
|
41
|
+
expectError(stringify6("a string"));
|
|
42
|
+
|
|
43
|
+
// Without inference - boolean type
|
|
44
|
+
const stringify7 = build({
|
|
45
|
+
type: "boolean",
|
|
46
|
+
});
|
|
47
|
+
stringify7(true);
|
|
48
|
+
expectError(stringify7("a string"));
|