Skip to content

Commit c0f827e

Browse files
Add suffix system
1 parent 8d6ce1f commit c0f827e

38 files changed

+263
-243
lines changed

C Sharp/Object Validator Test/Object Validator Test.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
1616
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
1717
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
18-
<PackageReference Include="Quicksilver0218.ObjectValidator" Version="1.0.3" />
18+
<PackageReference Include="Quicksilver0218.ObjectValidator" Version="2.0.0" />
1919
<None Include="rules.json">
2020
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
2121
</None>

C Sharp/Object Validator/Object Validator.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<ImplicitUsings>enable</ImplicitUsings>
77
<Nullable>enable</Nullable>
88
<PackageId>Quicksilver0218.ObjectValidator</PackageId>
9-
<PackageVersion>1.0.3</PackageVersion>
9+
<PackageVersion>2.0.0</PackageVersion>
1010
<Authors>Quicksilver0218</Authors>
1111
<Copyright>Copyright (c) Quicksilver0218 2024</Copyright>
1212
<Description>

C Sharp/Object Validator/Runtime/Condition.cs

+67-22
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,39 @@ abstract class Condition(bool reversed, string? fieldExpression)
66
protected readonly bool reversed = reversed;
77
protected readonly string? fieldExpression = fieldExpression;
88

