Skip to content

Commit e716ab4

Browse files
authored
feat: Array support explicit type (#83)
1 parent 76f7936 commit e716ab4

File tree

5 files changed

+391
-78
lines changed

5 files changed

+391
-78
lines changed

README.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ Schema for data modeling & validation
6262
- [`minLength(minLength: number, errorMessage?: string)`](#minlengthminlength-number-errormessage-string-1)
6363
- [`maxLength(maxLength: number, errorMessage?: string)`](#maxlengthmaxlength-number-errormessage-string-1)
6464
- [`unrepeatable(errorMessage?: string)`](#unrepeatableerrormessage-string)
65-
- [`of(type: object)`](#oftype-object)
65+
- [`of()`](#of)
6666
- [DateType(errorMessage?: string)](#datetypeerrormessage-string)
6767
- [`range(min: Date, max: Date, errorMessage?: string)`](#rangemin-date-max-date-errormessage-string)
6868
- [`min(min: Date, errorMessage?: string)`](#minmin-date-errormessage-string)
@@ -726,10 +726,24 @@ ArrayType().maxLength(3, "Can't exceed three");
726726
ArrayType().unrepeatable('Duplicate options cannot appear');
727727
```
728728

729-
#### `of(type: object)`
729+
#### `of()`
730730

731731
```js
732+
// for every element of array
732733
ArrayType().of(StringType('The tag should be a string').isRequired());
734+
// for every element of array
735+
ArrayType().of(
736+
ObjectType().shape({
737+
name: StringType().isEmail()
738+
})
739+
);
740+
// just specify the first and the second element
741+
ArrayType().of(
742+
StringType().isEmail(),
743+
ObjectType().shape({
744+
name: StringType().isEmail()
745+
})
746+
);
733747
```
734748

735749
### DateType(errorMessage?: string)

src/ArrayType.ts

Lines changed: 56 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export class ArrayType<DataType = any, E = ErrorMessageType> extends MixedType<
88
E,
99
ArrayTypeLocale
1010
> {
11-
[arrayTypeSchemaSpec]: MixedType<any, DataType, E>;
11+
[arrayTypeSchemaSpec]: MixedType<any, DataType, E> | MixedType<any, DataType, E>[];
1212
private isArrayTypeNested = false;
1313

1414
constructor(errorMessage?: E | string) {
@@ -75,44 +75,66 @@ export class ArrayType<DataType = any, E = ErrorMessageType> extends MixedType<
7575
return this;
7676
}
7777

78-
of(type: MixedType<any, DataType, E>) {
79-
this[arrayTypeSchemaSpec] = type;
80-
81-
// Mark inner ArrayType as nested when dealing with nested arrays
82-
if (type instanceof ArrayType) {
83-
type.isArrayTypeNested = true;
84-
}
78+
of(...types: MixedType<any, DataType, E>[]) {
79+
if (types.length === 1) {
80+
const type = types[0];
81+
this[arrayTypeSchemaSpec] = type;
8582

86-
super.pushRule({
87-
onValid: (items, data, fieldName) => {
88-
// For non-array values in nested arrays, pass directly to inner type validation
89-
if (!Array.isArray(items) && this.isArrayTypeNested) {
90-
return type.check(items, data, fieldName);
91-
}
83+
// Mark inner ArrayType as nested when dealing with nested arrays
84+
if (type instanceof ArrayType) {
85+
type.isArrayTypeNested = true;
86+
}
9287

93-
// For non-array values in non-nested arrays, return array type error
94-
if (!Array.isArray(items)) {
95-
return {
96-
hasError: true,
97-
errorMessage: this.locale.type
98-
};
99-
}
88+
super.pushRule({
89+
onValid: (items, data, fieldName) => {
90+
// For non-array values in nested arrays, pass directly to inner type validation
91+
if (!Array.isArray(items) && this.isArrayTypeNested) {
92+
return type.check(items, data, fieldName);
93+
}
10094

101-
const checkResults = items.map((value, index) => {
102-
const name = Array.isArray(fieldName)
103-
? [...fieldName, `[${index}]`]
104-
: [fieldName, `[${index}]`];
95+
// For non-array values in non-nested arrays, return array type error
96+
if (!Array.isArray(items)) {
97+
return {
98+
hasError: true,
99+
errorMessage: this.locale.type
100+
};
101+
}
105102

106-
return type.check(value, data, name as string[]);
107-
});
108-
const hasError = !!checkResults.find(item => item?.hasError);
103+
const checkResults = items.map((value, index) => {
104+
const name = Array.isArray(fieldName)
105+
? [...fieldName, `[${index}]`]
106+
: [fieldName, `[${index}]`];
109107

110-
return {
111-
hasError,
112-
array: checkResults
113-
} as CheckResult<string | E>;
114-
}
115-
});
108+
return type.check(value, data, name as string[]);
109+
});
110+
const hasError = !!checkResults.find(item => item?.hasError);
111+
112+
return {
113+
hasError,
114+
array: checkResults
115+
} as CheckResult<string | E>;
116+
}
117+
});
118+
} else {
119+
this[arrayTypeSchemaSpec] = types;
120+
super.pushRule({
121+
onValid: (items, data, fieldName) => {
122+
const checkResults = items.map((value, index) => {
123+
const name = Array.isArray(fieldName)
124+
? [...fieldName, `[${index}]`]
125+
: [fieldName, `[${index}]`];
126+
127+
return types[index].check(value, data, name as string[]);
128+
});
129+
const hasError = !!checkResults.find(item => item?.hasError);
130+
131+
return {
132+
hasError,
133+
array: checkResults
134+
} as CheckResult<string | E>;
135+
}
136+
});
137+
}
116138

117139
return this;
118140
}

src/MixedType.ts

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -32,34 +32,50 @@ export const arrayTypeSchemaSpec = 'arrayTypeSchemaSpec';
3232
* Get the field type from the schema object
3333
*/
3434
export function getFieldType(schemaSpec: any, fieldName: string, nestedObject?: boolean) {
35-
if (nestedObject) {
36-
const namePath = fieldName.split('.');
37-
const currentField = namePath[0];
38-
const arrayMatch = currentField.match(/(\w+)\[(\d+)\]/);
35+
if (schemaSpec) {
36+
if (nestedObject) {
37+
const namePath = fieldName.split('.');
38+
const currentField = namePath[0];
39+
const arrayMatch = currentField.match(/(\w+)\[(\d+)\]/);
40+
if (arrayMatch) {
41+
const [, arrayField, arrayIndex] = arrayMatch;
42+
const type = schemaSpec[arrayField];
43+
if (type?.[arrayTypeSchemaSpec]) {
44+
const arrayType = type[arrayTypeSchemaSpec];
3945

40-
if (arrayMatch) {
41-
const [, arrayField] = arrayMatch;
42-
const type = schemaSpec[arrayField];
46+
if (namePath.length > 1) {
47+
if (arrayType[schemaSpecKey]) {
48+
return getFieldType(arrayType[schemaSpecKey], namePath.slice(1).join('.'), true);
49+
}
50+
if (Array.isArray(arrayType) && arrayType[parseInt(arrayIndex)][schemaSpecKey]) {
51+
return getFieldType(
52+
arrayType[parseInt(arrayIndex)][schemaSpecKey],
53+
namePath.slice(1).join('.'),
54+
true
55+
);
56+
}
57+
}
58+
if (Array.isArray(arrayType)) {
59+
return arrayType[parseInt(arrayIndex)];
60+
}
61+
// Otherwise return the array element type directly
62+
return arrayType;
63+
}
64+
return type;
65+
} else {
66+
const type = schemaSpec[currentField];
4367

44-
if (type?.[arrayTypeSchemaSpec]) {
45-
// If there are remaining paths and the type is ObjectType (has schemaSpecKey)
46-
if (namePath.length > 1 && type[arrayTypeSchemaSpec][schemaSpecKey]) {
47-
return getFieldType(
48-
type[arrayTypeSchemaSpec][schemaSpecKey],
49-
namePath.slice(1).join('.'),
50-
true
51-
);
68+
if (namePath.length === 1) {
69+
return type;
70+
}
71+
72+
if (namePath.length > 1 && type[schemaSpecKey]) {
73+
return getFieldType(type[schemaSpecKey], namePath.slice(1).join('.'), true);
5274
}
53-
// Otherwise return the array element type directly
54-
return type[arrayTypeSchemaSpec];
5575
}
56-
return type;
5776
}
58-
59-
const joinedPath = namePath.join(`.${schemaSpecKey}.`);
60-
return get(schemaSpec, joinedPath);
77+
return schemaSpec?.[fieldName];
6178
}
62-
return schemaSpec?.[fieldName];
6379
}
6480

6581
/**

0 commit comments

Comments
 (0)