Structure of the rule list:
type Root = {
condition: Condition;
id?: number;
errorMessage?: string;
}[]
type Condition = {
type: string;
field?: string;
arg?: string | null;
args?: (string | null)[];
conditions?: Condition[];
}
For example:
[
{
"condition": {
"type": "and",
"field": "name",
"conditions": [
{
"type": "!null"
},
{
"type": "!blank"
}
]
},
"id": 1,
"errorMessage": "\"name\" is required."
},
{
"condition": {
"type": "range",
"field": "age",
"arg": "[18"
},
"id": 2,
"errorMessage": "\"age\" should be >= 18."
}
]
Consider this object:
{
"name": "John Smith",
"otherName": null,
"age": 25,
"address":
{
"city": "New York",
"state": "NY"
}
}
A field can be selected by putting its name into the expression. e.g. name
, otherName
, age
, address
. Nested fields can be selected by using dot separated expressions. e.g. address.city
, address.state
.
For maps (dictionaries), the field names are treated as keys. i.e. When address
is a map that contains a field named size
, that field can never be selected by address.size
. Instead, the value of the entry (key-value pair) with the key size
will be selected.
In C♯ and Java, undeclared field names are valid for null
but invalid for non-null values. e.g. otherName.city
returns null
while name.city
throws an exception. In JavaScript / TypeScript, both of them are valid but the returned values depend on the values. e.g. otherName.city
returns null
while name.city
returns undefined
.
Field expressions are nested. For example, this is a valid rule for the above object:
{
"condition": {
"type": "and",
"field": "address",
"conditions": [
{
"type": "!null"
},
{
"type": "!null",
"field": "city"
}
]
}
}
For iterable objects (e.g. Set
), iteration can be performed by using *
. Consider this object:
{
"name": "John Smith",
"phoneNumber":
[
{
"type": "home",
"number": "212 555-1234"
},
{
"type": "fax",
"number": "646 555-4567"
}
]
}
phoneNumber.*
can be used to iterate over phoneNumber
. For example, this rule will be passed only when all the phone numbers are not null
:
{
"condition": {
"type": "!null",
"field": "phoneNumber.*.number"
}
}
If you want to use *
as a key in a Map
(Dictionary
), please put /*
or */K
into the expression instead. (Please see Suffixes for detail.)
For lists and arrays, indices can also be used. e.g. phoneNumber.0.type
. While using an index out of bounds throws an exception in C♯ and Java, it is valid and undefined
is returned in JavaScript / TypeScript.
By default, a field name will be handled in this order:
- If the upper-level object is a
Map
(Dictionary
), the field name will be treated as a key. - If the upper-level object is a
List
/Array
and the field name can be parsed to an integer, the field name will be treated as an index. - The field name will be treated as a field name.
Therefore, if you want to express a field of a Map
(Dictionary
), a Suffix is required.
Field names ending with /?
will be treated as having a suffix, where ?
can be any character. Suffixes are case-insensitive. Supported suffixes are listed below.
C
stands for 'Concat'. This should be used when you want to have .
in a field name. e.g. part1/C.part2
represents part1.part2
as a whole field name.
F
stands for 'Field'. Field names with this suffix will be forcefully treated as a field name.
I
stands for 'Index'. Field names with this suffix will be forcefully treated as an index in a List
/ Array
.
K
stands for 'Key'. Field names with this suffix will be forcefully treated as a key in a Map
(Dictionary
).
This can be used when you want to use *
as a field name. e.g. /*
represents *
as a field name.
Sometimes a field name without suffixes looks like having a suffix. You can prevent a field name such as this from being treated as having a suffix by adding one more /
before /
. e.g. //A
represents /A
as a field name.
A NOT gate can be applied to a condition by adding !
to the beginning. e.g. !null
.
and
represents a AND gate. The conditions
field is required for this condition type. The condition is passed when all of the conditions are passed.
or
represents a OR gate. The conditions
field is required for this condition type. The condition is passed when any of the conditions are passed.
null
checks whether the value is a null value. The condition is passed when the value is null
or undefined
.
in
checks whether the string representation of the value exists in args
. The string representation is obtained by calling toString()
/ToString()
. The args
field is required for this condition type. The condition is passed when the string representation of the value exists in args
.
blank
checks whether the value is blank. It only supports string values. The condition is passed when the value is empty or contains only white space characters.
regex
checks whether the value matches the given regular expression. It only supports string values. The arg
field is required as the regular expression for this condition type. The condition is passed when the value matches the given regular expression.
bytes
checks whether the length of the value in UTF-8 encoding in bytes is within the given range. It only supports string values. The arg
field is required as the range expression for this condition type. (Please see Range Expression for detail.) The condition is passed when the length of the value in UTF-8 encoding in bytes is within the given range.
length
checks whether the length of the value is within the given range. It supports string-like and array-like values. The arg
field is required as the range expression for this condition type. (Please see Range Expression for detail.) The condition is passed when the length of the value is within the given range.
contains
checks whether the value contains the given string or null value. It supports string and iterable values. The arg
field is required as the given string or null value. For string values, the condition is passed when the value contains the given string. For iterable values, the condition is passed when any of the string representation of the elements equals to the given string or null value. The string representation is obtained by calling toString()
/ToString()
.
range
checks whether the value is within the given range. It supports number and date (C♯: DateTime, Java: Date, JavaScript: Date) values. The arg
field is required as the range expression for this condition type. ISO 8601 format is used for date. (Please see Range Expression for detail.) The condition is passed when the value is within the given range.
Ranges should be expressed as intervals. e.g. [0, 1)
, [2020-01-01T00:00:00Z
. Equality checks can be done without brackets. e.g. 100
.
true
checks whether the value is true
. It only supports boolean values. The condition is passed when the value is true
.
After validation, you can know whether the data is valid. However, to know that which rules are violated, extra information (i.e. id
, errorMessage
) are needed to be added to the rules. When a rule with id
or errorMessage
is violated, a ValidationFailure
with the same id
and message
will be added to failures
of the ValidationResult
. (Please see Classes / Types for detail.)
namespace Quicksilver.ObjectValidator;
public Validator(Rule[] rules, bool fastFail = false);
public Validator(string rulesJson, bool fastFail = false);
public bool fastFail;
public ValidationResult Validate(object? obj);
namespace Quicksilver.ObjectValidator;
public readonly bool passed;
public readonly ISet<string> failedFields;
public readonly ICollection<ValidationFailure> failures;
namespace Quicksilver.ObjectValidator;
public readonly int? id;
public readonly string? message;
namespace Quicksilver.ObjectValidator.Config;
public Rule(Condition condition, int? id = null, string? errorMessage = null);
public readonly Condition condition;
public readonly int? id;
public readonly string? errorMessage;
namespace Quicksilver.ObjectValidator.Config;
public Condition(string type, string? field, string? arg, string?[]? args, Condition[]? conditions);
public readonly string type;
public readonly string? field;
public readonly string? arg;
public readonly string?[]? args;
public readonly Condition[]? conditions;
package com.quicksilver.objectvalidator;
public Validator(Rule[] rules, boolean fastFail = false);
public Validator(String rulesJson, boolean fastFail = false) throws JsonMappingException, JsonProcessingException;
public boolean fastFail;
public ValidationResult validate(Object obj) throws ReflectiveOperationException;
package com.quicksilver.objectvalidator;
public final boolean passed;
public final Set<String> failedFields;
public final Collection<ValidationFailure> failures;
package com.quicksilver.objectvalidator;
public final Integer id;
public final String message;
package com.quicksilver.objectvalidator.config;
public Rule(Condition condition, Integer id, String errorMessage);
public final Condition condition;
public final Integer id;
public final String errorMessage;
package com.quicksilver.objectvalidator.config;
public Condition(String type, String field, String arg, String[] args, Condition[] conditions);
public final String type;
public final String field;
public final String arg;
public final String[] args;
public final Condition[] conditions;
module "@quicksilver0218/object-validator";
constructor(rules: Rule[], fastFail = false);
fastFail: boolean;
validate(obj: any): ValidationResult;
module "@quicksilver0218/object-validator";
readonly passed: boolean;
readonly failedFields: Set<string>;
readonly failures: ValidationFailure[];
module "@quicksilver0218/object-validator";
readonly id?: number;
readonly message?: string;
module "@quicksilver0218/object-validator";
readonly condition: Condition;
readonly id?: number;
readonly errorMessage?: string;
module "@quicksilver0218/object-validator";
readonly type: string;
readonly field?: string;
readonly arg?: string | null;
readonly args?: (string | null)[];
readonly conditions?: Condition[];