Skip to content

Commit 8e83aae

Browse files
committed
Revert "perf: optimize required props serialization (#675)"
This reverts commit 15a2386.
1 parent 3dc8dca commit 8e83aae

File tree

3 files changed

+68
-55
lines changed

3 files changed

+68
-55
lines changed

index.js

Lines changed: 64 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ const validLargeArrayMechanisms = [
2727
'json-stringify'
2828
]
2929

30+
const addComma = '!addComma && (addComma = true) || (json += \',\')'
31+
3032
let schemaIdCounter = 0
3133

3234
const mergedSchemaRef = Symbol('fjs-merged-schema-ref')
@@ -260,7 +262,7 @@ function inferTypeByKeyword (schema) {
260262
return schema.type
261263
}
262264

263-
function buildExtraObjectPropertiesSerializer (context, location, addComma) {
265+
function buildExtraObjectPropertiesSerializer (context, location) {
264266
const schema = location.schema
265267
const propertiesKeys = Object.keys(schema.properties || {})
266268

@@ -319,76 +321,88 @@ function buildExtraObjectPropertiesSerializer (context, location, addComma) {
319321
}
320322

321323
function buildInnerObject (context, location) {
324+
let code = ''
322325
const schema = location.schema
326+
const required = schema.required || []
323327

324328
const propertiesLocation = location.getPropertyLocation('properties')
325-
const requiredProperties = schema.required || []
326-
327-
// Should serialize required properties first
328-
const propertiesKeys = Object.keys(schema.properties || {}).sort(
329-
(key1, key2) => {
330-
const required1 = requiredProperties.includes(key1)
331-
const required2 = requiredProperties.includes(key2)
332-
return required1 === required2 ? 0 : required1 ? -1 : 1
333-
}
334-
)
335-
const hasRequiredProperties = requiredProperties.includes(propertiesKeys[0])
336329

337-
let code = ''
330+
const requiredWithDefault = []
331+
const requiredWithoutDefault = []
332+
if (schema.properties) {
333+
for (const key of Object.keys(schema.properties)) {
334+
if (required.indexOf(key) === -1) {
335+
continue
336+
}
337+
let propertyLocation = propertiesLocation.getPropertyLocation(key)
338+
if (propertyLocation.schema.$ref) {
339+
propertyLocation = resolveRef(context, propertyLocation)
340+
}
338341

339-
for (const key of requiredProperties) {
340-
if (!propertiesKeys.includes(key)) {
341-
code += `if (obj['${key}'] === undefined) throw new Error('"${key}" is required!')\n`
342+
const sanitizedKey = JSON.stringify(key)
343+
344+
// Using obj['key'] !== undefined instead of obj.hasOwnProperty(prop) for perf reasons,
345+
// see https://github.com/mcollina/fast-json-stringify/pull/3 for discussion.
346+
const defaultValue = propertyLocation.schema.default
347+
if (defaultValue === undefined) {
348+
code += `if (obj[${sanitizedKey}] === undefined) throw new Error('${sanitizedKey} is required!')\n`
349+
requiredWithoutDefault.push(key)
350+
}
351+
requiredWithDefault.push(key)
342352
}
343353
}
344354

345-
code += 'let json = \'{\'\n'
346-
347-
let addComma = ''
348-
if (!hasRequiredProperties) {
349-
code += 'let addComma = false\n'
350-
addComma = '!addComma && (addComma = true) || (json += \',\')'
355+
// handle extraneous required fields
356+
for (const requiredProperty of required) {
357+
if (requiredWithDefault.indexOf(requiredProperty) !== -1) continue
358+
code += `if (obj['${requiredProperty}'] === undefined) throw new Error('"${requiredProperty}" is required!')\n`
351359
}
352360

353-
for (const key of propertiesKeys) {
354-
let propertyLocation = propertiesLocation.getPropertyLocation(key)
355-
if (propertyLocation.schema.$ref) {
356-
propertyLocation = resolveRef(context, propertyLocation)
357-
}
361+
code += `
362+
let addComma = false
363+
let json = '{'
364+
`
358365

359-
const sanitizedKey = JSON.stringify(key)
360-
const defaultValue = propertyLocation.schema.default
361-
const isRequired = requiredProperties.includes(key)
366+
if (schema.properties) {
367+
for (const key of Object.keys(schema.properties)) {
368+
let propertyLocation = propertiesLocation.getPropertyLocation(key)
369+
if (propertyLocation.schema.$ref) {
370+
propertyLocation = resolveRef(context, propertyLocation)
371+
}
362372

363-
code += `
364-
if (obj[${sanitizedKey}] !== undefined) {
373+
const sanitizedKey = JSON.stringify(key)
374+
375+
if (requiredWithoutDefault.indexOf(key) !== -1) {
376+
code += `
365377
${addComma}
366378
json += ${JSON.stringify(sanitizedKey + ':')}
367379
${buildValue(context, propertyLocation, `obj[${sanitizedKey}]`)}
368-
}`
369-
370-
if (defaultValue !== undefined) {
371-
code += ` else {
372-
${addComma}
373-
json += ${JSON.stringify(sanitizedKey + ':' + JSON.stringify(defaultValue))}
374-
}
375380
`
376-
} else if (isRequired) {
377-
code += ` else {
378-
throw new Error('${sanitizedKey} is required!')
381+
} else {
382+
// Using obj['key'] !== undefined instead of obj.hasOwnProperty(prop) for perf reasons,
383+
// see https://github.com/mcollina/fast-json-stringify/pull/3 for discussion.
384+
code += `
385+
if (obj[${sanitizedKey}] !== undefined) {
386+
${addComma}
387+
json += ${JSON.stringify(sanitizedKey + ':')}
388+
${buildValue(context, propertyLocation, `obj[${sanitizedKey}]`)}
389+
}
390+
`
391+
const defaultValue = propertyLocation.schema.default
392+
if (defaultValue !== undefined) {
393+
code += `
394+
else {
395+
${addComma}
396+
json += ${JSON.stringify(sanitizedKey + ':' + JSON.stringify(defaultValue))}
397+
}
398+
`
399+
}
379400
}
380-
`
381-
} else {
382-
code += '\n'
383-
}
384-
385-
if (hasRequiredProperties) {
386-
addComma = 'json += \',\''
387401
}
388402
}
389403

390404
if (schema.patternProperties || schema.additionalProperties) {
391-
code += buildExtraObjectPropertiesSerializer(context, location, addComma)
405+
code += buildExtraObjectPropertiesSerializer(context, location)
392406
}
393407

394408
code += `

test/allof.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ test('object with allOf and multiple schema on the allOf', (t) => {
121121
id: 1,
122122
name: 'string',
123123
tag: 'otherString'
124-
}), '{"name":"string","id":1,"tag":"otherString"}')
124+
}), '{"name":"string","tag":"otherString","id":1}')
125125
})
126126

127127
test('object with allOf and one schema on the allOf', (t) => {

test/required.test.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,18 +126,17 @@ test('object with multiple required field not in properties schema', (t) => {
126126
stringify({})
127127
t.fail()
128128
} catch (e) {
129-
t.equal(e.message, '"key1" is required!')
129+
t.equal(e.message, '"num" is required!')
130130
t.pass()
131131
}
132132

133133
try {
134134
stringify({
135-
key1: 42,
136-
key2: 42
135+
num: 42
137136
})
138137
t.fail()
139138
} catch (e) {
140-
t.equal(e.message, '"num" is required!')
139+
t.equal(e.message, '"key1" is required!')
141140
t.pass()
142141
}
143142

0 commit comments

Comments
 (0)