Skip to content

Commit 89e7387

Browse files
authored
Merge pull request #11 from mcollina/additional-properties
Additional properties support
2 parents 77daa51 + dac3f7b commit 89e7387

File tree

5 files changed

+330
-11
lines changed

5 files changed

+330
-11
lines changed

README.md

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,20 @@ JSON.stringify obj x 1,763,980 ops/sec ±1.30% (88 runs sampled)
1717
fast-json-stringify obj x 5,085,148 ops/sec ±1.56% (89 runs sampled)
1818
```
1919

20+
#### Table of contents:
21+
- <a href="#example">`Example`</a>
22+
- <a href="#api">`API`</a>
23+
- <a href="#fastJsonStringify">`fastJsonStringify`</a>
24+
- <a href="#specific">`Specific use cases`</a>
25+
- <a href="#required">`Required`</a>
26+
- <a href="#missingFields">`Missing fields`</a>
27+
- <a href="#patternProperties">`Pattern Properties`</a>
28+
- <a href="#additionalProperties">`Additional Properties`</a>
29+
- <a href="#acknowledgements">`Acknowledgements`</a>
30+
- <a href="#license">`License`</a>
31+
32+
33+
<a name="example"></a>
2034
## Example
2135

2236
```js
@@ -48,9 +62,9 @@ console.log(stringify({
4862
reg: /"([^"]|\\")*"/
4963
}))
5064
```
51-
65+
<a name="api"></a>
5266
## API
53-
67+
<a name="fastJsonStringify"></a>
5468
### fastJsonStringify(schema)
5569

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

6983
And nested ones, too.
7084

85+
<a name="specific"></a>
7186
#### Specific use cases
7287

7388
| Instance | Serialized as |
7489
| -----------|------------------------------|
7590
| `Date` | `string` via `toISOString()` |
7691
| `RegExp` | `string` |
7792

93+
<a name="required"></a>
7894
#### Required
7995
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.
8096
Example:
@@ -95,6 +111,7 @@ const schema = {
95111
```
96112
If the object to stringify has not the required field(s), `fast-json-stringify` will throw an error.
97113

114+
<a name="missingFields"></a>
98115
#### Missing fields
99116
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.
100117
Example:
@@ -109,8 +126,7 @@ const stringify = fastJson({
109126
mail: {
110127
type: 'string'
111128
}
112-
},
113-
required: ['mail']
129+
}
114130
})
115131

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

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

