Validate numbers, objects, and undefinables in the json module

This commit adds a `number` validator`, an `object` validator, an `isNumber` predicate, and `undefinable()` to test optional-but-not-null properties.
This commit is contained in:
Mario Campos
2026-06-16 17:11:58 -05:00
parent 6010f85d81
commit 445107e23a
2 changed files with 57 additions and 1 deletions
+23
View File
@@ -44,3 +44,26 @@ test("validateSchema - optional properties are optional", async (t) => {
t.true(json.validateSchema(optionalSchema, { optionalKey: "" })); t.true(json.validateSchema(optionalSchema, { optionalKey: "" }));
t.true(json.validateSchema(optionalSchema, { optionalKey: "foo" })); t.true(json.validateSchema(optionalSchema, { optionalKey: "foo" }));
}); });
const undefinableSchema = {
optionalKey: json.undefinable(json.string),
};
test("validateSchema - undefinable properties are optional but reject null", async (t) => {
// Optional fields may be absent or explicitly undefined
t.true(json.validateSchema(undefinableSchema, {}));
t.true(json.validateSchema(undefinableSchema, { optionalKey: undefined }));
// But, unlike `optional`, `null` is rejected
t.false(json.validateSchema(undefinableSchema, { optionalKey: null }));
// And, if present, should have the expected type
t.false(json.validateSchema(undefinableSchema, { optionalKey: 0 }));
t.false(json.validateSchema(undefinableSchema, { optionalKey: 123 }));
t.false(json.validateSchema(undefinableSchema, { optionalKey: false }));
t.false(json.validateSchema(undefinableSchema, { optionalKey: true }));
t.false(json.validateSchema(undefinableSchema, { optionalKey: [] }));
t.false(json.validateSchema(undefinableSchema, { optionalKey: {} }));
t.true(json.validateSchema(undefinableSchema, { optionalKey: "" }));
t.true(json.validateSchema(undefinableSchema, { optionalKey: "foo" }));
});
+34 -1
View File
@@ -30,6 +30,11 @@ export function isString(value: unknown): value is string {
return typeof value === "string"; return typeof value === "string";
} }
/** Asserts that `value` is a number. */
export function isNumber(value: unknown): value is number {
return typeof value === "number";
}
/** Asserts that `value` is either a string or undefined. */ /** Asserts that `value` is either a string or undefined. */
export function isStringOrUndefined( export function isStringOrUndefined(
value: unknown, value: unknown,
@@ -55,7 +60,22 @@ export const string = {
required: true, required: true,
} as const satisfies Validator<string>; } as const satisfies Validator<string>;
/** Transforms a validator to be optional. */ /** A validator for number fields in schemas. */
export const number = {
validate: isNumber,
required: true,
} as const satisfies Validator<number>;
/** A validator for object fields in schemas. */
export const object = {
validate: isObject,
required: true,
} as const satisfies Validator<UnvalidatedObject<unknown>>;
/**
* Transforms a validator to be optional, accepting `undefined` or `null` for an
* absent value.
*/
export function optional<T>(validator: Validator<T>) { export function optional<T>(validator: Validator<T>) {
return { return {
validate: (val: unknown) => { validate: (val: unknown) => {
@@ -65,6 +85,19 @@ export function optional<T>(validator: Validator<T>) {
} as const satisfies Validator<T | undefined | null>; } as const satisfies Validator<T | undefined | null>;
} }
/**
* Transforms a validator to be optional, accepting `undefined` for an absent
* value but, unlike `optional`, rejecting `null`.
*/
export function undefinable<T>(validator: Validator<T>) {
return {
validate: (val: unknown): val is T | undefined => {
return val === undefined || validator.validate(val);
},
required: false,
} as const satisfies Validator<T | undefined>;
}
/** Represents an arbitrary object schema. */ /** Represents an arbitrary object schema. */
export type Schema = Record<string, Validator<any>>; export type Schema = Record<string, Validator<any>>;