fast-json-stringify 1.16.0 → 1.16.4

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/README.md CHANGED
@@ -220,6 +220,7 @@ console.log(stringify(obj)) // '{"matchfoo":"42","otherfoo":"str","matchnum":3,"
220
220
  If *additionalProperties* is not present or is set to `false`, every property that is not explicitly listed in the *properties* and *patternProperties* objects,will be ignored, as described in <a href="#missingFields">Missing fields</a>.
221
221
  Missing fields are ignored to avoid having to rewrite objects before serializing. However, other schema rules would throw in similar situations.
222
222
  If *additionalProperties* is set to `true`, it will be used by `JSON.stringify` to stringify the additional properties. If you want to achieve maximum performance, we strongly encourage you to use a fixed schema where possible.
223
+ The additional properties will always be serialzied at the end of the object.
223
224
  Example:
224
225
  ```javascript
225
226
  const stringify = fastJson({
@@ -252,7 +253,7 @@ const obj = {
252
253
  nomatchint: 313
253
254
  }
254
255
 
255
- console.log(stringify(obj)) // '{"matchfoo":"42","otherfoo":"str","matchnum":3,"nomatchstr":"valar morghulis",nomatchint:"313","nickname":"nick"}'
256
+ console.log(stringify(obj)) // '{"nickname":"nick","matchfoo":"42","otherfoo":"str","matchnum":3,"nomatchstr":"valar morghulis",nomatchint:"313"}'
256
257
  ```
257
258
 
258
259
  #### AnyOf
package/index.js CHANGED
@@ -6,6 +6,7 @@ var Ajv = require('ajv')
6
6
  var merge = require('deepmerge')
7
7
  var util = require('util')
8
8
  var validate = require('./schema-validator')
9
+ var stringSimilarity = null
9
10
 
10
11
  var uglify = null
11
12
  var isLong
