Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

stream stringify #709

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,8 @@ const fastJson = require('fast-json-stringify')
const stringify = fastJson(mySchema, {
schema: { ... },
ajv: { ... },
rounding: 'ceil'
rounding: 'ceil',
enableStream: false
})
```

Expand All @@ -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)


<a name="api"></a>
Expand Down Expand Up @@ -648,6 +650,37 @@ const stringify = fastJson({
})
```

<a name="stream"></a>
#### 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
Expand Down
104 changes: 90 additions & 14 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')}
Expand Down Expand Up @@ -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 (
Expand Down Expand Up @@ -313,6 +318,8 @@ function buildExtraObjectPropertiesSerializer (context, location, addComma) {

code += `
}

/** end buildExtraObjectPropertiesSerializer */
`
return code
}
Expand All @@ -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)) {
Expand Down Expand Up @@ -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 {
Expand All @@ -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
}
Expand Down Expand Up @@ -476,6 +506,7 @@ function buildObject (context, location) {
}

let functionCode = `
/** begin buildObject */
`

const nullable = schema.nullable === true
Expand All @@ -487,6 +518,8 @@ function buildObject (context, location) {

${buildInnerObject(context, location)}
}

/** end buildObject */
`

context.functions.push(functionCode)
Expand Down Expand Up @@ -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.\`)
}
Expand All @@ -540,19 +575,36 @@ 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 += `
let value
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}]`)}) {
Expand All @@ -576,6 +628,7 @@ function buildArray (context, location) {
if (i < arrayLength - 1) {
jsonOutput += ','
}
${codePushLoop}
}`
}
} else {
Expand All @@ -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
Expand Down Expand Up @@ -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 }
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -993,19 +1063,25 @@ 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) {
code += `
}
`
}

code += '\n/** end buildValue */\n'
return code
}

Expand Down