9+
private static void HandleField(object o, string field, List<object?> fields) {
10+
fields.Add(o.GetType().GetField(field)!.GetValue(o));
11+
}
12+
13+
private static void HandleIndex(IList list, int index, List<object?> fields) {
14+
fields.Add(list[index]);
15+
}
16+
17+
private static void HandleKey(IDictionary map, string key, List<object?> fields) {
18+
fields.Add(map[key]);
19+
}
20+
21+
private static void HandleFields(List<object?> fields, string name, List<object?> newFields) {
22+
foreach (object? o in fields)
23+
if (o == null)
24+
newFields.Add(null);
25+
else if (o is IDictionary d)
26+
HandleKey(d, name, newFields);
27+
else if (o is IList l && int.TryParse(name, out int index))
28+
HandleIndex(l, index, newFields);
29+
else
30+
HandleField(o, name, newFields);
31+
}
32+
933
internal bool Check(object? root, string? rootFieldExpression, HashSet<string> passedFields, HashSet<string> failedFields) {
1034
List<object?> fields = [root];
1135
if (fieldExpression != null) {
12-
if (root != null)
36+
if (root != null) {
37+
string fullName = "";
1338
foreach (string name in fieldExpression.Split('.')) {
1439
List<object?> newFields = [];
15-
if (name == "*")
40+
fullName += name;
41+
if (fullName == "*")
1642
foreach (object? o in fields)
1743
if (o == null)
1844
newFields.Add(null);
@@ -21,28 +47,47 @@ internal bool Check(object? root, string? rootFieldExpression, HashSet<string> p
2147
newFields.Add(item);
2248
else
2349
throw new Exception("Unsupported type for iteration: " + o.GetType());
24-
else {
25-
string newName;
26-
foreach (char c in name)
27-
if (c != '*') {
28-
newName = name;
29-
goto Handle;
30-
}
31-
newName = name[1..];
32-
Handle:
33-
foreach (object? o in fields)
34-
if (o == null)
35-
newFields.Add(null);
36-
else if (o is IDictionary d)
37-
newFields.Add(d[newName]);
38-
else if (o is IList l && int.TryParse(newName, out int index)) {
39-
if (index < l.Count)
40-
newFields.Add(l[index]);
41-
} else
42-
newFields.Add(o.GetType().GetField(newName)!.GetValue(o));
43-
}
50+
else if (name.Length >= 3 && name[^3..^1] == "//")
51+
fullName = fullName[0..^3] + fullName[^2..];
52+
else if (name.Length >= 2 && name[^2] == '/') {
53+
fullName = fullName[0..^2];
54+
switch (char.ToUpper(name[^1])) {
55+
case 'C':
56+
fullName += ".";
57+
continue;
58+
case 'F':
59+
foreach (object? o in fields)
60+
if (o == null)
61+
newFields.Add(null);
62+
else
63+
HandleField(o, fullName, newFields);
64+
break;
65+
case 'I':
66+
foreach (object? o in fields)
67+
if (o == null)
68+
newFields.Add(null);
69+
else
70+
HandleIndex((IList)o, int.Parse(fullName), newFields);
71+
break;
72+
case 'K':
73+
foreach (object? o in fields)
74+
if (o == null)
75+
newFields.Add(null);
76+
else
77+
HandleKey((IDictionary)o, fullName, newFields);
78+
break;
79+
case '*':
80+
HandleFields(fields, fullName + "*", newFields);
81+
break;
82+
default:
83+
throw new Exception("Unsupported suffix: " + name[^1]);
84+
}
85+
} else
86+
HandleFields(fields, name, newFields);
4487
fields = newFields;
88+
fullName = "";
4589
}
90+
}
4691
if (rootFieldExpression != null)
4792
rootFieldExpression += "." + fieldExpression;
4893
else

Document.md

+35-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
- [Field Expression](#field-expression)
44
- [General](#general)
55
- [Iterable](#iterable)
6+
- [Type Precedence and Suffixes](#type-precedence-and-suffixes)
7+
- [Type Precedence](#type-precedence)
8+
- [Suffixes](#suffixes)
69
- [Condition Types](#condition-types)
710
- [Not (!)](#not-)
811
- [And](#and)
@@ -161,9 +164,39 @@ For iterable objects (e.g. `Set`), iteration can be performed by using `*`. Cons
161164
}
162165
```
163166
164-
If a string only containing 1 or more `*` should be used as a key in a map (dictionary), an extra `*` should be added. e.g. `"**"` represents `"*"` as a key and `"***"` represents `"**"` as a key.
167+
If you want to use `*` as a key in a `Map` (`Dictionary`), please put `/*` or `*/K` into the expression instead. (Please see [Suffixes](#suffixes) for detail.)
165168
166-
For array-like objects (e.g. `Array`), 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.
169+
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.
170+
171+
#### Type Precedence and Suffixes
172+
##### Type Precedence
173+
By default, a field name will be handled in this order:
174+
1. If the upper-level object is a `Map` (`Dictionary`), the field name will be treated as a key.
175+
2. 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.
176+
3. The field name will be treated as a field name.
177+
178+
Therefore, if you want to express a field of a `Map` (`Dictionary`), a [Suffix](#suffixes) is required.
179+
180+
##### Suffixes
181+
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.
182+
183+
###### C
184+
`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.
185+
186+
###### F
187+
`F` stands for 'Field'. Field names with this suffix will be forcefully treated as a field name.
188+
189+
###### I
190+
`I` stands for 'Index'. Field names with this suffix will be forcefully treated as an index in a `List` / `Array`.
191+
192+
###### K
193+
`K` stands for 'Key'. Field names with this suffix will be forcefully treated as a key in a `Map` (`Dictionary`).
194+
195+
###### *
196+
This can be used when you want to use `*` as a field name. e.g. `/*` represents `*` as a field name.
197+
198+
###### Escape Character (/)
199+
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.
167200
168201
### Condition Types
169202
#### Not (!)

Java/Object Validator/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@
44
# Ignore Gradle build output directory
55
build
66

7+
lib/bin
78
.vscode
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Java/Object Validator/lib/bin/test/rules.json

-155
This file was deleted.

Java/Object Validator/lib/build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ tasks.named('test') {
4646
}
4747

4848
archivesBaseName = 'com.quicksilver.objectvalidator'
49-
version = '1.0.3'
49+
version = '2.0.0'
5050
description = 'A library providing functions that let users validate the values in objects with JSON-formatted rules.'
5151
group = 'io.github.quicksilver0218'
5252

0 commit comments

Comments
 (0)