fast-json-stringify
Advanced tools
Comparing version 4.2.0 to 5.0.0
@@ -10,3 +10,3 @@ 'use strict' | ||
function buildAjv (options) { | ||
const ajvInstance = new Ajv({ ...options, strictSchema: false, uriResolver: fastUri }) | ||
const ajvInstance = new Ajv({ ...options, strictSchema: false, validateSchema: false, uriResolver: fastUri }) | ||
ajvFormats(ajvInstance) | ||
@@ -13,0 +13,0 @@ |
418
index.js
@@ -15,3 +15,2 @@ 'use strict' | ||
let largeArraySize = 2e4 | ||
let stringSimilarity = null | ||
let largeArrayMechanism = 'default' | ||
@@ -45,14 +44,44 @@ const validLargeArrayMechanisms = [ | ||
function mergeLocation (source, dest) { | ||
function mergeLocation (location, key) { | ||
return { | ||
schema: dest.schema || source.schema, | ||
root: dest.root || source.root, | ||
externalSchema: dest.externalSchema || source.externalSchema | ||
schema: location.schema[key], | ||
schemaId: location.schemaId, | ||
jsonPointer: location.jsonPointer + '/' + key | ||
} | ||
} | ||
function resolveRef (location, ref) { | ||
let hashIndex = ref.indexOf('#') | ||
if (hashIndex === -1) { | ||
hashIndex = ref.length | ||
} | ||
const schemaId = ref.slice(0, hashIndex) || location.schemaId | ||
const jsonPointer = ref.slice(hashIndex) | ||
const schemaRef = schemaId + jsonPointer | ||
let ajvSchema | ||
try { | ||
ajvSchema = ajvInstance.getSchema(schemaRef) | ||
} catch (error) { | ||
throw new Error(`Cannot find reference "${ref}"`) | ||
} | ||
if (ajvSchema === undefined) { | ||
throw new Error(`Cannot find reference "${ref}"`) | ||
} | ||
const schema = ajvSchema.schema | ||
if (schema.$ref !== undefined) { | ||
return resolveRef({ schema, schemaId, jsonPointer }, schema.$ref) | ||
} | ||
return { schema, schemaId, jsonPointer } | ||
} | ||
const arrayItemsReferenceSerializersMap = new Map() | ||
const objectReferenceSerializersMap = new Map() | ||
const schemaReferenceMap = new Map() | ||
let rootSchemaId = null | ||
let ajvInstance = null | ||
@@ -62,5 +91,6 @@ let contextFunctions = null | ||
function build (schema, options) { | ||
schema = clone(schema) | ||
arrayItemsReferenceSerializersMap.clear() | ||
objectReferenceSerializersMap.clear() | ||
schemaReferenceMap.clear() | ||
@@ -71,8 +101,24 @@ contextFunctions = [] | ||
ajvInstance = buildAjv(options.ajv) | ||
rootSchemaId = schema.$id || randomUUID() | ||
isValidSchema(schema) | ||
extendDateTimeType(schema) | ||
ajvInstance.addSchema(schema, rootSchemaId) | ||
if (options.schema) { | ||
// eslint-disable-next-line | ||
for (var key of Object.keys(options.schema)) { | ||
isValidSchema(options.schema[key], key) | ||
options.schema = clone(options.schema) | ||
for (const key of Object.keys(options.schema)) { | ||
const externalSchema = options.schema[key] | ||
isValidSchema(externalSchema, key) | ||
extendDateTimeType(externalSchema) | ||
let schemaKey = externalSchema.$id || key | ||
if (externalSchema.$id !== undefined && externalSchema.$id[0] === '#') { | ||
schemaKey = key + externalSchema.$id // relative URI | ||
} | ||
if (ajvInstance.getSchema(schemaKey) === undefined) { | ||
ajvInstance.addSchema(externalSchema, schemaKey) | ||
} | ||
} | ||
@@ -105,19 +151,5 @@ } | ||
let location = { | ||
schema, | ||
root: schema, | ||
externalSchema: options.schema | ||
} | ||
const location = { schema, schemaId: rootSchemaId, jsonPointer: '#' } | ||
const code = buildValue(location, 'input') | ||
if (schema.$ref) { | ||
location = refFinder(schema.$ref, location) | ||
schema = location.schema | ||
} | ||
if (schema.type === undefined) { | ||
schema.type = inferTypeByKeyword(schema) | ||
} | ||
const code = buildValue('main', 'input', location) | ||
const contextFunctionCode = ` | ||
@@ -154,6 +186,6 @@ function main (input) { | ||
ajvInstance = null | ||
rootSchemaId = null | ||
contextFunctions = null | ||
arrayItemsReferenceSerializersMap.clear() | ||
objectReferenceSerializersMap.clear() | ||
schemaReferenceMap.clear() | ||
@@ -239,6 +271,7 @@ return stringifyFunc | ||
Object.keys(pp).forEach((regex, index) => { | ||
let ppLocation = mergeLocation(location, { schema: pp[regex] }) | ||
const patternPropertiesLocation = mergeLocation(location, 'patternProperties') | ||
Object.keys(pp).forEach((regex) => { | ||
let ppLocation = mergeLocation(patternPropertiesLocation, regex) | ||
if (pp[regex].$ref) { | ||
ppLocation = refFinder(pp[regex].$ref, location) | ||
ppLocation = resolveRef(ppLocation, pp[regex].$ref) | ||
pp[regex] = ppLocation.schema | ||
@@ -253,3 +286,3 @@ } | ||
const valueCode = buildValue('', 'obj[keys[i]]', ppLocation) | ||
const valueCode = buildValue(ppLocation, 'obj[keys[i]]') | ||
code += ` | ||
@@ -287,9 +320,9 @@ if (/${regex.replace(/\\*\//g, '\\/')}/.test(keys[i])) { | ||
} | ||
let apLocation = mergeLocation(location, { schema: ap }) | ||
let apLocation = mergeLocation(location, 'additionalProperties') | ||
if (ap.$ref) { | ||
apLocation = refFinder(ap.$ref, location) | ||
apLocation = resolveRef(location, ap.$ref) | ||
ap = apLocation.schema | ||
} | ||
const valueCode = buildValue('', 'obj[keys[i]]', apLocation) | ||
const valueCode = buildValue(apLocation, 'obj[keys[i]]') | ||
@@ -316,136 +349,5 @@ code += ` | ||
function idFinder (schema, searchedId) { | ||
let objSchema | ||
const explore = (schema, searchedId) => { | ||
Object.keys(schema || {}).forEach((key, i, a) => { | ||
if (key === '$id' && schema[key] === searchedId) { | ||
objSchema = schema | ||
} else if (objSchema === undefined && typeof schema[key] === 'object') { | ||
explore(schema[key], searchedId) | ||
} | ||
}) | ||
} | ||
explore(schema, searchedId) | ||
return objSchema | ||
} | ||
function refFinder (ref, location) { | ||
const externalSchema = location.externalSchema | ||
let root = location.root | ||
let schema = location.schema | ||
if (externalSchema && externalSchema[ref]) { | ||
return { | ||
schema: externalSchema[ref], | ||
root: externalSchema[ref], | ||
externalSchema | ||
} | ||
} | ||
// Split file from walk | ||
ref = ref.split('#') | ||
// Check schemaReferenceMap for $id entry | ||
if (ref[0] && schemaReferenceMap.has(ref[0])) { | ||
schema = schemaReferenceMap.get(ref[0]) | ||
root = schemaReferenceMap.get(ref[0]) | ||
if (schema.$ref) { | ||
return refFinder(schema.$ref, { | ||
schema, | ||
root, | ||
externalSchema | ||
}) | ||
} | ||
} else if (ref[0]) { // If external file | ||
schema = externalSchema[ref[0]] | ||
root = externalSchema[ref[0]] | ||
if (schema === undefined) { | ||
findBadKey(externalSchema, [ref[0]]) | ||
} | ||
if (schema.$ref) { | ||
return refFinder(schema.$ref, { | ||
schema, | ||
root, | ||
externalSchema | ||
}) | ||
} | ||
} | ||
let code = 'return schema' | ||
// If it has a path | ||
if (ref[1]) { | ||
// ref[1] could contain a JSON pointer - ex: /definitions/num | ||
// or plain name fragment id without suffix # - ex: customId | ||
const walk = ref[1].split('/') | ||
if (walk.length === 1) { | ||
const targetId = `#${ref[1]}` | ||
let dereferenced = idFinder(schema, targetId) | ||
if (dereferenced === undefined && !ref[0]) { | ||
// eslint-disable-next-line | ||
for (var key of Object.keys(externalSchema)) { | ||
dereferenced = idFinder(externalSchema[key], targetId) | ||
if (dereferenced !== undefined) { | ||
root = externalSchema[key] | ||
break | ||
} | ||
} | ||
} | ||
return { | ||
schema: dereferenced, | ||
root, | ||
externalSchema | ||
} | ||
} else { | ||
// eslint-disable-next-line | ||
for (var i = 1; i < walk.length; i++) { | ||
code += `[${JSON.stringify(walk[i])}]` | ||
} | ||
} | ||
} | ||
let result | ||
try { | ||
result = (new Function('schema', code))(root) | ||
} catch (err) {} | ||
if (result === undefined && ref[1]) { | ||
const walk = ref[1].split('/') | ||
findBadKey(schema, walk.slice(1)) | ||
} | ||
if (result.$ref) { | ||
return refFinder(result.$ref, { | ||
schema, | ||
root, | ||
externalSchema | ||
}) | ||
} | ||
return { | ||
schema: result, | ||
root, | ||
externalSchema | ||
} | ||
function findBadKey (obj, keys) { | ||
if (keys.length === 0) return null | ||
const key = keys.shift() | ||
if (obj[key] === undefined) { | ||
stringSimilarity = stringSimilarity || require('string-similarity') | ||
const { bestMatch } = stringSimilarity.findBestMatch(key, Object.keys(obj)) | ||
if (bestMatch.rating >= 0.5) { | ||
throw new Error(`Cannot find reference ${JSON.stringify(key)}, did you mean ${JSON.stringify(bestMatch.target)}?`) | ||
} else { | ||
throw new Error(`Cannot find reference ${JSON.stringify(key)}`) | ||
} | ||
} | ||
return findBadKey(obj[key], keys) | ||
} | ||
} | ||
function buildCode (location, locationPath) { | ||
function buildCode (location) { | ||
if (location.schema.$ref) { | ||
location = refFinder(location.schema.$ref, location) | ||
location = resolveRef(location, location.schema.$ref) | ||
} | ||
@@ -458,6 +360,7 @@ | ||
const propertiesLocation = mergeLocation(location, 'properties') | ||
Object.keys(schema.properties || {}).forEach((key) => { | ||
let propertyLocation = mergeLocation(location, { schema: schema.properties[key] }) | ||
let propertyLocation = mergeLocation(propertiesLocation, key) | ||
if (schema.properties[key].$ref) { | ||
propertyLocation = refFinder(schema.properties[key].$ref, location) | ||
propertyLocation = resolveRef(location, schema.properties[key].$ref) | ||
schema.properties[key] = propertyLocation.schema | ||
@@ -478,3 +381,3 @@ } | ||
code += buildValue(locationPath + key, `obj[${JSON.stringify(key)}]`, mergeLocation(propertyLocation, { schema: schema.properties[key] })) | ||
code += buildValue(propertyLocation, `obj[${JSON.stringify(key)}]`) | ||
@@ -509,5 +412,10 @@ const defaultValue = schema.properties[key].default | ||
function mergeAllOfSchema (location, schema, mergedSchema) { | ||
for (let allOfSchema of schema.allOf) { | ||
const allOfLocation = mergeLocation(location, 'allOf') | ||
for (let i = 0; i < schema.allOf.length; i++) { | ||
let allOfSchema = schema.allOf[i] | ||
if (allOfSchema.$ref) { | ||
allOfSchema = refFinder(allOfSchema.$ref, mergeLocation(location, { schema: allOfSchema })).schema | ||
const allOfSchemaLocation = mergeLocation(allOfLocation, i) | ||
allOfSchema = resolveRef(allOfSchemaLocation, allOfSchema.$ref).schema | ||
} | ||
@@ -599,5 +507,5 @@ | ||
function buildInnerObject (location, locationPath) { | ||
function buildInnerObject (location) { | ||
const schema = location.schema | ||
let code = buildCode(location, locationPath) | ||
let code = buildCode(location) | ||
if (schema.patternProperties) { | ||
@@ -611,35 +519,32 @@ code += addPatternProperties(location) | ||
function addIfThenElse (location, locationPath) { | ||
let code = '' | ||
function addIfThenElse (location) { | ||
const schema = merge({}, location.schema) | ||
const thenSchema = schema.then | ||
const elseSchema = schema.else || { additionalProperties: true } | ||
const schema = location.schema | ||
const copy = merge({}, schema) | ||
const i = copy.if | ||
const then = copy.then | ||
const e = copy.else ? copy.else : { additionalProperties: true } | ||
delete copy.if | ||
delete copy.then | ||
delete copy.else | ||
let merged = merge(copy, then) | ||
let mergedLocation = mergeLocation(location, { schema: merged }) | ||
delete schema.if | ||
delete schema.then | ||
delete schema.else | ||
const schemaKey = i.$id || randomUUID() | ||
ajvInstance.addSchema(i, schemaKey) | ||
const ifLocation = mergeLocation(location, 'if') | ||
const ifSchemaRef = ifLocation.schemaId + ifLocation.jsonPointer | ||
code += ` | ||
valid = ajv.validate("${schemaKey}", obj) | ||
if (valid) { | ||
let code = ` | ||
if (ajv.validate("${ifSchemaRef}", obj)) { | ||
` | ||
if (merged.if && merged.then) { | ||
code += addIfThenElse(mergedLocation, locationPath + 'Then') | ||
} | ||
code += buildInnerObject(mergedLocation, locationPath + 'Then') | ||
const thenLocation = mergeLocation(location, 'then') | ||
thenLocation.schema = merge(schema, thenSchema) | ||
if (thenSchema.if && thenSchema.then) { | ||
code += addIfThenElse(thenLocation) | ||
} | ||
code += buildInnerObject(thenLocation) | ||
code += ` | ||
} | ||
` | ||
merged = merge(copy, e) | ||
mergedLocation = mergeLocation(mergedLocation, { schema: merged }) | ||
const elseLocation = mergeLocation(location, 'else') | ||
elseLocation.schema = merge(schema, elseSchema) | ||
code += ` | ||
@@ -649,8 +554,6 @@ else { | ||
if (merged.if && merged.then) { | ||
code += addIfThenElse(mergedLocation, locationPath + 'Else') | ||
if (elseSchema.if && elseSchema.then) { | ||
code += addIfThenElse(elseLocation) | ||
} | ||
code += buildInnerObject(mergedLocation, locationPath + 'Else') | ||
code += buildInnerObject(elseLocation) | ||
code += ` | ||
@@ -669,7 +572,4 @@ } | ||
function buildObject (location, locationPath) { | ||
function buildObject (location) { | ||
const schema = location.schema | ||
if (schema.$id !== undefined) { | ||
schemaReferenceMap.set(schema.$id, schema) | ||
} | ||
@@ -683,5 +583,6 @@ if (objectReferenceSerializersMap.has(schema)) { | ||
const schemaId = location.schemaId === rootSchemaId ? '' : location.schemaId | ||
let functionCode = ` | ||
function ${functionName} (input) { | ||
// ${locationPath} | ||
// ${schemaId + location.jsonPointer} | ||
` | ||
@@ -704,8 +605,5 @@ if (schema.nullable) { | ||
if (schema.if && schema.then) { | ||
functionCode += ` | ||
var valid | ||
` | ||
rCode = addIfThenElse(location, locationPath) | ||
rCode = addIfThenElse(location) | ||
} else { | ||
rCode = buildInnerObject(location, locationPath) | ||
rCode = buildInnerObject(location) | ||
} | ||
@@ -724,7 +622,4 @@ | ||
function buildArray (location, locationPath) { | ||
function buildArray (location) { | ||
let schema = location.schema | ||
if (schema.$id !== undefined) { | ||
schemaReferenceMap.set(schema.$id, schema) | ||
} | ||
@@ -736,2 +631,4 @@ // default to any items type | ||
let itemsLocation = mergeLocation(location, 'items') | ||
if (schema.items.$ref) { | ||
@@ -744,3 +641,4 @@ if (!schema[fjsCloned]) { | ||
location = refFinder(schema.items.$ref, location) | ||
location = resolveRef(location, schema.items.$ref) | ||
itemsLocation = location | ||
schema.items = location.schema | ||
@@ -756,5 +654,6 @@ } | ||
const schemaId = location.schemaId === rootSchemaId ? '' : location.schemaId | ||
let functionCode = ` | ||
function ${functionName} (obj) { | ||
// ${locationPath} | ||
// ${schemaId + location.jsonPointer} | ||
` | ||
@@ -797,7 +696,6 @@ | ||
const accessor = '[i]' | ||
if (Array.isArray(schema.items)) { | ||
for (let i = 0; i < schema.items.length; i++) { | ||
const item = schema.items[i] | ||
const tmpRes = buildValue(locationPath + accessor + i, `obj[${i}]`, mergeLocation(location, { schema: item })) | ||
const tmpRes = buildValue(mergeLocation(itemsLocation, i), `obj[${i}]`) | ||
functionCode += ` | ||
@@ -830,3 +728,3 @@ if (${i} < arrayLength) { | ||
} else { | ||
const code = buildValue(locationPath + accessor, 'obj[i]', mergeLocation(location, { schema: schema.items })) | ||
const code = buildValue(itemsLocation, 'obj[i]') | ||
functionCode += ` | ||
@@ -888,26 +786,2 @@ for (let i = 0; i < arrayLength; i++) { | ||
function dereferenceOfRefs (location, type) { | ||
if (!location.schema[fjsCloned]) { | ||
const schemaClone = clone(location.schema) | ||
schemaClone[fjsCloned] = true | ||
location.schema = schemaClone | ||
} | ||
const schema = location.schema | ||
const locations = [] | ||
schema[type].forEach((s, index) => { | ||
// follow the refs | ||
let sLocation = mergeLocation(location, { schema: s }) | ||
while (s.$ref) { | ||
sLocation = refFinder(s.$ref, sLocation) | ||
schema[type][index] = sLocation.schema | ||
s = schema[type][index] | ||
} | ||
locations[index] = sLocation | ||
}) | ||
return locations | ||
} | ||
let genFuncNameCounter = 0 | ||
@@ -918,7 +792,8 @@ function generateFuncName () { | ||
function buildValue (locationPath, input, location) { | ||
function buildValue (location, input) { | ||
let schema = location.schema | ||
if (schema.$ref) { | ||
schema = refFinder(schema.$ref, location) | ||
location = resolveRef(location, schema.$ref) | ||
schema = location.schema | ||
} | ||
@@ -970,37 +845,28 @@ | ||
case 'object': | ||
funcName = buildObject(location, locationPath) | ||
funcName = buildObject(location) | ||
code += `json += ${funcName}(${input})` | ||
break | ||
case 'array': | ||
funcName = buildArray(location, locationPath) | ||
funcName = buildArray(location) | ||
code += `json += ${funcName}(${input})` | ||
break | ||
case undefined: | ||
if (schema.anyOf || schema.oneOf) { | ||
if (schema.fjs_date_type) { | ||
funcName = getStringSerializer(schema.fjs_date_type, nullable) | ||
code += `json += ${funcName}(${input})` | ||
break | ||
} else if (schema.anyOf || schema.oneOf) { | ||
// beware: dereferenceOfRefs has side effects and changes schema.anyOf | ||
const locations = dereferenceOfRefs(location, schema.anyOf ? 'anyOf' : 'oneOf') | ||
locations.forEach((location, index) => { | ||
const nestedResult = buildValue(locationPath + 'i' + index, input, location) | ||
// Since we are only passing the relevant schema to ajv.validate, it needs to be full dereferenced | ||
// otherwise any $ref pointing to an external schema would result in an error. | ||
// Full dereference of the schema happens as side effect of two functions: | ||
// 1. `dereferenceOfRefs` loops through the `schema.anyOf`` array and replaces any top level reference | ||
// with the actual schema | ||
// 2. `buildValue`, through `buildCode`, replaces any reference in object properties with the actual schema | ||
// (see https://github.com/fastify/fast-json-stringify/blob/6da3b3e8ac24b1ca5578223adedb4083b7adf8db/index.js#L631) | ||
const type = schema.anyOf ? 'anyOf' : 'oneOf' | ||
const anyOfLocation = mergeLocation(location, type) | ||
// Ajv does not support js date format. In order to properly validate objects containing a date, | ||
// it needs to replace all occurrences of the string date format with a custom keyword fjs_date_type. | ||
// (see https://github.com/fastify/fast-json-stringify/pull/441) | ||
const extendedSchema = clone(location.schema) | ||
extendDateTimeType(extendedSchema) | ||
const schemaKey = location.schema.$id || randomUUID() | ||
ajvInstance.addSchema(extendedSchema, schemaKey) | ||
for (let index = 0; index < location.schema[type].length; index++) { | ||
const optionLocation = mergeLocation(anyOfLocation, index) | ||
const schemaRef = optionLocation.schemaId + optionLocation.jsonPointer | ||
const nestedResult = buildValue(optionLocation, input) | ||
code += ` | ||
${index === 0 ? 'if' : 'else if'}(ajv.validate("${schemaKey}", ${input})) | ||
${index === 0 ? 'if' : 'else if'}(ajv.validate("${schemaRef}", ${input})) | ||
${nestedResult} | ||
` | ||
}) | ||
} | ||
@@ -1042,6 +908,7 @@ code += ` | ||
const locationClone = clone(location) | ||
sortedTypes.forEach((type, index) => { | ||
const statement = index === 0 ? 'if' : 'else if' | ||
const tempSchema = Object.assign({}, schema, { type }) | ||
const nestedResult = buildValue(locationPath, input, mergeLocation(location, { schema: tempSchema })) | ||
locationClone.schema.type = type | ||
const nestedResult = buildValue(locationClone, input) | ||
switch (type) { | ||
@@ -1095,3 +962,8 @@ case 'string': { | ||
// Ajv does not support js date format. In order to properly validate objects containing a date, | ||
// it needs to replace all occurrences of the string date format with a custom keyword fjs_date_type. | ||
// (see https://github.com/fastify/fast-json-stringify/pull/441) | ||
function extendDateTimeType (schema) { | ||
if (schema === null) return | ||
if (schema.type === 'string' && ['date-time', 'date', 'time'].includes(schema.format)) { | ||
@@ -1098,0 +970,0 @@ schema.fjs_date_type = schema.format |
{ | ||
"name": "fast-json-stringify", | ||
"version": "4.2.0", | ||
"version": "5.0.0", | ||
"description": "Stringify your JSON at max speed", | ||
@@ -40,3 +40,3 @@ "main": "index.js", | ||
"compile-json-stringify": "^0.1.2", | ||
"inquirer": "^8.2.4", | ||
"inquirer": "^9.0.0", | ||
"is-my-json-valid": "^2.20.0", | ||
@@ -57,9 +57,5 @@ "luxon": "^2.4.0", | ||
"deepmerge": "^4.2.2", | ||
"fast-uri": "^2.0.0", | ||
"rfdc": "^1.2.0", | ||
"string-similarity": "^4.0.1" | ||
"fast-uri": "^2.1.0", | ||
"rfdc": "^1.2.0" | ||
}, | ||
"engines": { | ||
"node": ">= 10.0.0" | ||
}, | ||
"standard": { | ||
@@ -66,0 +62,0 @@ "ignore": [ |
@@ -70,40 +70,40 @@ 'use strict' | ||
asDatetime (date, skipQuotes) { | ||
const quotes = skipQuotes === true ? '' : '"' | ||
asDatetime (date) { | ||
const quotes = '"' | ||
if (date instanceof Date) { | ||
return quotes + date.toISOString() + quotes | ||
} | ||
return this.asString(date, skipQuotes) | ||
return this.asString(date) | ||
} | ||
asDatetimeNullable (date, skipQuotes) { | ||
return date === null ? 'null' : this.asDatetime(date, skipQuotes) | ||
asDatetimeNullable (date) { | ||
return date === null ? 'null' : this.asDatetime(date) | ||
} | ||
asDate (date, skipQuotes) { | ||
const quotes = skipQuotes === true ? '' : '"' | ||
asDate (date) { | ||
const quotes = '"' | ||
if (date instanceof Date) { | ||
return quotes + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(0, 10) + quotes | ||
} | ||
return this.asString(date, skipQuotes) | ||
return this.asString(date) | ||
} | ||
asDateNullable (date, skipQuotes) { | ||
return date === null ? 'null' : this.asDate(date, skipQuotes) | ||
asDateNullable (date) { | ||
return date === null ? 'null' : this.asDate(date) | ||
} | ||
asTime (date, skipQuotes) { | ||
const quotes = skipQuotes === true ? '' : '"' | ||
asTime (date) { | ||
const quotes = '"' | ||
if (date instanceof Date) { | ||
return quotes + new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().slice(11, 19) + quotes | ||
} | ||
return this.asString(date, skipQuotes) | ||
return this.asString(date) | ||
} | ||
asTimeNullable (date, skipQuotes) { | ||
return date === null ? 'null' : this.asTime(date, skipQuotes) | ||
asTimeNullable (date) { | ||
return date === null ? 'null' : this.asTime(date) | ||
} | ||
asString (str, skipQuotes) { | ||
const quotes = skipQuotes === true ? '' : '"' | ||
asString (str) { | ||
const quotes = '"' | ||
if (str instanceof Date) { | ||
@@ -118,7 +118,2 @@ return quotes + str.toISOString() + quotes | ||
} | ||
// If we skipQuotes it means that we are using it as test | ||
// no need to test the string length for the render | ||
if (skipQuotes) { | ||
return str | ||
} | ||
@@ -125,0 +120,0 @@ if (str.length < 42) { |
@@ -405,8 +405,10 @@ 'use strict' | ||
second: { | ||
id2: { | ||
$id: '#id2', | ||
type: 'object', | ||
properties: { | ||
id2: { | ||
type: 'integer' | ||
definitions: { | ||
id2: { | ||
$id: '#id2', | ||
type: 'object', | ||
properties: { | ||
id2: { | ||
type: 'integer' | ||
} | ||
} | ||
@@ -426,3 +428,3 @@ } | ||
{ | ||
$ref: 'second#id2' | ||
$ref: 'second#/definitions/id2' | ||
} | ||
@@ -429,0 +431,0 @@ ] |
@@ -434,6 +434,6 @@ 'use strict' | ||
first: { | ||
$ref: '#first-schema' | ||
$ref: 'first#first-schema' | ||
}, | ||
second: { | ||
$ref: '#second-schema' | ||
$ref: 'second#second-schema' | ||
} | ||
@@ -461,2 +461,207 @@ } | ||
test('external reference to $id', (t) => { | ||
t.plan(2) | ||
const externalSchema = { | ||
first: { | ||
$id: 'external-reference', | ||
type: 'object', | ||
properties: { | ||
str: { | ||
type: 'string' | ||
} | ||
} | ||
} | ||
} | ||
const schema = { | ||
type: 'object', | ||
properties: { | ||
first: { | ||
$ref: 'external-reference' | ||
} | ||
} | ||
} | ||
const object = { first: { str: 'test' } } | ||
const stringify = build(schema, { schema: externalSchema }) | ||
const output = stringify(object) | ||
JSON.parse(output) | ||
t.pass() | ||
t.equal(output, '{"first":{"str":"test"}}') | ||
}) | ||
test('external reference to key#id', (t) => { | ||
t.plan(2) | ||
const externalSchema = { | ||
first: { | ||
$id: '#external-reference', | ||
type: 'object', | ||
properties: { | ||
str: { | ||
type: 'string' | ||
} | ||
} | ||
} | ||
} | ||
const schema = { | ||
type: 'object', | ||
properties: { | ||
first: { | ||
$ref: 'first#external-reference' | ||
} | ||
} | ||
} | ||
const object = { first: { str: 'test' } } | ||
const stringify = build(schema, { schema: externalSchema }) | ||
const output = stringify(object) | ||
JSON.parse(output) | ||
t.pass() | ||
t.equal(output, '{"first":{"str":"test"}}') | ||
}) | ||
test('external and inner reference', (t) => { | ||
t.plan(2) | ||
const externalSchema = { | ||
first: { | ||
$id: 'reference', | ||
$ref: '#reference', | ||
definitions: { | ||
inner: { | ||
$id: '#reference', | ||
type: 'object', | ||
properties: { | ||
str: { | ||
type: 'string' | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
const schema = { | ||
type: 'object', | ||
properties: { | ||
first: { | ||
$ref: 'reference' | ||
} | ||
} | ||
} | ||
const object = { first: { str: 'test' } } | ||
const stringify = build(schema, { schema: externalSchema }) | ||
const output = stringify(object) | ||
JSON.parse(output) | ||
t.pass() | ||
t.equal(output, '{"first":{"str":"test"}}') | ||
}) | ||
test('external reference to key', (t) => { | ||
t.plan(2) | ||
const externalSchema = { | ||
first: { | ||
$id: 'external-reference', | ||
type: 'object', | ||
properties: { | ||
str: { | ||
type: 'string' | ||
} | ||
} | ||
} | ||
} | ||
const schema = { | ||
type: 'object', | ||
properties: { | ||
first: { | ||
$ref: 'external-reference' | ||
} | ||
} | ||
} | ||
const object = { first: { str: 'test' } } | ||
const stringify = build(schema, { schema: externalSchema }) | ||
const output = stringify(object) | ||
JSON.parse(output) | ||
t.pass() | ||
t.equal(output, '{"first":{"str":"test"}}') | ||
}) | ||
test('ref external - plain name fragment', (t) => { | ||
t.plan(2) | ||
const externalSchema = { | ||
first: { | ||
$id: 'first-schema', | ||
type: 'object', | ||
properties: { | ||
str: { | ||
type: 'string' | ||
} | ||
} | ||
}, | ||
second: { | ||
definitions: { | ||
second: { | ||
$id: 'second-schema', | ||
type: 'object', | ||
properties: { | ||
int: { | ||
type: 'integer' | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
const schema = { | ||
title: 'object with $ref to external plain name fragment', | ||
type: 'object', | ||
properties: { | ||
first: { | ||
$ref: 'first-schema' | ||
}, | ||
second: { | ||
$ref: 'second-schema' | ||
} | ||
} | ||
} | ||
const object = { | ||
first: { | ||
str: 'test' | ||
}, | ||
second: { | ||
int: 42 | ||
} | ||
} | ||
const stringify = build(schema, { schema: externalSchema }) | ||
const output = stringify(object) | ||
JSON.parse(output) | ||
t.pass() | ||
t.equal(output, '{"first":{"str":"test"},"second":{"int":42}}') | ||
}) | ||
test('ref external - duplicate plain name fragment', (t) => { | ||
@@ -508,3 +713,3 @@ t.plan(2) | ||
other: { | ||
$ref: '#otherSchema' | ||
$ref: 'other#otherSchema' | ||
} | ||
@@ -899,3 +1104,3 @@ } | ||
type: 'object', | ||
$ref: 'numbers#/definitions/num' | ||
$ref: 'numbers' | ||
} | ||
@@ -985,28 +1190,2 @@ | ||
test('ref in definition with exact match', (t) => { | ||
t.plan(2) | ||
const externalSchema = { | ||
'#/definitions/foo': { | ||
type: 'string' | ||
} | ||
} | ||
const schema = { | ||
type: 'object', | ||
properties: { | ||
foo: { $ref: '#/definitions/foo' } | ||
} | ||
} | ||
const object = { foo: 'foo' } | ||
const stringify = build(schema, { schema: externalSchema }) | ||
const output = stringify(object) | ||
JSON.parse(output) | ||
t.pass() | ||
t.equal(output, '{"foo":"foo"}') | ||
}) | ||
test('Bad key', t => { | ||
@@ -1034,3 +1213,3 @@ t.test('Find match', t => { | ||
} catch (err) { | ||
t.equal(err.message, 'Cannot find reference "porjectId", did you mean "projectId"?') | ||
t.equal(err.message, 'Cannot find reference "#/definitions/porjectId"') | ||
} | ||
@@ -1060,3 +1239,3 @@ }) | ||
} catch (err) { | ||
t.equal(err.message, 'Cannot find reference "foobar"') | ||
t.equal(err.message, 'Cannot find reference "#/definitions/foobar"') | ||
} | ||
@@ -1091,3 +1270,3 @@ }) | ||
} catch (err) { | ||
t.equal(err.message, 'Cannot find reference "porjectId", did you mean "projectId"?') | ||
t.equal(err.message, 'Cannot find reference "external#/definitions/porjectId"') | ||
} | ||
@@ -1122,3 +1301,3 @@ }) | ||
} catch (err) { | ||
t.equal(err.message, 'Cannot find reference "foobar"') | ||
t.equal(err.message, 'Cannot find reference "external#/definitions/foobar"') | ||
} | ||
@@ -1153,3 +1332,3 @@ }) | ||
} catch (err) { | ||
t.equal(err.message, 'Cannot find reference "deifnitions", did you mean "definitions"?') | ||
t.equal(err.message, 'Cannot find reference "external#/deifnitions/projectId"') | ||
} | ||
@@ -1179,3 +1358,3 @@ }) | ||
} catch (err) { | ||
t.equal(err.message, 'Cannot find reference "deifnitions", did you mean "definitions"?') | ||
t.equal(err.message, 'Cannot find reference "#/deifnitions/projectId"') | ||
} | ||
@@ -1210,3 +1389,3 @@ }) | ||
} catch (err) { | ||
t.equal(err.message, 'Cannot find reference "extrenal", did you mean "external"?') | ||
t.equal(err.message, 'Cannot find reference "extrenal#/definitions/projectId"') | ||
} | ||
@@ -1264,1 +1443,395 @@ }) | ||
}) | ||
test('Reference through multiple definitions', (t) => { | ||
t.plan(2) | ||
const schema = { | ||
$ref: '#/definitions/A', | ||
definitions: { | ||
A: { | ||
type: 'object', | ||
additionalProperties: false, | ||
properties: { a: { anyOf: [{ $ref: '#/definitions/B' }] } }, | ||
required: ['a'] | ||
}, | ||
B: { | ||
type: 'object', | ||
properties: { b: { anyOf: [{ $ref: '#/definitions/C' }] } }, | ||
required: ['b'], | ||
additionalProperties: false | ||
}, | ||
C: { | ||
type: 'object', | ||
properties: { c: { type: 'string', const: 'd' } }, | ||
required: ['c'], | ||
additionalProperties: false | ||
} | ||
} | ||
} | ||
const object = { a: { b: { c: 'd' } } } | ||
const stringify = build(schema) | ||
const output = stringify(object) | ||
JSON.parse(output) | ||
t.pass() | ||
t.equal(output, JSON.stringify(object)) | ||
}) | ||
test('issue #350', (t) => { | ||
t.plan(2) | ||
const schema = { | ||
title: 'Example Schema', | ||
type: 'object', | ||
properties: { | ||
firstName: { $ref: '#foo' }, | ||
lastName: { $ref: '#foo' }, | ||
nested: { | ||
type: 'object', | ||
properties: { | ||
firstName: { $ref: '#foo' }, | ||
lastName: { $ref: '#foo' } | ||
} | ||
} | ||
}, | ||
definitions: { | ||
foo: { | ||
$id: '#foo', | ||
type: 'string' | ||
} | ||
} | ||
} | ||
const object = { | ||
firstName: 'Matteo', | ||
lastName: 'Collina', | ||
nested: { | ||
firstName: 'Matteo', | ||
lastName: 'Collina' | ||
} | ||
} | ||
const stringify = build(schema) | ||
const output = stringify(object) | ||
JSON.parse(output) | ||
t.pass() | ||
t.equal(output, JSON.stringify(object)) | ||
}) | ||
test('deep union type', (t) => { | ||
t.plan(1) | ||
const stringify = build({ | ||
schema: { | ||
type: 'array', | ||
items: { | ||
oneOf: [ | ||
{ | ||
$ref: 'components#/schemas/IDirectory' | ||
}, | ||
{ | ||
$ref: 'components#/schemas/IImageFile' | ||
}, | ||
{ | ||
$ref: 'components#/schemas/ITextFile' | ||
}, | ||
{ | ||
$ref: 'components#/schemas/IZipFile' | ||
} | ||
] | ||
}, | ||
nullable: false | ||
}, | ||
components: { | ||
schemas: { | ||
IDirectory: { | ||
$id: 'IDirectory', | ||
$recursiveAnchor: true, | ||
type: 'object', | ||
properties: { | ||
children: { | ||
type: 'array', | ||
items: { | ||
oneOf: [ | ||
{ | ||
$recursiveRef: '#' | ||
}, | ||
{ | ||
$ref: 'components#/schemas/IImageFile' | ||
}, | ||
{ | ||
$ref: 'components#/schemas/ITextFile' | ||
}, | ||
{ | ||
$ref: 'components#/schemas/IZipFile' | ||
} | ||
] | ||
}, | ||
nullable: false | ||
}, | ||
type: { | ||
type: 'string', | ||
nullable: false | ||
}, | ||
id: { | ||
type: 'string', | ||
nullable: false | ||
}, | ||
name: { | ||
type: 'string', | ||
nullable: false | ||
} | ||
}, | ||
nullable: false, | ||
required: [ | ||
'children', | ||
'type', | ||
'id', | ||
'name' | ||
] | ||
}, | ||
IImageFile: { | ||
$id: 'IImageFile', | ||
type: 'object', | ||
properties: { | ||
width: { | ||
type: 'number', | ||
nullable: false | ||
}, | ||
height: { | ||
type: 'number', | ||
nullable: false | ||
}, | ||
url: { | ||
type: 'string', | ||
nullable: false | ||
}, | ||
extension: { | ||
type: 'string', | ||
nullable: false | ||
}, | ||
size: { | ||
type: 'number', | ||
nullable: false | ||
}, | ||
type: { | ||
type: 'string', | ||
nullable: false | ||
}, | ||
id: { | ||
type: 'string', | ||
nullable: false | ||
}, | ||
name: { | ||
type: 'string', | ||
nullable: false | ||
} | ||
}, | ||
nullable: false, | ||
required: [ | ||
'width', | ||
'height', | ||
'url', | ||
'extension', | ||
'size', | ||
'type', | ||
'id', | ||
'name' | ||
] | ||
}, | ||
ITextFile: { | ||
$id: 'ITextFile', | ||
type: 'object', | ||
properties: { | ||
content: { | ||
type: 'string', | ||
nullable: false | ||
}, | ||
extension: { | ||
type: 'string', | ||
nullable: false | ||
}, | ||
size: { | ||
type: 'number', | ||
nullable: false | ||
}, | ||
type: { | ||
type: 'string', | ||
nullable: false | ||
}, | ||
id: { | ||
type: 'string', | ||
nullable: false | ||
}, | ||
name: { | ||
type: 'string', | ||
nullable: false | ||
} | ||
}, | ||
nullable: false, | ||
required: [ | ||
'content', | ||
'extension', | ||
'size', | ||
'type', | ||
'id', | ||
'name' | ||
] | ||
}, | ||
IZipFile: { | ||
$id: 'IZipFile', | ||
type: 'object', | ||
properties: { | ||
files: { | ||
type: 'number', | ||
nullable: false | ||
}, | ||
extension: { | ||
type: 'string', | ||
nullable: false | ||
}, | ||
size: { | ||
type: 'number', | ||
nullable: false | ||
}, | ||
type: { | ||
type: 'string', | ||
nullable: false | ||
}, | ||
id: { | ||
type: 'string', | ||
nullable: false | ||
}, | ||
name: { | ||
type: 'string', | ||
nullable: false | ||
} | ||
}, | ||
nullable: false, | ||
required: [ | ||
'files', | ||
'extension', | ||
'size', | ||
'type', | ||
'id', | ||
'name' | ||
] | ||
} | ||
} | ||
} | ||
}) | ||
const obj = [ | ||
{ | ||
type: 'directory', | ||
id: '7b1068a4-dd6e-474a-8d85-09a2d77639cb', | ||
name: 'ixcWGOKI', | ||
children: [ | ||
{ | ||
type: 'directory', | ||
id: '5883e17c-b207-46d4-ad2d-be72249711ce', | ||
name: 'vecQwFGS', | ||
children: [] | ||
}, | ||
{ | ||
type: 'file', | ||
id: '670b6556-a610-4a48-8a16-9c2da97a0d18', | ||
name: 'eStFddzX', | ||
extension: 'jpg', | ||
size: 7, | ||
width: 300, | ||
height: 1200, | ||
url: 'https://github.com/samchon/typescript-json' | ||
}, | ||
{ | ||
type: 'file', | ||
id: '85dc796d-9593-4833-b1a1-addc8ebf74ea', | ||
name: 'kTdUfwRJ', | ||
extension: 'ts', | ||
size: 86, | ||
content: 'console.log("Hello world");' | ||
}, | ||
{ | ||
type: 'file', | ||
id: '8933c86a-7a1e-4d4a-b0a6-17d6896fdf89', | ||
name: 'NBPkefUG', | ||
extension: 'zip', | ||
size: 22, | ||
files: 20 | ||
} | ||
] | ||
} | ||
] | ||
t.equal(JSON.stringify(obj), stringify(obj)) | ||
}) | ||
test('ref with same id in properties', (t) => { | ||
t.plan(2) | ||
const externalSchema = { | ||
ObjectId: { | ||
$id: 'ObjectId', | ||
type: 'string' | ||
}, | ||
File: { | ||
$id: 'File', | ||
type: 'object', | ||
properties: { | ||
_id: { $ref: 'ObjectId' }, | ||
name: { type: 'string' }, | ||
owner: { $ref: 'ObjectId' } | ||
} | ||
} | ||
} | ||
t.test('anyOf', (t) => { | ||
t.plan(1) | ||
const schema = { | ||
$id: 'Article', | ||
type: 'object', | ||
properties: { | ||
_id: { $ref: 'ObjectId' }, | ||
image: { | ||
anyOf: [ | ||
{ $ref: 'File' }, | ||
{ type: 'null' } | ||
] | ||
} | ||
} | ||
} | ||
const stringify = build(schema, { schema: externalSchema }) | ||
const output = stringify({ _id: 'foo', image: { _id: 'bar', name: 'hello', owner: 'baz' } }) | ||
t.equal(output, '{"_id":"foo","image":{"_id":"bar","name":"hello","owner":"baz"}}') | ||
}) | ||
t.test('oneOf', (t) => { | ||
t.plan(1) | ||
const schema = { | ||
$id: 'Article', | ||
type: 'object', | ||
properties: { | ||
_id: { $ref: 'ObjectId' }, | ||
image: { | ||
oneOf: [ | ||
{ $ref: 'File' }, | ||
{ type: 'null' } | ||
] | ||
} | ||
} | ||
} | ||
const stringify = build(schema, { schema: externalSchema }) | ||
const output = stringify({ _id: 'foo', image: { _id: 'bar', name: 'hello', owner: 'baz' } }) | ||
t.equal(output, '{"_id":"foo","image":{"_id":"bar","name":"hello","owner":"baz"}}') | ||
}) | ||
}) |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
308855
5
11468
4
- Removedstring-similarity@^4.0.1
- Removedstring-similarity@4.0.4(transitive)
Updatedfast-uri@^2.1.0