Skip to content

Commit c1b05d7

Browse files
authored
Merge pull request #37 from andersnm/issue35
Handle irregular object property names
2 parents 6d5f208 + 41b5efd commit c1b05d7

File tree

2 files changed

+101
-3
lines changed

2 files changed

+101
-3
lines changed

lib/validator.js

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,34 @@ function loadRules() {
2222
};
2323
}
2424

25+
// Quick regex to match most common unquoted JavaScript property names. Note the spec allows Unicode letters.
26+
// Unmatched property names will be quoted and validate slighly slower. https://www.ecma-international.org/ecma-262/5.1/#sec-7.6
27+
const identifierRegex = /^[_$a-zA-Z][_$a-zA-Z0-9]*$/;
28+
29+
// Regex to escape quoted property names for eval/new Function
30+
const escapeEvalRegex = /["'\\\n\r\u2028\u2029]/g;
31+
32+
function escapeEvalString(str) {
33+
// Based on https://github.com/joliss/js-string-escape
34+
return str.replace(escapeEvalRegex, function (character) {
35+
switch (character) {
36+
case "\"":
37+
case "'":
38+
case "\\":
39+
return "\\" + character;
40+
// Four possible LineTerminator characters need to be escaped:
41+
case "\n":
42+
return "\\n";
43+
case "\r":
44+
return "\\r";
45+
case "\u2028":
46+
return "\\u2028";
47+
case "\u2029":
48+
return "\\u2029";
49+
}
50+
});
51+
}
52+
2553
/**
2654
* Validator class constructor
2755
*
@@ -106,12 +134,14 @@ Validator.prototype.compileSchemaObject = function(schemaObject) {
106134
sourceCode.push("const errors = [];");
107135
for (let i = 0; i < compiledObject.properties.length; i++) {
108136
const property = compiledObject.properties[i];
109-
const name = property.name;
137+
const name = escapeEvalString(property.name);
138+
const propertyValueExpr = identifierRegex.test(name) ? `value.${name}` : `value["${name}"]`;
139+
110140
sourceCode.push(`propertyPath = (path !== undefined ? path + ".${name}" : "${name}");`);
111141
if (Array.isArray(property.compiledType)) {
112-
sourceCode.push(`res = this.checkSchemaType(value.${name}, properties[${i}].compiledType, propertyPath, value);`);
142+
sourceCode.push(`res = this.checkSchemaType(${propertyValueExpr}, properties[${i}].compiledType, propertyPath, value);`);
113143
} else {
114-
sourceCode.push(`res = this.checkSchemaRule(value.${name}, properties[${i}].compiledType, propertyPath, value);`);
144+
sourceCode.push(`res = this.checkSchemaRule(${propertyValueExpr}, properties[${i}].compiledType, propertyPath, value);`);
115145
}
116146
sourceCode.push("if (res !== true) {");
117147
sourceCode.push("\tthis.handleResult(errors, propertyPath, res);");

test/validator.spec.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,3 +1062,71 @@ describe("Test recursive/cyclic schema", () => {
10621062
expect(res[0].field).toBe("subcategories[1].subcategories[0].name");
10631063
});
10641064
});
1065+
1066+
describe("Test irregular object property names", () => {
1067+
const v = new Validator();
1068+
it("should compile schema with dash", () => {
1069+
const schema = {
1070+
"1-1": { type: "string" },
1071+
};
1072+
1073+
const res = v.validate({
1074+
"1-1": "test",
1075+
}, schema);
1076+
expect(res).toBe(true);
1077+
});
1078+
1079+
it("should compile schema with quotes", () => {
1080+
const schema = {
1081+
"a'bc": { type: "string" },
1082+
"a\"bc": { type: "string" },
1083+
};
1084+
1085+
const res = v.validate({ "a'bc": "test", "a\"bc": "test" }, schema);
1086+
expect(res).toBe(true);
1087+
});
1088+
1089+
it("should compile schema with linebreak", () => {
1090+
const schema = {
1091+
"a\nbc\ndef": { type: "string" },
1092+
"a\rbc": { type: "string" },
1093+
"a\u2028bc": { type: "string" },
1094+
"a\u2029bc": { type: "string" },
1095+
};
1096+
1097+
const res = v.validate({
1098+
"a\nbc\ndef": "test",
1099+
"a\rbc": "test",
1100+
"a\u2028bc": "test",
1101+
"a\u2029bc": "test",
1102+
}, schema);
1103+
expect(res).toBe(true);
1104+
});
1105+
1106+
it("should compile schema with escape characters", () => {
1107+
const schema = {
1108+
"\\o/": { type: "string" },
1109+
};
1110+
1111+
const res = v.validate({ "\\o/": "test" }, schema);
1112+
expect(res).toBe(true);
1113+
});
1114+
1115+
it("should compile schema with reserved keyword", () => {
1116+
// Reserved keywords are permitted as unquoted property names in ES5+. There is no special support for these
1117+
const schema = {
1118+
for: { type: "string" },
1119+
goto: { type: "string" },
1120+
var: { type: "string" },
1121+
try: { type: "string" },
1122+
};
1123+
1124+
const res = v.validate({
1125+
for: "hello",
1126+
goto: "hello",
1127+
var: "test",
1128+
try: "test",
1129+
}, schema);
1130+
expect(res).toBe(true);
1131+
});
1132+
});

0 commit comments

Comments
 (0)