Skip to content
Merged
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
72 changes: 67 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@ JSON.stringify obj x 1,763,980 ops/sec ±1.30% (88 runs sampled)
fast-json-stringify obj x 5,085,148 ops/sec ±1.56% (89 runs sampled)
```

#### Table of contents:
- <a href="#example">`Example`</a>
- <a href="#api">`API`</a>
- <a href="#fastJsonStringify">`fastJsonStringify`</a>
- <a href="#specific">`Specific use cases`</a>
- <a href="#required">`Required`</a>
- <a href="#missingFields">`Missing fields`</a>
- <a href="#patternProperties">`Pattern Properties`</a>
- <a href="#additionalProperties">`Additional Properties`</a>
- <a href="#acknowledgements">`Acknowledgements`</a>
- <a href="#license">`License`</a>


<a name="example"></a>
## Example

```js
Expand Down Expand Up @@ -48,9 +62,9 @@ console.log(stringify({
reg: /"([^"]|\\")*"/
}))
```

<a name="api"></a>
## API

<a name="fastJsonStringify"></a>
### fastJsonStringify(schema)

Build a `stringify()` function based on
Expand All @@ -68,13 +82,15 @@ Supported types:

And nested ones, too.

<a name="specific"></a>
#### Specific use cases

| Instance | Serialized as |
| -----------|------------------------------|
| `Date` | `string` via `toISOString()` |
| `RegExp` | `string` |

<a name="required"></a>
#### Required
You can set specific fields of an object as required in your schema, by adding the field name inside the `required` array in your schema.
Example:
Expand All @@ -95,6 +111,7 @@ const schema = {
```
If the object to stringify has not the required field(s), `fast-json-stringify` will throw an error.

<a name="missingFields"></a>
#### Missing fields
If a field *is present* in the schema (and is not required) but it *is not present* in the object to stringify, `fast-json-stringify` will not write it in the final string.
Example:
Expand All @@ -109,8 +126,7 @@ const stringify = fastJson({
mail: {
type: 'string'
}
},
required: ['mail']
}
})

const obj = {
Expand All @@ -120,6 +136,7 @@ const obj = {
console.log(stringify(obj)) // '{"mail":"[email protected]"}'
```

<a name="patternProperties"></a>
#### Pattern properties
`fast-json-stringify` supports pattern properties as defined inside JSON schema.
*patternProperties* must be an object, where the key is a valid regex and the value is an object, declared in this way: `{ type: 'type' }`.
Expand Down Expand Up @@ -151,13 +168,58 @@ const obj = {
matchnum: 3
}

console.log(stringify(obj)) // '{"nickname":"nick","matchfoo":"42","otherfoo":"str","matchnum":3}'
console.log(stringify(obj)) // '{"matchfoo":"42","otherfoo":"str","matchnum":3,"nickname":"nick"}'
```

<a name="additionalProperties"></a>
#### Additional properties
`fast-json-stringify` supports additional properties as defined inside JSON schema.
*additionalProperties* must be an object or a boolean, declared in this way: `{ type: 'type' }`.
*additionalProperties* will work only for the properties that are not explicitly listed in the *properties* and *patternProperties* objects.

If *additionalProperties* is not present or is setted to false, every property that is not explicitly listed in the *properties* and *patternProperties* objects, will be ignored, as said in <a href="#missingFields">Missing fields</a>.
If *additionalProperties* is setted to *true*, it will be used `fast-safe-stringify` to stringify the additional properties. If you want to achieve maximum performances we strongly encourage you to use a fixed schema where possible.
Example:
```javascript
const stringify = fastJson({
title: 'Example Schema',
type: 'object',
properties: {
nickname: {
type: 'string'
}
},
patternProperties: {
'num': {
type: 'number'
},
'.*foo$': {
type: 'string'
}
},
additionalProperties: {
type: 'string'
}
})

const obj = {
nickname: 'nick',
matchfoo: 42,
otherfoo: 'str'
matchnum: 3,
nomatchstr: 'valar morghulis',
nomatchint: 313
}

console.log(stringify(obj)) // '{"matchfoo":"42","otherfoo":"str","matchnum":3,"nomatchstr":"valar morghulis",nomatchint:"313","nickname":"nick"}'
```

<a name="acknowledgements"></a>
## Acknowledgements

This project was kindly sponsored by [nearForm](http://nearform.com).

<a name="license"></a>
## License

MIT
8 changes: 7 additions & 1 deletion example.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ const stringify = fastJson({
'test': {
type: 'number'
}
},
additionalProperties: {
type: 'string'
}
})

Expand All @@ -63,5 +66,8 @@ console.log(stringify({
test: 42,
strtest: '23',
arr: [{ str: 'stark' }, { str: 'lannister' }],
obj: { bool: true }
obj: { bool: true },
notmatch: 'valar morghulis',
notmatchobj: { a: true },
notmatchnum: 42
}))
76 changes: 71 additions & 5 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use strict'

const fastSafeStringify = require('fast-safe-stringify')

function build (schema) {
/* eslint no-new-func: "off" */
var code = `
Expand Down Expand Up @@ -51,7 +53,9 @@ function build (schema) {
;
return ${main}
`

if (schema.additionalProperties === true) {
return (new Function('fastSafeStringify', code))(fastSafeStringify)
}
return (new Function(code))()
}

Expand Down Expand Up @@ -137,7 +141,7 @@ function $asRegExp (reg) {
return '"' + reg + '"'
}

function addPatternProperties (pp) {
function addPatternProperties (pp, ap) {
let code = `
var keys = Object.keys(obj)
for (var i = 0; i < keys.length; i++) {
Expand Down Expand Up @@ -176,27 +180,87 @@ function addPatternProperties (pp) {
`
} else {
code += `
throw new Error('Cannot coerce ' + obj[keys[i]] + ' to ${type}')
throw new Error('Cannot coerce ' + obj[keys[i]] + ' to ${type}')
`
}

code += `
continue
}
`
})
if (ap) {
code += additionalProperty(ap)
}

code += `
}
if (Object.keys(properties).length === 0) json = json.substring(0, json.length - 1)
`
return code
}