@@ -74,6 +75,7 @@ function build (schema, options) {
74
75
  `
75
76
  }
76
77
 
78
+ const fullSchema = schema
77
79
  if (schema.$ref) {
78
80
  schema = refFinder(schema.$ref, schema, options.schema)
79
81
  }
@@ -89,7 +91,7 @@ function build (schema, options) {
89
91
  switch (schema.type) {
90
92
  case 'object':
91
93
  main = '$main'
92
- code = buildObject(schema, code, main, options.schema, schema)
94
+ code = buildObject(schema, code, main, options.schema, fullSchema)
93
95
  break
94
96
  case 'string':
95
97
  main = schema.nullable ? $asStringNullable.name : $asString.name
@@ -490,6 +492,10 @@ function refFinder (ref, schema, externalSchema) {
490
492
  if (ref[0]) {
491
493
  schema = externalSchema[ref[0]]
492
494
 
495
+ if (schema === undefined) {
496
+ findBadKey(externalSchema, [ref[0]])
497
+ }
498
+
493
499
  if (schema.$ref) {
494
500
  return refFinder(schema.$ref, schema, externalSchema)
495
501
  }
@@ -517,8 +523,30 @@ function refFinder (ref, schema, externalSchema) {
517
523
  }
518
524
  }
519
525
  }
520
- const result = (new Function('schema', code))(schema)
526
+ var result
527
+ try {
528
+ result = (new Function('schema', code))(schema)
529
+ } catch (err) {}
530
+
531
+ if (result === undefined) {
532
+ findBadKey(schema, walk.slice(1))
533
+ }
521
534
  return result.$ref ? refFinder(result.$ref, schema, externalSchema) : result
535
+
536
+ function findBadKey (obj, keys) {
537
+ if (keys.length === 0) return null
538
+ const key = sanitizeKey(keys.shift())
539
+ if (obj[key] === undefined) {
540
+ stringSimilarity = stringSimilarity || require('string-similarity')
541
+ const { bestMatch } = stringSimilarity.findBestMatch(key, Object.keys(obj))
542
+ if (bestMatch.rating >= 0.5) {
543
+ throw new Error(`Cannot find reference '${key}', did you mean '${bestMatch.target}'?`)
544
+ } else {
545
+ throw new Error(`Cannot find reference '${key}'`)
546
+ }
547
+ }
548
+ return findBadKey(obj[key], keys)
549
+ }
522
550
  }
523
551
 
524
552
  function sanitizeKey (key) {
@@ -658,15 +686,13 @@ function buildCodeWithAllOfs (schema, code, laterCode, name, externalSchema, ful
658
686
  }
659
687
 
660
688
  function buildInnerObject (schema, name, externalSchema, fullSchema) {
661
- var laterCode = ''
662
- var code = ''
689
+ var result = buildCodeWithAllOfs(schema, '', '', name, externalSchema, fullSchema)
663
690
  if (schema.patternProperties) {
664
- code += addPatternProperties(schema, externalSchema, fullSchema)
691
+ result.code += addPatternProperties(schema, externalSchema, fullSchema)
665
692
  } else if (schema.additionalProperties && !schema.patternProperties) {
666
- code += addAdditionalProperties(schema, externalSchema, fullSchema)
693
+ result.code += addAdditionalProperties(schema, externalSchema, fullSchema)
667
694
  }
668
-
669
- return buildCodeWithAllOfs(schema, code, laterCode, name, externalSchema, fullSchema)
695
+ return result
670
696
  }
671
697
 
672
698
  function addIfThenElse (schema, name, externalSchema, fullSchema) {
@@ -879,12 +905,26 @@ function buildArrayTypeCondition (type, accessor) {
879
905
  return condition
880
906
  }
881
907
 
908
+ function dereferenceAnyOfRefs (schema, externalSchema, fullSchema) {
909
+ schema.anyOf.forEach((s, index) => {
910
+ // follow the refs
911
+ while (s.$ref) {
912
+ schema.anyOf[index] = refFinder(s.$ref, fullSchema, externalSchema)
913
+ s = schema.anyOf[index]
914
+ }
915
+ })
916
+ }
917
+
882
918
  function nested (laterCode, name, key, schema, externalSchema, fullSchema, subKey) {
883
919
  var code = ''
884
920
  var funcName
885
921
 
886
922
  subKey = subKey || ''
887
923
 
924
+ if (schema.$ref) {
925
+ schema = refFinder(schema.$ref, fullSchema, externalSchema)
926
+ }
927
+
888
928
  if (schema.type === undefined) {
889
929
  var inferedType = inferTypeByKeyword(schema)
890
930
  if (inferedType) {
@@ -930,6 +970,7 @@ function nested (laterCode, name, key, schema, externalSchema, fullSchema, subKe
930
970
  break
931
971
  case undefined:
932
972
  if ('anyOf' in schema) {
973
+ dereferenceAnyOfRefs(schema, externalSchema, fullSchema)
933
974
  schema.anyOf.forEach((s, index) => {
934
975
  var nestedResult = nested(laterCode, name, key, s, externalSchema, fullSchema, subKey !== '' ? subKey : 'i' + index)
935
976
  code += `
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fast-json-stringify",
3
- "version": "1.16.0",
3
+ "version": "1.16.4",
4
4
  "description": "Stringify your JSON at max speed",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -39,16 +39,17 @@
39
39
  "long": "^4.0.0",
40
40
  "pre-commit": "^1.2.2",
41
41
  "proxyquire": "^2.1.3",
42
- "semver": "^6.3.0",
42
+ "semver": "^7.1.0",
43
43
  "standard": "^14.0.0",
44
44
  "tap": "^12.6.5",
45
45
  "tap-mocha-reporter": "^3.0.9",
46
- "typescript": "^3.6.3",
46
+ "typescript": "^3.7.5",
47
47
  "uglify-es": "^3.3.9"
48
48
  },
49
49
  "dependencies": {
50
- "ajv": "^6.10.2",
51
- "deepmerge": "^4.0.0"
50
+ "ajv": "^6.11.0",
51
+ "deepmerge": "^4.2.2",
52
+ "string-similarity": "^4.0.1"
52
53
  },
53
54
  "standard": {
54
55
  "ignore": [
@@ -19,7 +19,7 @@ test('additionalProperties', (t) => {
19
19
  })
20
20
 
21
21
  const obj = { str: 'test', foo: 42, ofoo: true, foof: 'string', objfoo: { a: true } }
22
- t.equal('{"foo":"42","ofoo":"true","foof":"string","objfoo":"[object Object]","str":"test"}', stringify(obj))
22
+ t.equal('{"str":"test","foo":"42","ofoo":"true","foof":"string","objfoo":"[object Object]"}', stringify(obj))
23
23
  })
