At the current version of Typescript 3.8.3, there isn't a definitive best practice for this particular scenario. There are three potential solutions that do not rely on external libraries. In all cases, it is necessary to store the strings in an object that is accessible at runtime (such as an array).
For the purpose of illustration, let's say we require a function to validate at runtime if a given string matches any of the predefined sheep names, which are commonly known as Capn Frisky
, Mr. Snugs
, and Lambchop
. Here are three approaches to accomplish this while ensuring compatibility with the Typescript compiler.
1: Type Assertion (Simpler Method)
Take off your helmet, manually verify the type, and apply an assertion.
const sheepNames = ['Capn Frisky', 'Mr. Snugs', 'Lambchop'] as const;
type SheepName = typeof sheepNames[number]; // "Capn Frisky" | "Mr. Snugs" | "Lambchop"
// The content of this string will be evaluated at runtime; TypeScript cannot determine its type validity.
const unsafeJson = '"Capn Frisky"';
/**
* Retrieve a valid SheepName from a JSON-encoded string or throw an error.
*/
function parseSheepName(jsonString: string): SheepName {
const maybeSheepName: unknown = JSON.parse(jsonString);
if (typeof maybeSheepName === 'string' && sheepNames.includes(maybeSheepName)) {
return (maybeSheepName as SheepName); // type assertion satisfies the compiler
}
throw new Error('The provided string does not match any of the sheep names.');
}
const definitelySheepName = parseSheepName(unsafeJson);
PRO: Simple and easy to comprehend.
CON: Fragile. You rely solely on your verification without TypeScript enforcing strict checks. Accidental removal of checks can lead to errors.
2: Custom Type Guards (Enhanced Reusability)
This method is a more elaborate, generalized version of the type assertion, but ultimately functions similarly.
const sheepNames = ['Capn Frisky', 'Mr. Snugs', 'Lambchop'] as const;
type SheepName = typeof sheepNames[number];
const unsafeJson = '"Capn Frisky"';
/**
* Implement a custom type guard to confirm whether an unknown object is a SheepName.
*/
function isSheepName(maybeSheepName: unknown): maybeSheepName is SheepName {
return typeof maybeSheepName === 'string' && sheepNames.includes(maybeSheepName);
}
/**
* Retrieve a valid SheepName from a JSON-encoded string or raise an exception.
*/
function parseSheepName(jsonString: string): SheepName {
const maybeSheepName: unknown = JSON.parse(jsonString);
if (isSheepName(maybeSheepName)) {
// The custom type guard confirms that this is a SheepName, satisfying TypeScript.
return (maybeSheepName as SheepName);
}
throw new Error('The input data does not correspond to a sheep name.');
}
const definitelySheepName = parseSheepName(unsafeJson);
PRO: Enhanced reusability, slightly less fragile, potentially more readable.
CON: TypeScript still relies on manual verification. Seems like a cumbersome process for a simple task.
3: Utilize Array.find (Safer and Recommended Approach)
This method eliminates the need for type assertions, beneficial for those who prefer additional validation.
const sheepNames = ['Capn Frisky', 'Mr. Snugs', 'Lambchop'] as const;
type SheepName = typeof sheepNames[number];
const unsafeJson = '"Capn Frisky"';
/**
* Retrieve a valid SheepName from a JSON-encoded string or raise an error.
*/
function parseSheepName(jsonString: string): SheepName {
const maybeSheepName: unknown = JSON.parse(jsonString);
const sheepName = sheepNames.find((validName) => validName === maybeSheepName);
if (sheepName) {
// `sheepName` originates from the list of `sheepNames`, providing assurance to the compiler.
return sheepName;
}
throw new Error('The input does not represent a sheep name.');
}
const definitelySheepName = parseSheepName(unsafeJson);
PRO: Bypasses type assertions, leaving validation to the compiler. This aspect holds significance for me, making this solution my preference.
CON: Appears somewhat unusual and may pose challenges in terms of performance optimization.
In conclusion, you have the flexibility to choose from these strategies or explore recommendations from third-party libraries. It's worth noting that utilizing an array might not be the most efficient approach, especially for large datasets. Consider optimizing by converting the sheepNames
array into a set for faster lookups (O(1)). Particularly useful when dealing with numerous potential sheep names or similar scenarios.