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.
@@ -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)
@@ -1,4 +1,4 @@
1
- const fastJson = require('.')
1
+ const fastJson = require('..')
2
2
  const stringify = fastJson({
3
3
  title: 'Example Schema',
4
4
  type: 'object',
@@ -2,7 +2,7 @@
2
2
 
3
3
  const http = require('http')
4
4
 
5
- const stringify = require('.')({
5
+ const stringify = require('fast-json-stringify')({
6
6
  type: 'object',
7
7
  properties: {
8
8
  hello: {
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: any) => any;
193
- declare function build(schema: build.StringSchema, options?: build.Options): (doc: string) => string;
194
- declare function build(schema: build.IntegerSchema | build.NumberSchema, options?: build.Options): (doc: number) => string;
195
- declare function build(schema: build.NullSchema, options?: build.Options): (doc: null) => "null";
196
- declare function build(schema: build.BooleanSchema, options?: build.Options): (doc: boolean) => string;
197
- declare function build(schema: build.ArraySchema | build.TupleSchema, options?: build.Options): (doc: any[]) => string;
198
- declare function build(schema: build.ObjectSchema, options?: build.Options): (doc: object) => string;
199
- declare function build(schema: build.Schema, options?: build.Options): (doc: object | any[] | string | number | boolean | null) => string;
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 arrayItemsReferenceSerializersMap = new Map()
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
- arrayItemsReferenceSerializersMap.clear()
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 { code: dependenciesName.join('\n'), validator, ajv: validator.ajv }
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
- arrayItemsReferenceSerializersMap.clear()
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 (objectReferenceSerializersMap.has(schema)) {
542
- return objectReferenceSerializersMap.get(schema)
542
+ if (contextFunctionsNamesBySchema.has(schema)) {
543
+ return contextFunctionsNamesBySchema.get(schema)
543
544
  }
544
545
 
545
546
  const functionName = generateFuncName()
546
- objectReferenceSerializersMap.set(schema, functionName)
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
- let schema = location.schema
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.items.$ref) {
594
- if (!schema[fjsCloned]) {
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
- if (arrayItemsReferenceSerializersMap.has(schema.items)) {
606
- return arrayItemsReferenceSerializersMap.get(schema.items)
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
- arrayItemsReferenceSerializersMap.set(schema.items, functionName)
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 > ${schema.items.length}) {
636
- throw new Error(\`Item at ${schema.items.length} does not match schema definition.\`)
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(schema.items)) {
654
- for (let i = 0; i < schema.items.length; i++) {
655
- const item = schema.items[i]
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 = ${schema.items.length}; i < arrayLength; 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 ('const' in schema) {
779
- if (nullable) {
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
- json += ${input} === null ? 'null' : '${JSON.stringify(schema.const)}'
892
+ ${index === 0 ? 'if' : 'else if'}(validator.validate("${schemaRef}", ${input}))
893
+ ${nestedResult}
782
894
  `
783
- return code
784
895
  }
785
- code += `json += '${JSON.stringify(schema.const)}'`
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
- switch (type) {
790
- case 'null':
791
- code += 'json += serializer.asNull()'
792
- break
793
- case 'string': {
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
- code += `
851
- json += JSON.stringify(${input})
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
- const locationClone = clone(location)
869
- sortedTypes.forEach((type, index) => {
870
- const statement = index === 0 ? 'if' : 'else if'
871
- locationClone.schema.type = type
872
- const nestedResult = buildValue(locationClone, input)
873
- switch (type) {
874
- case 'string': {
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
- if (nullable) {
927
- code += `
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.2.0",
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
  }
@@ -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
+ })
@@ -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(4)
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(4)
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(5)
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(5)
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"));