24
24
 
25
25
  test('additionalProperties should not change properties', (t) => {
@@ -38,7 +38,7 @@ test('additionalProperties should not change properties', (t) => {
38
38
  })
39
39
 
40
40
  const obj = { foo: '42', ofoo: 42 }
41
- t.equal('{"ofoo":42,"foo":"42"}', stringify(obj))
41
+ t.equal('{"foo":"42","ofoo":42}', stringify(obj))
42
42
  })
43
43
 
44
44
  test('additionalProperties should not change properties and patternProperties', (t) => {
@@ -62,7 +62,7 @@ test('additionalProperties should not change properties and patternProperties',
62
62
  })
63
63
 
64
64
  const obj = { foo: '42', ofoo: 42, test: '42' }
65
- t.equal('{"ofoo":"42","test":42,"foo":"42"}', stringify(obj))
65
+ t.equal('{"foo":"42","ofoo":"42","test":42}', stringify(obj))
66
66
  })
67
67
 
68
68
  test('additionalProperties set to true, use of fast-safe-stringify', (t) => {
@@ -248,3 +248,151 @@ test('null value in schema', (t) => {
248
248
  t.fail()
249
249
  }
250
250
  })
251
+
252
+ test('anyOf and $ref together', (t) => {
253
+ t.plan(2)
254
+
255
+ const schema = {
256
+ type: 'object',
257
+ properties: {
258
+ cs: {
259
+ anyOf: [
260
+ {
261
+ $ref: '#/definitions/Option'
262
+ },
263
+ {
264
+ type: 'boolean'
265
+ }
266
+ ]
267
+ }
268
+ },
269
+ definitions: {
270
+ Option: {
271
+ type: 'string'
272
+ }
273
+ }
274
+ }
275
+
276
+ const stringify = build(schema)
277
+
278
+ try {
279
+ const value = stringify({
280
+ cs: 'franco'
281
+ })
282
+ t.is(value, '{"cs":"franco"}')
283
+ } catch (e) {
284
+ t.fail()
285
+ }
286
+
287
+ try {
288
+ const value = stringify({
289
+ cs: true
290
+ })
291
+ t.is(value, '{"cs":true}')
292
+ } catch (e) {
293
+ t.fail()
294
+ }
295
+ })
296
+
297
+ test('anyOf and $ref: 2 levels are fine', (t) => {
298
+ t.plan(1)
299
+
300
+ const schema = {
301
+ type: 'object',
302
+ properties: {
303
+ cs: {
304
+ anyOf: [
305
+ {
306
+ $ref: '#/definitions/Option'
307
+ },
308
+ {
309
+ type: 'boolean'
310
+ }
311
+ ]
312
+ }
313
+ },
314
+ definitions: {
315
+ Option: {
316
+ anyOf: [
317
+ {
318
+ type: 'number'
319
+ },
320
+ {
321
+ type: 'boolean'
322
+ }
323
+ ]
324
+ }
325
+ }
326
+ }
327
+
328
+ const stringify = build(schema)
329
+ try {
330
+ const value = stringify({
331
+ cs: 3
332
+ })
333
+ t.is(value, '{"cs":3}')
334
+ } catch (e) {
335
+ t.fail()
336
+ }
337
+ })
338
+
339
+ test('anyOf and $ref: multiple levels should throw at build.', (t) => {
340
+ t.plan(3)
341
+
342
+ const schema = {
343
+ type: 'object',
344
+ properties: {
345
+ cs: {
346
+ anyOf: [
347
+ {
348
+ $ref: '#/definitions/Option'
349
+ },
350
+ {
351
+ type: 'boolean'
352
+ }
353
+ ]
354
+ }
355
+ },
356
+ definitions: {
357
+ Option: {
358
+ anyOf: [
359
+ {
360
+ $ref: '#/definitions/Option2'
361
+ },
362
+ {
363
+ type: 'string'
364
+ }
365
+ ]
366
+ },
367
+ Option2: {
368
+ type: 'number'
369
+ }
370
+ }
371
+ }
372
+
373
+ const stringify = build(schema)
374
+ try {
375
+ const value = stringify({
376
+ cs: 3
377
+ })
378
+ t.is(value, '{"cs":3}')
379
+ } catch (e) {
380
+ t.fail(e)
381
+ }
382
+ try {
383
+ const value = stringify({
384
+ cs: true
385
+ })
386
+ t.is(value, '{"cs":true}')
387
+ } catch (e) {
388
+ t.fail(e)
389
+ }
390
+ try {
391
+ const value = stringify({
392
+ cs: 'pippo'
393
+ })
394
+ t.is(value, '{"cs":"pippo"}')
395
+ } catch (e) {
396
+ t.fail(e)
397
+ }
398
+ })
@@ -21,7 +21,7 @@ test('patternProperties', (t) => {
21
21
  })
22
22
 
23
23
  const obj = { str: 'test', foo: 42, ofoo: true, foof: 'string', objfoo: { a: true }, notMe: false }
24
- t.equal(stringify(obj), '{"foo":"42","ofoo":"true","foof":"string","objfoo":"[object Object]","str":"test"}')
24
+ t.equal(stringify(obj), '{"str":"test","foo":"42","ofoo":"true","foof":"string","objfoo":"[object Object]"}')
25
25
  })