154-
console.log(stringify(obj)) // '{"nickname":"nick","matchfoo":"42","otherfoo":"str","matchnum":3}'
171+
console.log(stringify(obj)) // '{"matchfoo":"42","otherfoo":"str","matchnum":3,"nickname":"nick"}'
172+
```
173+
174+
<a name="additionalProperties"></a>
175+
#### Additional properties
176+
`fast-json-stringify` supports additional properties as defined inside JSON schema.
177+
*additionalProperties* must be an object or a boolean, declared in this way: `{ type: 'type' }`.
178+
*additionalProperties* will work only for the properties that are not explicitly listed in the *properties* and *patternProperties* objects.
179+
180+
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>.
181+
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.
182+
Example:
183+
```javascript
184+
const stringify = fastJson({
185+
title: 'Example Schema',
186+
type: 'object',
187+
properties: {
188+
nickname: {
189+
type: 'string'
190+
}
191+
},
192+
patternProperties: {
193+
'num': {
194+
type: 'number'
195+
},
196+
'.*foo$': {
197+
type: 'string'
198+
}
199+
},
200+
additionalProperties: {
201+
type: 'string'
202+
}
203+
})
204+
205+
const obj = {
206+
nickname: 'nick',
207+
matchfoo: 42,
208+
otherfoo: 'str'
209+
matchnum: 3,
210+
nomatchstr: 'valar morghulis',
211+
nomatchint: 313
212+
}
213+
214+
console.log(stringify(obj)) // '{"matchfoo":"42","otherfoo":"str","matchnum":3,"nomatchstr":"valar morghulis",nomatchint:"313","nickname":"nick"}'
155215
```
156216

217+
<a name="acknowledgements"></a>
157218
## Acknowledgements
158219

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

222+
<a name="license"></a>
161223
## License
162224

163225
MIT

example.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ const stringify = fastJson({
4949
'test': {
5050
type: 'number'
5151
}
52+
},
53+
additionalProperties: {
54+
type: 'string'
5255
}
5356
})
5457

@@ -63,5 +66,8 @@ console.log(stringify({
6366
test: 42,
6467
strtest: '23',
6568
arr: [{ str: 'stark' }, { str: 'lannister' }],
66-
obj: { bool: true }
69+
obj: { bool: true },
70+
notmatch: 'valar morghulis',
71+
notmatchobj: { a: true },
72+
notmatchnum: 42
6773
}))

index.js

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
'use strict'
22

3+
const fastSafeStringify = require('fast-safe-stringify')
4+
35
function build (schema) {
46
/* eslint no-new-func: "off" */
57
var code = `
@@ -51,7 +53,9 @@ function build (schema) {
5153
;
5254
return ${main}
5355
`
54-
56+
if (schema.additionalProperties === true) {
57+
return (new Function('fastSafeStringify', code))(fastSafeStringify)
58+
}
5559
return (new Function(code))()
5660
}
5761

@@ -137,7 +141,7 @@ function $asRegExp (reg) {
137141
return '"' + reg + '"'
138142
}
139143

140-
function addPatternProperties (pp) {
144+
function addPatternProperties (pp, ap) {
141145
let code = `
142146
var keys = Object.keys(obj)
143147
for (var i = 0; i < keys.length; i++) {
@@ -176,27 +180,87 @@ function addPatternProperties (pp) {
176180
`
177181
} else {
178182
code += `
179-
throw new Error('Cannot coerce ' + obj[keys[i]] + ' to ${type}')
183+
throw new Error('Cannot coerce ' + obj[keys[i]] + ' to ${type}')
180184
`
181185
}
186+
182187
code += `
188+
continue
183189
}
184190
`
185191
})
192+
if (ap) {
193+
code += additionalProperty(ap)
194+
}
195+
186196
code += `
187197
}
188-
if (Object.keys(properties).length === 0) json = json.substring(0, json.length - 1)
189198
`
190199
return code
191200
}
192201

202+
function additionalProperty (ap) {
203+
let code = ''
204+
if (ap === true) {
205+
return `
206+
json += $asString(keys[i]) + ':' + fastSafeStringify(obj[keys[i]]) + ','
207+
`
208+
}
209+
let type = ap.type
210+
if (type === 'object') {
211+
code += buildObject(ap, '', 'buildObjectAP')
212+
code += `
213+
json += $asString(keys[i]) + ':' + buildObjectAP(obj[keys[i]]) + ','
214+
`
215+
} else if (type === 'array') {
216+
code += buildArray(ap, '', 'buildArrayAP')
217+
code += `
218+
json += $asString(keys[i]) + ':' + buildArrayAP(obj[keys[i]]) + ','
219+
`
220+
} else if (type === 'null') {
221+
code += `
222+
json += $asString(keys[i]) +':null,'
223+
`
224+
} else if (type === 'string') {
225+
code += `
226+
json += $asString(keys[i]) + ':' + $asString(obj[keys[i]]) + ','
227+
`
228+
} else if (type === 'number' || type === 'integer') {
229+
code += `
230+
json += $asString(keys[i]) + ':' + $asNumber(obj[keys[i]]) + ','
231+
`
232+
} else if (type === 'boolean') {
233+
code += `
234+
json += $asString(keys[i]) + ':' + $asBoolean(obj[keys[i]]) + ','
235+
`
236+
} else {
237+
code += `
238+
throw new Error('Cannot coerce ' + obj[keys[i]] + ' to ${type}')
239+
`
240+
}
241+
return code
242+
}
243+
244+
function addAdditionalProperties (ap) {
245+
return `
246+
var keys = Object.keys(obj)
247+
for (var i = 0; i < keys.length; i++) {
248+
if (properties[keys[i]]) continue
249+
${additionalProperty(ap)}
250+
}
251+
`
252+
}
253+
193254
function buildObject (schema, code, name) {
194255
code += `
195256
function ${name} (obj) {
196257
var json = '{'
197258
`
259+
198260
if (schema.patternProperties) {
199-
code += addPatternProperties(schema.patternProperties)
261+
code += addPatternProperties(schema.patternProperties, schema.additionalProperties)
262+
} else if (schema.additionalProperties && !schema.patternProperties) {
263+
code += addAdditionalProperties(schema.additionalProperties)
200264
}
201265

202266
var laterCode = ''
@@ -232,7 +296,9 @@ function buildObject (schema, code, name) {
232296
`
233297
})
234298

299+
// Removes the comma if is the last element of the string (in case there are not properties)
235300
code += `
301+
if (json[json.length - 1] === ',') json = json.substring(0, json.length - 1)
236302
json += '}'
237303
return json
238304
}

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,8 @@
2929
"pre-commit": "^1.1.3",
3030
"standard": "^8.2.0",
3131
"tap": "^7.1.2"
32+
},
33+
"dependencies": {
34+
"fast-safe-stringify": "^1.1.0"
3235
}
3336
}

0 commit comments

Comments
 (0)