diff --git a/README.md b/README.md index f3a0d206..0439973e 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,8 @@ const fastJson = require('fast-json-stringify') const stringify = fastJson(mySchema, { schema: { ... }, ajv: { ... }, - rounding: 'ceil' + rounding: 'ceil', + enableStream: false }) ``` @@ -127,6 +128,7 @@ const stringify = fastJson(mySchema, { - `rounding`: setup how the `integer` types will be rounded when not integers. [More details](#integer) - `largeArrayMechanism`: set the mechanism that should be used to handle large (by default `20000` or more items) arrays. [More details](#largearrays) +- `enableStream`: write the json on a stream [More details](#stream) @@ -648,6 +650,37 @@ const stringify = fastJson({ }) ``` + +#### stream +You can set specific a stream to write +Example: +```javascript +const fastJson = require('fast-json-stringify') + +const schema = { + title: 'Example Schema', + type: 'object', + properties: { + foo: { + type: 'string' + } +} +const stringify = fastJson(schema, { enableStream: true }) + +const data = { foo: 'bar' } +const stream = new Readable() +stringify(data, stream); + +const chunks = [] +stream.on('data', (chunk) => { + const str = Buffer.from(chunk).toString('utf8') + console.log('chunk', str) + chunks.push(str) +}) +stream.on('end', () => console.log(chunks.join(''))) +``` + + ##### Benchmarks For reference, here goes some benchmarks for comparison over the three diff --git a/index.js b/index.js index 9bb50116..71703c34 100644 --- a/index.js +++ b/index.js @@ -155,9 +155,14 @@ function build (schema, options) { ` } else { contextFunctionCode = ` - function main (input) { + let stream + function main (input, _stream) { + stream = _stream let json = '' ${code} + if( stream ){ + stream.push(null) + } return json } ${context.functions.join('\n')} @@ -263,7 +268,7 @@ function buildExtraObjectPropertiesSerializer (context, location, addComma) { const schema = location.schema const propertiesKeys = Object.keys(schema.properties || {}) - let code = ` + let code = `/** begin buildExtraObjectPropertiesSerializer */ const propertiesKeys = ${JSON.stringify(propertiesKeys)} for (const [key, value] of Object.entries(obj)) { if ( @@ -313,6 +318,8 @@ function buildExtraObjectPropertiesSerializer (context, location, addComma) { code += ` } + + /** end buildExtraObjectPropertiesSerializer */ ` return code } @@ -333,7 +340,9 @@ function buildInnerObject (context, location) { ) const hasRequiredProperties = requiredProperties.includes(propertiesKeys[0]) - let code = 'let value\n' + let code = `/** begin buildInnerObject */ + let value + ` for (const key of requiredProperties) { if (!propertiesKeys.includes(key)) { @@ -363,9 +372,16 @@ function buildInnerObject (context, location) { value = obj[${sanitizedKey}] if (value !== undefined) { ${addComma} - json += ${JSON.stringify(sanitizedKey + ':')} - ${buildValue(context, propertyLocation, 'value')} - }` + json += ${JSON.stringify(sanitizedKey + ':')}` + if (context.options.enableStream) { + code += ` + stream.push(json) + json = ''; + ` + } + code += ` + ${buildValue(context, propertyLocation, 'value')} + }` if (defaultValue !== undefined) { code += ` else { @@ -392,7 +408,21 @@ function buildInnerObject (context, location) { } code += ` - return json + '}' + json += '}' + ` + + if (context.options.enableStream) { + code += ` + if( json ){ + stream.push(json) + json = '' + } + ` + } + + code += ` + return json + /** end buildInnerObject */ ` return code } @@ -476,6 +506,7 @@ function buildObject (context, location) { } let functionCode = ` + /** begin buildObject */ ` const nullable = schema.nullable === true @@ -487,6 +518,8 @@ function buildObject (context, location) { ${buildInnerObject(context, location)} } + + /** end buildObject */ ` context.functions.push(functionCode) @@ -518,13 +551,15 @@ function buildArray (context, location) { } let functionCode = ` + /** begin buildArray */ + function ${functionName} (obj) { // ${schemaRef} ` const nullable = schema.nullable === true functionCode += ` - ${!nullable ? 'if (obj === null) return \'[]\'' : ''} + ${!nullable ? `if (obj === null) return ${context.options.enableStream ? 'stream.push(\'[]\')' : '\'[]\''}` : ''} if (!Array.isArray(obj)) { throw new TypeError(\`The value of '${schemaRef}' does not match schema definition.\`) } @@ -540,7 +575,13 @@ function buildArray (context, location) { } if (largeArrayMechanism === 'json-stringify') { - functionCode += `if (arrayLength && arrayLength >= ${largeArraySize}) return JSON.stringify(obj)\n` + functionCode += `if (arrayLength && arrayLength >= ${largeArraySize}) return ${context.options.enableStream ? 'stream.push(JSON.stringify(obj))' : 'JSON.stringify(obj)'}\n` + } + + if (context.options.enableStream) { + functionCode += ` + stream.push('[') + ` } functionCode += ` @@ -548,11 +589,22 @@ function buildArray (context, location) { let jsonOutput = '' ` + let codePushLoop = '' + if (context.options.enableStream) { + codePushLoop = ` + if( jsonOutput ){ + stream.push(jsonOutput) + jsonOutput = '' + } + ` + } + if (Array.isArray(itemsSchema)) { for (let i = 0; i < itemsSchema.length; i++) { const item = itemsSchema[i] functionCode += `value = obj[${i}]` const tmpRes = buildValue(context, itemsLocation.getPropertyLocation(i), 'value') + functionCode += ` if (${i} < arrayLength) { if (${buildArrayTypeCondition(item.type, `[${i}]`)}) { @@ -576,6 +628,7 @@ function buildArray (context, location) { if (i < arrayLength - 1) { jsonOutput += ',' } + ${codePushLoop} }` } } else { @@ -588,12 +641,25 @@ function buildArray (context, location) { if (i < arrayLength - 1) { jsonOutput += ',' } + ${codePushLoop} }` } + if (context.options.enableStream) { + functionCode += ` + stream.push(']') + return '' + ` + } else { + functionCode += ` + return \`[\${jsonOutput}]\` + ` + } + functionCode += ` - return \`[\${jsonOutput}]\` - }` + } + /** end buildArray */ + ` context.functions.push(functionCode) return functionName @@ -650,7 +716,9 @@ function buildMultiTypeSerializer (context, location, input) { const schema = location.schema const types = schema.type.sort(t1 => t1 === 'null' ? -1 : 1) - let code = '' + let code = ` + /** begin buildMultiTypeSerializer */ + ` types.forEach((type, index) => { location.schema = { ...location.schema, type } @@ -710,6 +778,8 @@ function buildMultiTypeSerializer (context, location, input) { } code += ` else throw new TypeError(\`The value of '${schemaRef}' does not match schema definition.\`) + + /** end buildMultiTypeSerializer */ ` return code @@ -980,7 +1050,7 @@ function buildValue (context, location, input) { } } - let code = '' + let code = '\n/** begin buildValue */\n' const type = schema.type const nullable = schema.nullable === true @@ -993,11 +1063,17 @@ function buildValue (context, location, input) { } if (schema.const !== undefined) { + code += '\n/** begin buildConstSerializer */\n' code += buildConstSerializer(location, input) + code += '\n/** end buildConstSerializer */\n' } else if (Array.isArray(type)) { + code += '\n/** begin buildMultiTypeSerializer */\n' code += buildMultiTypeSerializer(context, location, input) + code += '\n/** end buildMultiTypeSerializer */\n' } else { + code += '\n/** begin buildSingleTypeSerializer */\n' code += buildSingleTypeSerializer(context, location, input) + code += '\n/** end buildSingleTypeSerializer */\n' } if (nullable) { @@ -1005,7 +1081,7 @@ function buildValue (context, location, input) { } ` } - + code += '\n/** end buildValue */\n' return code }