26
26
 
27
27
  test('patternProperties should not change properties', (t) => {
@@ -42,7 +42,7 @@ test('patternProperties should not change properties', (t) => {
42
42
  })
43
43
 
44
44
  const obj = { foo: '42', ofoo: 42 }
45
- t.equal(stringify(obj), '{"ofoo":42,"foo":"42"}')
45
+ t.equal(stringify(obj), '{"foo":"42","ofoo":42}')
46
46
  })
47
47
 
48
48
  test('patternProperties - string coerce', (t) => {
package/test/ref.test.js CHANGED
@@ -685,6 +685,41 @@ test('ref internal - multiple $ref format', (t) => {
685
685
  t.equal(output, '{"zero":"test","a":"test","b":"test","c":"test","d":"test","e":"test"}')
686
686
  })
687
687
 
688
+ test('ref in root internal', (t) => {
689
+ t.plan(2)
690
+
691
+ const schema = {
692
+ title: 'object with $ref in root schema',
693
+ $ref: '#/definitions/num',
694
+ definitions: {
695
+ num: {
696
+ type: 'object',
697
+ properties: {
698
+ int: {
699
+ $ref: '#/definitions/int'
700
+ }
701
+ }
702
+ },
703
+ int: {
704
+ type: 'integer'
705
+ }
706
+ }
707
+ }
708
+
709
+ const object = { int: 42 }
710
+ const stringify = build(schema)
711
+ const output = stringify(object)
712
+
713
+ try {
714
+ JSON.parse(output)
715
+ t.pass()
716
+ } catch (e) {
717
+ t.fail()
718
+ }
719
+
720
+ t.equal(output, '{"int":42}')
721
+ })
722
+
688
723
  test('ref in root external', (t) => {
689
724
  t.plan(2)
690
725
 
@@ -876,3 +911,202 @@ test('ref in definition with exact match', (t) => {
876
911
 
877
912
  t.equal(output, '{"foo":"foo"}')
878
913
  })
914
+
915
+ test('Bad key', t => {
916
+ t.test('Find match', t => {
917
+ t.plan(1)
918
+ try {
919
+ build({
920
+ definitions: {
921
+ projectId: {
922
+ type: 'object',
923
+ properties: {
924
+ id: { type: 'integer' }
925
+ }
926
+ }
927
+ },
928
+ type: 'object',
929
+ properties: {
930
+ data: {
931
+ $ref: '#/definitions/porjectId'
932
+ }
933
+ }
934
+ })
935
+ t.fail('Should throw')
936
+ } catch (err) {
937
+ t.is(err.message, "Cannot find reference 'porjectId', did you mean 'projectId'?")
938
+ }
939
+ })
940
+
941
+ t.test('No match', t => {
942
+ t.plan(1)
943
+ try {
944
+ build({
945
+ definitions: {
946
+ projectId: {
947
+ type: 'object',
948
+ properties: {
949
+ id: { type: 'integer' }
950
+ }
951
+ }
952
+ },
953
+ type: 'object',
954
+ properties: {
955
+ data: {
956
+ $ref: '#/definitions/foobar'
957
+ }
958
+ }
959
+ })
960
+ t.fail('Should throw')
961
+ } catch (err) {
962
+ t.is(err.message, "Cannot find reference 'foobar'")
963
+ }
964
+ })
965
+
966
+ t.test('Find match (external schema)', t => {
967
+ t.plan(1)
968
+ try {
969
+ build({
970
+ type: 'object',
971
+ properties: {
972
+ data: {
973
+ $ref: 'external#/definitions/porjectId'
974
+ }
975
+ }
976
+ }, {
977
+ schema: {
978
+ external: {
979
+ definitions: {
980
+ projectId: {
981
+ type: 'object',
982
+ properties: {
983
+ id: { type: 'integer' }
984
+ }
985
+ }
986
+ }
987
+ }
988
+ }
989
+ })
990
+ t.fail('Should throw')
991
+ } catch (err) {
992
+ t.is(err.message, "Cannot find reference 'porjectId', did you mean 'projectId'?")
993
+ }
994
+ })
995
+
996
+ t.test('No match (external schema)', t => {
997
+ t.plan(1)
998
+ try {
999
+ build({
1000
+ type: 'object',
1001
+ properties: {
1002
+ data: {
1003
+ $ref: 'external#/definitions/foobar'
1004
+ }
1005
+ }
1006
+ }, {
1007
+ schema: {
1008
+ external: {
1009
+ definitions: {
1010
+ projectId: {
1011
+ type: 'object',
1012
+ properties: {
1013
+ id: { type: 'integer' }
1014
+ }
1015
+ }
1016
+ }
1017
+ }
1018
+ }
1019
+ })
1020
+ t.fail('Should throw')
1021
+ } catch (err) {
1022
+ t.is(err.message, "Cannot find reference 'foobar'")
1023
+ }
1024
+ })
1025
+
1026
+ t.test('Find match (external definitions typo)', t => {
1027
+ t.plan(1)
1028
+ try {
1029
+ build({
1030
+ type: 'object',
1031
+ properties: {
1032
+ data: {
1033
+ $ref: 'external#/deifnitions/projectId'
1034
+ }
1035
+ }
1036
+ }, {
1037
+ schema: {
1038
+ external: {
1039
+ definitions: {
1040
+ projectId: {
1041
+ type: 'object',
1042
+ properties: {
1043
+ id: { type: 'integer' }
1044
+ }
1045
+ }
1046
+ }
1047
+ }
1048
+ }
1049
+ })
1050
+ t.fail('Should throw')
1051
+ } catch (err) {
1052
+ t.is(err.message, "Cannot find reference 'deifnitions', did you mean 'definitions'?")
1053
+ }
1054
+ })
1055
+
1056
+ t.test('Find match (definitions typo)', t => {
1057
+ t.plan(1)
1058
+ try {
1059
+ build({
1060
+ definitions: {
1061
+ projectId: {
1062
+ type: 'object',
1063
+ properties: {
1064
+ id: { type: 'integer' }
1065
+ }
1066
+ }
1067
+ },
1068
+ type: 'object',
1069
+ properties: {
1070
+ data: {
1071
+ $ref: '#/deifnitions/projectId'
1072
+ }
1073
+ }
1074
+ })
1075
+ t.fail('Should throw')
1076
+ } catch (err) {
1077
+ t.is(err.message, "Cannot find reference 'deifnitions', did you mean 'definitions'?")
1078
+ }
1079
+ })
1080
+
1081
+ t.test('Find match (external schema typo)', t => {
1082
+ t.plan(1)
1083
+ try {
1084
+ build({
1085
+ type: 'object',
1086
+ properties: {
1087
+ data: {
1088
+ $ref: 'extrenal#/definitions/projectId'
1089
+ }
1090
+ }
1091
+ }, {
1092
+ schema: {
1093
+ external: {
1094
+ definitions: {
1095
+ projectId: {
1096
+ type: 'object',
1097
+ properties: {
1098
+ id: { type: 'integer' }
1099
+ }
1100
+ }
1101
+ }
1102
+ }
1103
+ }
1104
+ })
1105
+ t.fail('Should throw')
1106
+ } catch (err) {
1107
+ t.is(err.message, "Cannot find reference 'extrenal', did you mean 'external'?")
1108
+ }
1109
+ })
1110
+
1111
+ t.end()
1112
+ })