function additionalProperty (ap) {
let code = ''
if (ap === true) {
return `
json += $asString(keys[i]) + ':' + fastSafeStringify(obj[keys[i]]) + ','
`
}
let type = ap.type
if (type === 'object') {
code += buildObject(ap, '', 'buildObjectAP')
code += `
json += $asString(keys[i]) + ':' + buildObjectAP(obj[keys[i]]) + ','
`
} else if (type === 'array') {
code += buildArray(ap, '', 'buildArrayAP')
code += `
json += $asString(keys[i]) + ':' + buildArrayAP(obj[keys[i]]) + ','
`
} else if (type === 'null') {
code += `
json += $asString(keys[i]) +':null,'
`
} else if (type === 'string') {
code += `
json += $asString(keys[i]) + ':' + $asString(obj[keys[i]]) + ','
`
} else if (type === 'number' || type === 'integer') {
code += `
json += $asString(keys[i]) + ':' + $asNumber(obj[keys[i]]) + ','
`
} else if (type === 'boolean') {
code += `
json += $asString(keys[i]) + ':' + $asBoolean(obj[keys[i]]) + ','
`
} else {
code += `
throw new Error('Cannot coerce ' + obj[keys[i]] + ' to ${type}')
`
}
return code
}

function addAdditionalProperties (ap) {
return `
var keys = Object.keys(obj)
for (var i = 0; i < keys.length; i++) {
if (properties[keys[i]]) continue
${additionalProperty(ap)}
}
`
}

function buildObject (schema, code, name) {
code += `
function ${name} (obj) {
var json = '{'
`

if (schema.patternProperties) {
code += addPatternProperties(schema.patternProperties)
code += addPatternProperties(schema.patternProperties, schema.additionalProperties)
} else if (schema.additionalProperties && !schema.patternProperties) {
code += addAdditionalProperties(schema.additionalProperties)
}

var laterCode = ''
Expand Down Expand Up @@ -232,7 +296,9 @@ function buildObject (schema, code, name) {
`
})

// Removes the comma if is the last element of the string (in case there are not properties)
code += `
if (json[json.length - 1] === ',') json = json.substring(0, json.length - 1)
json += '}'
return json
}
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,8 @@
"pre-commit": "^1.1.3",
"standard": "^8.2.0",
"tap": "^7.1.2"
},
"dependencies": {
"fast-safe-stringify": "^1.1.0"
}
}
Loading