guardz
Version:
A simple and lightweight TypeScript type guard library for runtime type validation.
82 lines (81 loc) • 3.54 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.isIndexSignature = isIndexSignature;
const generateTypeGuardError_1 = require("./generateTypeGuardError");
/**
* Creates a type guard that validates objects with index signatures.
*
* This function validates that an object has properties with keys of a specific type
* and values of a specific type. It's useful for validating objects with dynamic keys
* like `{ [key: string]: T }` or `{ [key: number]: T }`.
*
* Note: For number keys, this validates that the string keys can be converted to numbers.
* JavaScript object keys are always strings, so `{ 1: true }` becomes `{ "1": true }`.
*
* @template K - The type of the keys (string or number)
* @template V - The type of the values
* @param keyGuard - Type guard function for validating keys
* @param valueGuard - Type guard function for validating values
* @returns A type guard function that validates index signature objects
*
* @example
* ```typescript
* import { isIndexSignature, isString, isNumber, isBoolean } from 'guardz';
*
* // String-keyed object with number values
* const isStringNumberMap = isIndexSignature(isString, isNumber);
*
* // Number-keyed object with boolean values
* const isNumberBooleanMap = isIndexSignature(isNumber, isBoolean);
*
* console.log(isStringNumberMap({ a: 1, b: 2, c: 3 })); // true
* console.log(isStringNumberMap({ "key1": 10, "key2": 20 })); // true
* console.log(isStringNumberMap({ a: 1, b: "2" })); // false (mixed value types)
* console.log(isStringNumberMap({ 1: "a", 2: "b" })); // false (number keys)
*
* console.log(isNumberBooleanMap({ 1: true, 2: false })); // true
* console.log(isNumberBooleanMap({ "1": true })); // true (string keys that are numbers)
* console.log(isNumberBooleanMap({ "abc": true })); // false (non-numeric string keys)
*
* // With type narrowing
* const data: unknown = getDataFromAPI();
* if (isStringNumberMap(data)) {
* // data is now typed as { [key: string]: number }
* Object.entries(data).forEach(([key, value]) => {
* console.log(`${key}: ${value * 2}`); // Safe to use as number
* });
* }
* ```
*/
function isIndexSignature(keyGuard, valueGuard) {
return function (value, config) {
// Check if it's a plain object
const isPlainObject = typeof value === 'object' &&
value !== null &&
!Array.isArray(value) &&
value.constructor === Object;
if (!isPlainObject) {
if (config) {
config.callbackOnError((0, generateTypeGuardError_1.generateTypeGuardError)(value, config.identifier, 'Object'));
}
return false;
}
// Cast to Record<string | number, unknown> for iteration
const obj = value;
// Get all property names (string keys) and symbols
const stringKeys = Object.getOwnPropertyNames(obj);
const symbolKeys = Object.getOwnPropertySymbols(obj);
const allKeys = [...stringKeys, ...symbolKeys];
// Check all entries
return allKeys.every((key, index) => {
const val = obj[key];
const keyValid = keyGuard(key, config
? { ...config, identifier: `${config.identifier}[key:${index}]` }
: null);
const valueValid = valueGuard(val, config
? { ...config, identifier: `${config.identifier}[value:${index}]` }
: null);
return keyValid && valueValid;
});
};
}