Skip to content

Commit 30a6f55

Browse files
authored
process required first (#634)
1 parent 4b81cd6 commit 30a6f55

File tree

2 files changed

+95
-45
lines changed

2 files changed

+95
-45
lines changed

benchmark/bench.js

+22
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,28 @@ const benchmarks = [
254254
},
255255
input: { firstName: 'Max', lastName: 'Power', age: 22 }
256256
},
257+
{
258+
name: 'simple object with required fields',
259+
schema: {
260+
title: 'Example Schema',
261+
type: 'object',
262+
properties: {
263+
firstName: {
264+
type: 'string'
265+
},
266+
lastName: {
267+
type: ['string', 'null']
268+
},
269+
age: {
270+
description: 'Age in years',
271+
type: 'integer',
272+
minimum: 0
273+
}
274+
},
275+
required: ['firstName', 'lastName', 'age']
276+
},
277+
input: { firstName: 'Max', lastName: 'Power', age: 22 }
278+
},
257279
{
258280
name: 'object with const string property',
259281
schema: {

index.js

+73-45
Original file line numberDiff line numberDiff line change
@@ -298,59 +298,96 @@ function buildExtraObjectPropertiesSerializer (context, location) {
298298
}
299299

300300
function buildInnerObject (context, location) {
301+
let code = ''
301302
const schema = location.schema
302303
const required = schema.required || []
303304

304-
let code = ''
305-
306305
const propertiesLocation = location.getPropertyLocation('properties')
307-
Object.keys(schema.properties || {}).forEach((key) => {
308-
let propertyLocation = propertiesLocation.getPropertyLocation(key)
309-
if (propertyLocation.schema.$ref) {
310-
propertyLocation = resolveRef(context, location, propertyLocation.schema.$ref)
306+
307+
const requiredWithDefault = []
308+
const requiredWithoutDefault = []
309+
if (schema.properties) {
310+
for (const key of Object.keys(schema.properties)) {
311+
if (required.indexOf(key) === -1) {
312+
continue
313+
}
314+
let propertyLocation = propertiesLocation.getPropertyLocation(key)
315+
if (propertyLocation.schema.$ref) {
316+
propertyLocation = resolveRef(context, location, propertyLocation.schema.$ref)
317+
}
318+
319+
const sanitizedKey = JSON.stringify(key)
320+
321+
// Using obj['key'] !== undefined instead of obj.hasOwnProperty(prop) for perf reasons,
322+
// see https://github.com/mcollina/fast-json-stringify/pull/3 for discussion.
323+
const defaultValue = propertyLocation.schema.default
324+
if (defaultValue === undefined) {
325+
code += `if (obj[${sanitizedKey}] === undefined) throw new Error('${sanitizedKey} is required!')\n`
326+
requiredWithoutDefault.push(key)
327+
}
328+
requiredWithDefault.push(key)
311329
}
330+
}
312331

313-
const sanitized = JSON.stringify(key)
332+
// handle extraneous required fields
333+
for (const requiredProperty of required) {
334+
if (requiredWithDefault.indexOf(requiredProperty) !== -1) continue
335+
code += `if (obj['${requiredProperty}'] === undefined) throw new Error('"${requiredProperty}" is required!')\n`
336+
}
314337

315-
// Using obj['key'] !== undefined instead of obj.hasOwnProperty(prop) for perf reasons,
316-
// see https://github.com/mcollina/fast-json-stringify/pull/3 for discussion.
338+
code += `
339+
let addComma = false
340+
let json = '${context.wrapObjects ? '{' : ''}'
341+
`
342+
const wrapObjects = context.wrapObjects
343+
context.wrapObjects = true
317344

318-
code += `
319-
if (obj[${sanitized}] !== undefined) {
320-
${addComma}
321-
json += ${JSON.stringify(sanitized + ':')}
322-
`
345+
if (schema.properties) {
346+
for (const key of Object.keys(schema.properties)) {
347+
let propertyLocation = propertiesLocation.getPropertyLocation(key)
348+
if (propertyLocation.schema.$ref) {
349+
propertyLocation = resolveRef(context, location, propertyLocation.schema.$ref)
350+
}
323351

324-
code += buildValue(context, propertyLocation, `obj[${sanitized}]`)
352+
const sanitizedKey = JSON.stringify(key)
325353

326-
const defaultValue = propertyLocation.schema.default
327-
if (defaultValue !== undefined) {
328-
code += `
329-
} else {
354+
if (requiredWithoutDefault.indexOf(key) !== -1) {
355+
code += `
330356
${addComma}
331-
json += ${JSON.stringify(sanitized + ':' + JSON.stringify(defaultValue))}
357+
json += ${JSON.stringify(sanitizedKey + ':')}
358+
${buildValue(context, propertyLocation, `obj[${sanitizedKey}]`)}
332359
`
333-
} else if (required.includes(key)) {
334-
code += `
335360
} else {
336-
throw new Error('${sanitized} is required!')
337-
`
338-
}
339-
340-
code += `
361+
// Using obj['key'] !== undefined instead of obj.hasOwnProperty(prop) for perf reasons,
362+
// see https://github.com/mcollina/fast-json-stringify/pull/3 for discussion.
363+
code += `
364+
if (obj[${sanitizedKey}] !== undefined) {
365+
${addComma}
366+
json += ${JSON.stringify(sanitizedKey + ':')}
367+
${buildValue(context, propertyLocation, `obj[${sanitizedKey}]`)}
368+
}
369+
`
370+
const defaultValue = propertyLocation.schema.default
371+
if (defaultValue !== undefined) {
372+
code += `
373+
else {
374+
${addComma}
375+
json += ${JSON.stringify(sanitizedKey + ':' + JSON.stringify(defaultValue))}
376+
}
377+
`
378+
}
341379
}
342-
`
343-
})
344-
345-
for (const requiredProperty of required) {
346-
if (schema.properties && schema.properties[requiredProperty] !== undefined) continue
347-
code += `if (obj['${requiredProperty}'] === undefined) throw new Error('"${requiredProperty}" is required!')\n`
380+
}
348381
}
349382

350383
if (schema.patternProperties || schema.additionalProperties) {
351384
code += buildExtraObjectPropertiesSerializer(context, location)
352385
}
353386

387+
context.wrapObjects = wrapObjects
388+
code += `
389+
return json${context.wrapObjects ? ' + \'}\'' : ''}
390+
`
354391
return code
355392
}
356393

@@ -505,22 +542,13 @@ function buildObject (context, location) {
505542
}
506543

507544
let functionCode = `
508-
function ${functionName} (input) {
509-
// ${schemaRef}
510545
`
511546

512547
functionCode += `
548+
// ${schemaRef}
549+
function ${functionName} (input) {
513550
const obj = ${toJSON('input')}
514-
let json = '${context.wrapObjects ? '{' : ''}'
515-
let addComma = false
516-
`
517-
518-
const wrapObjects = context.wrapObjects
519-
context.wrapObjects = true
520-
functionCode += buildInnerObject(context, location)
521-
context.wrapObjects = wrapObjects
522-
functionCode += `
523-
return json${context.wrapObjects ? ' + \'}\'' : ''}
551+
${buildInnerObject(context, location)}
524552
}
525553
`
526554

0 commit comments

Comments
 (0)