Skip to content

Commit 78511fe

Browse files
authored
Merge pull request #7 from mcollina/pattern-properties
Pattern properties support
2 parents 6053c63 + cefb86c commit 78511fe

File tree

4 files changed

+294
-8
lines changed

4 files changed

+294
-8
lines changed

README.md

+38-3
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ Build a `stringify()` function based on
5858

5959
Supported types:
6060

61+
* `'string'`
6162
* `'integer'`
6263
* `'number'`
6364
* `'array'`
@@ -106,10 +107,10 @@ const stringify = fastJson({
106107
type: 'string'
107108
},
108109
mail: {
109-
type: 'string',
110-
required: true
110+
type: 'string'
111111
}
112-
}
112+
},
113+
required: ['mail']
113114
})
114115

115116
const obj = {
@@ -119,6 +120,40 @@ const obj = {
119120
console.log(stringify(obj)) // '{"mail":"[email protected]"}'
120121
```
121122

123+
#### Pattern properties
124+
`fast-json-stringify` supports pattern properties as defined inside JSON schema.
125+
*patternProperties* must be an object, where the key is a valid regex and the value is an object, declared in this way: `{ type: 'type' }`.
126+
*patternProperties* will work only for the properties that are not explicitly listed in the properties object.
127+
Example:
128+
```javascript
129+
const stringify = fastJson({
130+
title: 'Example Schema',
131+
type: 'object',
132+
properties: {
133+
nickname: {
134+
type: 'string'
135+
}
136+
},
137+
patternProperties: {
138+
'num': {
139+
type: 'number'
140+
},
141+
'.*foo$': {
142+
type: 'string'
143+
}
144+
}
145+
})
146+
147+
const obj = {
148+
nickname: 'nick',
149+
matchfoo: 42,
150+
otherfoo: 'str'
151+
matchnum: 3
152+
}
153+
154+
console.log(stringify(obj)) // '{"nickname":"nick","matchfoo":"42","otherfoo":"str","matchnum":3}'
155+
```
156+
122157
## Acknowledgements
123158

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

example.js

+35-2
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,48 @@ const stringify = fastJson({
2020
},
2121
reg: {
2222
type: 'string'
23+
},
24+
obj: {
25+
type: 'object',
26+
properties: {
27+
bool: {
28+
type: 'boolean'
29+
}
30+
}
31+
},
32+
arr: {
33+
type: 'array',
34+
items: {
35+
type: 'object',
36+
properties: {
37+
str: {
38+
type: 'string'
39+
}
40+
}
41+
}
2342
}
2443
},
25-
required: ['now']
44+
required: ['now'],
45+
patternProperties: {
46+
'.*foo$': {
47+
type: 'string'
48+
},
49+
'test': {
50+
type: 'number'
51+
}
52+
}
2653
})
2754

2855
console.log(stringify({
2956
firstName: 'Matteo',
3057
lastName: 'Collina',
3158
age: 32,
3259
now: new Date(),
33-
reg: /"([^"]|\\")*"/
60+
reg: /"([^"]|\\")*"/,
61+
foo: 'hello',
62+
numfoo: 42,
63+
test: 42,
64+
strtest: '23',
65+
arr: [{ str: 'stark' }, { str: 'lannister' }],
66+
obj: { bool: true }
3467
}))

index.js

+64-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@ function build (schema) {
44
/*eslint no-new-func: "off"*/
55
var code = `
66
'use strict'
7-
7+
`
8+
// used to support patternProperties and additionalProperties
9+
// they need to check if a field belongs to the properties in the schema
10+
code += `
11+
const properties = ${JSON.stringify(schema.properties)}
12+
`
13+
code += `
814
${$asString.toString()}
915
${$asStringSmall.toString()}
1016
${$asStringLong.toString()}
@@ -58,7 +64,7 @@ function $asNumber (i) {
5864
if (isNaN(num)) {
5965
return 'null'
6066
} else {
61-
return '' + i
67+
return '' + num
6268
}
6369
}
6470

@@ -131,11 +137,67 @@ function $asRegExp (reg) {
131137
return '"' + reg + '"'
132138
}
133139

140+
function addPatternProperties (pp) {
141+
let code = `
142+
var keys = Object.keys(obj)
143+
for (var i = 0; i < keys.length; i++) {
144+
if (properties[keys[i]]) continue
145+
`
146+
Object.keys(pp).forEach((regex, index) => {
147+
var type = pp[regex].type
148+
code += `
149+
if (/${regex}/.test(keys[i])) {
150+
`
151+
if (type === 'object') {
152+
code += buildObject(pp[regex], '', 'buildObjectPP' + index)
153+
code += `
154+
json += $asString(keys[i]) + ':' + buildObjectPP${index}(obj[keys[i]]) + ','
155+
`
156+
} else if (type === 'array') {
157+
code += buildArray(pp[regex], '', 'buildArrayPP' + index)
158+
code += `
159+
json += $asString(keys[i]) + ':' + buildArrayPP${index}(obj[keys[i]]) + ','
160+
`
161+
} else if (type === 'null') {
162+
code += `
163+
json += $asString(keys[i]) +':null,'
164+
`
165+
} else if (type === 'string') {
166+
code += `
167+
json += $asString(keys[i]) + ':' + $asString(obj[keys[i]]) + ','
168+
`
169+
} else if (type === 'number' || type === 'integer') {
170+
code += `
171+
json += $asString(keys[i]) + ':' + $asNumber(obj[keys[i]]) + ','
172+
`
173+
} else if (type === 'boolean') {
174+
code += `
175+
json += $asString(keys[i]) + ':' + $asBoolean(obj[keys[i]]) + ','
176+
`
177+
} else {
178+
code += `
179+
throw new Error('Cannot coerce ' + obj[keys[i]] + ' to ${type}')
180+
`
181+
}
182+
code += `
183+
}
184+
`
185+
})
186+
code += `
187+
}
188+
if (Object.keys(properties).length === 0) json = json.substring(0, json.length - 1)
189+
`
190+
return code
191+
}
192+
134193
function buildObject (schema, code, name) {
135194
code += `
136195
function ${name} (obj) {
137196
var json = '{'
138197
`
198+
if (schema.patternProperties) {
199+
code += addPatternProperties(schema.patternProperties)
200+
}
139201

140202
var laterCode = ''
141203

@@ -177,7 +239,6 @@ function buildObject (schema, code, name) {
177239
`
178240

179241
code += laterCode
180-
181242
return code
182243
}
183244

test.js

+157
Original file line numberDiff line numberDiff line change
@@ -316,3 +316,160 @@ test('missing values', (t) => {
316316
t.equal('{"str":"string","val":"value"}', stringify({ str: 'string', val: 'value' }))
317317
t.equal('{"str":"string","num":42,"val":"value"}', stringify({ str: 'string', num: 42, val: 'value' }))
318318
})
319+
320+
test('patternProperties', (t) => {
321+
t.plan(1)
322+
const stringify = build({
323+
title: 'patternProperties',
324+
type: 'object',
325+
properties: {
326+
str: {
327+
type: 'string'
328+
}
329+
},
330+
patternProperties: {
331+
'foo': {
332+
type: 'string'
333+
}
334+
}
335+
})
336+
337+
let obj = { str: 'test', foo: 42, ofoo: true, foof: 'string', objfoo: {a: true}, notMe: false }
338+
t.equal('{"foo":"42","ofoo":"true","foof":"string","objfoo":"[object Object]","str":"test"}', stringify(obj))
339+
})
340+
341+
test('patternProperties should not change properties', (t) => {
342+
t.plan(1)
343+
const stringify = build({
344+
title: 'patternProperties should not change properties',
345+
type: 'object',
346+
properties: {
347+
foo: {
348+
type: 'string'
349+
}
350+
},
351+
patternProperties: {
352+
foo: {
353+
type: 'number'
354+
}
355+
}
356+
})
357+
358+
const obj = { foo: '42', ofoo: 42 }
359+
t.equal('{"ofoo":42,"foo":"42"}', stringify(obj))
360+
})
361+
362+
test('patternProperties - string coerce', (t) => {
363+
t.plan(1)
364+
const stringify = build({
365+
title: 'check string coerce',
366+
type: 'object',
367+
properties: {},
368+
patternProperties: {
369+
foo: {
370+
type: 'string'
371+
}
372+
}
373+
})
374+
375+
const obj = { foo: true, ofoo: 42, arrfoo: ['array', 'test'], objfoo: { a: 'world' } }
376+
t.equal('{"foo":"true","ofoo":"42","arrfoo":"array,test","objfoo":"[object Object]"}', stringify(obj))
377+
})
378+
379+
test('patternProperties - number coerce', (t) => {
380+
t.plan(1)
381+
const stringify = build({
382+
title: 'check number coerce',
383+
type: 'object',
384+
properties: {},
385+
patternProperties: {
386+
foo: {
387+
type: 'number'
388+
}
389+
}
390+
})
391+
392+
const obj = { foo: true, ofoo: '42', xfoo: 'string', arrfoo: [1, 2], objfoo: { num: 42 } }
393+
t.equal('{"foo":1,"ofoo":42,"xfoo":null,"arrfoo":null,"objfoo":null}', stringify(obj))
394+
})
395+
396+
test('patternProperties - boolean coerce', (t) => {
397+
t.plan(1)
398+
const stringify = build({
399+
title: 'check boolean coerce',
400+
type: 'object',
401+
properties: {},
402+
patternProperties: {
403+
foo: {
404+
type: 'boolean'
405+
}
406+
}
407+
})
408+
409+
const obj = { foo: 'true', ofoo: 0, arrfoo: [1, 2], objfoo: { a: true } }
410+
t.equal('{"foo":true,"ofoo":false,"arrfoo":true,"objfoo":true}', stringify(obj))
411+
})
412+
413+
test('patternProperties - object coerce', (t) => {
414+
t.plan(1)
415+
const stringify = build({
416+
title: 'check object coerce',
417+
type: 'object',
418+
properties: {},
419+
patternProperties: {
420+
foo: {
421+
type: 'object',
422+
properties: {
423+
answer: {
424+
type: 'number'
425+
}
426+
}
427+
}
428+
}
429+
})
430+
431+
const obj = { objfoo: { answer: 42 } }
432+
t.equal('{"objfoo":{"answer":42}}', stringify(obj))
433+
})
434+
435+
test('patternProperties - array coerce', (t) => {
436+
t.plan(1)
437+
const stringify = build({
438+
title: 'check array coerce',
439+
type: 'object',
440+
properties: {},
441+
patternProperties: {
442+
foo: {
443+
type: 'array',
444+
items: {
445+
type: 'string'
446+
}
447+
}
448+
}
449+
})
450+
451+
const obj = { foo: 'true', ofoo: 0, arrfoo: [1, 2], objfoo: { tyrion: 'lannister' } }
452+
t.equal('{"foo":["t","r","u","e"],"ofoo":[],"arrfoo":["1","2"],"objfoo":[]}', stringify(obj))
453+
})
454+
455+
test('patternProperties - throw on unknown type', (t) => {
456+
t.plan(1)
457+
const stringify = build({
458+
title: 'check array coerce',
459+
type: 'object',
460+
properties: {},
461+
patternProperties: {
462+
foo: {
463+
type: 'strangetype'
464+
}
465+
}
466+
})
467+
468+
const obj = { foo: 'true', ofoo: 0, arrfoo: [1, 2], objfoo: { tyrion: 'lannister' } }
469+
try {
470+
stringify(obj)
471+
t.fail()
472+
} catch (e) {
473+
t.pass()
474+
}
475+
})

0 commit comments

Comments
 (0)