Question
How to Check if a Value Exists in an Enum-Like Object in TypeScript
Question
I receive a numeric value such as 3 and need to check whether it exists in this TypeScript constant object:
export const MESSAGE_TYPE = {
INFO: 1,
SUCCESS: 2,
WARNING: 3,
ERROR: 4,
};
One approach I found is to convert the object values into an array and then use indexOf, for example:
if (-1 < _.values(MESSAGE_TYPE).indexOf(_.toInteger(type))) {
// do stuff...
}
Is there a simpler and more readable way to check whether a value exists in this enum-like object?
Short Answer
By the end of this page, you will understand how to check whether a value exists inside an enum-like object in TypeScript. You will learn readable approaches using Object.values(), how this differs from checking keys, when to use a real enum, and how to avoid common mistakes with string and number comparisons.
Concept
In this question, the main concept is checking whether a value exists in a fixed set of allowed constants.
Your MESSAGE_TYPE example looks like an enum, but technically it is a plain object used as an enum-like constant map:
export const MESSAGE_TYPE = {
INFO: 1,
SUCCESS: 2,
WARNING: 3,
ERROR: 4,
};
This is common in TypeScript and JavaScript projects. Developers often use objects like this to define a small set of valid values.
The goal is simple:
- you receive some input such as
3 - you want to know whether that number is one of the allowed values
A readable way to do this is:
Object.values(MESSAGE_TYPE).includes(type)
If type might not already be a number, convert it first:
Object.values(MESSAGE_TYPE).includes(Number(type))
Why this matters in real programming:
- validating API input
- checking configuration values
- deciding whether to show a message, status, or alert
- preventing invalid data from reaching business logic
This is really a membership check: “Is this value one of the allowed values?”
In TypeScript, there are two related but different ideas:
- checking keys: does
"WARNING"exist? - checking values: does
3exist?
Those are not the same, and many beginners mix them up. In your case, you want to check the values.
Mental Model
Think of MESSAGE_TYPE like a small menu:
INFO→ 1SUCCESS→ 2WARNING→ 3ERROR→ 4
If someone gives you the number 3, you are not asking:
- “Is there a menu item named
3?”
You are asking:
- “Is
3one of the codes on the menu?”
So the job is to scan the list of allowed codes and see whether the number appears there.
A simple mental model:
- keys are the labels on the boxes
- values are the contents inside the boxes
Your question is about checking the contents, not the labels.
Syntax and Examples
The most readable modern approach is to use Object.values() with includes().
Basic syntax
Object.values(MESSAGE_TYPE).includes(value)
Example
export const MESSAGE_TYPE = {
INFO: 1,
SUCCESS: 2,
WARNING: 3,
ERROR: 4,
};
const type = 3;
if (Object.values(MESSAGE_TYPE).includes(type)) {
console.log("Valid message type");
}
Why this is clearer
Object.values(MESSAGE_TYPE)gives[1, 2, 3, 4].includes(type)checks whethertypeis in that array- the code reads almost like plain English
If the input might be a string
Sometimes values come from forms, URLs, or APIs as strings:
const typeFromApi = "3";
if (Object.values(MESSAGE_TYPE).includes(Number(typeFromApi))) {
console.log("Valid message type");
}
With a helper function
If you do this often, wrap it in a reusable function:
export const MESSAGE_TYPE = {
INFO: 1,
SUCCESS: 2,
WARNING: 3,
ERROR: 4,
} as const;
function isValidMessageType(value: unknown): boolean {
return Object.values(MESSAGE_TYPE).includes(Number(value) as typeof MESSAGE_TYPE[keyof typeof MESSAGE_TYPE]);
}
console.log(isValidMessageType(3)); // true
console.log(isValidMessageType(9)); // false
console.log(isValidMessageType("2")); // true
This makes the rest of your code easier to read:
if (isValidMessageType(type)) {
// do stuff...
}
Step by Step Execution
Consider this example:
const MESSAGE_TYPE = {
INFO: 1,
SUCCESS: 2,
WARNING: 3,
ERROR: 4,
} as const;
const type = 3;
const values = Object.values(MESSAGE_TYPE);
const exists = values.includes(type);
console.log(values);
console.log(exists);
Here is what happens step by step:
-
MESSAGE_TYPEis created as an object:INFOhas value1SUCCESShas value2WARNINGhas value3ERRORhas value4
-
typeis set to3. -
Object.values(MESSAGE_TYPE)creates an array of the object's values:
[1, 2, 3, 4]
-
values.includes(type)checks whether3is inside[1, 2, 3, 4]. -
Since
3is present,existsbecomestrue. -
The output is:
[1, 2, 3, 4]
true
Now try another case:
const type = 7;
const exists = Object.values(MESSAGE_TYPE).includes(type);
console.log(exists);
Step by step:
- values are still
[1, 2, 3, 4] includes(7)checks for77is not found- result is
false
Real World Use Cases
Checking whether a value exists in an enum-like object is very common.
API input validation
An API may receive a message type code:
const inputType = Number(req.body.type);
if (!Object.values(MESSAGE_TYPE).includes(inputType)) {
throw new Error("Invalid message type");
}
Form processing
A dropdown may send numeric values as strings:
const selectedType = Number(formData.type);
if (Object.values(MESSAGE_TYPE).includes(selectedType)) {
saveMessageType(selectedType);
}
Configuration checks
A config file may define a status level or logging level:
const level = Number(config.messageType);
if (!Object.values(MESSAGE_TYPE).includes(level)) {
console.warn("Unsupported message type in config");
}
Defensive programming
When data comes from outside your code, never assume it is valid. Membership checks help keep invalid values out of the rest of the application.
Real Codebase Usage
In real projects, developers usually do not inline this logic everywhere. They wrap it in validation helpers and use patterns that keep the code readable.
Common pattern: validation helper
const MESSAGE_TYPE = {
INFO: 1,
SUCCESS: 2,
WARNING: 3,
ERROR: 4,
} as const;
function isMessageType(value: unknown): value is typeof MESSAGE_TYPE[keyof typeof MESSAGE_TYPE] {
return Object.values(MESSAGE_TYPE).includes(Number(value) as typeof MESSAGE_TYPE[keyof typeof MESSAGE_TYPE]);
}
This keeps calling code clean:
if (!isMessageType(type)) {
return;
}
Guard clauses
A very common style is to reject invalid input early:
function handleMessage(type: unknown) {
if (!Object.values(MESSAGE_TYPE).includes(Number(type))) {
throw new Error("Invalid message type");
}
// safe to continue
}
Validation before branching
Developers often validate first, then switch or map based on the value:
function getLabel(type: number): string {
if (!Object.values(MESSAGE_TYPE).includes(type)) {
return "Unknown";
}
switch (type) {
case MESSAGE_TYPE.INFO:
return "Info";
case MESSAGE_TYPE.SUCCESS:
return "Success";
case MESSAGE_TYPE.WARNING:
return "Warning";
case MESSAGE_TYPE.ERROR:
return "Error";
default:
return "Unknown";
}
}
Precomputed sets for frequent checks
If checks happen many times, a Set can make intent even clearer:
const messageTypeValues = new Set(Object.values(MESSAGE_TYPE));
function isMessageType(value: number): boolean {
return messageTypeValues.has(value);
}
This is useful when membership checks are repeated often.
Common Mistakes
1. Checking keys instead of values
This checks property names like "INFO", not numeric values like 1 or 3:
const type = 3;
if (type in MESSAGE_TYPE) {
console.log("exists");
}
Why it is wrong:
inchecks whether a property name exists in the object- the keys are
INFO,SUCCESS,WARNING,ERROR 3is a value, not a key
2. Forgetting type conversion
const type = "3";
console.log(Object.values(MESSAGE_TYPE).includes(type));
This returns false because:
- array values are numbers
typeis a stringincludes()uses strict comparison
Fix:
console.log(Object.values(MESSAGE_TYPE).includes(Number(type)));
3. Using indexOf when includes is clearer
This works, but is less readable:
if (Object.values(MESSAGE_TYPE).indexOf(type) !== -1) {
// valid
}
Prefer:
if (Object.values(MESSAGE_TYPE).includes(type)) {
// valid
}
4. Assuming any number is valid because the type is number
TypeScript's number type is broad. It does not mean “one of the allowed values”.
let type: number = 999;
This is still a valid number, but not a valid message type. You still need runtime validation.
5. Not using as const when you want stricter typing
Without as const, TypeScript may widen the values to just number.
const MESSAGE_TYPE = {
INFO: 1,
SUCCESS: 2,
WARNING: 3,
ERROR: 4,
};
Better:
const MESSAGE_TYPE = {
INFO: 1,
SUCCESS: 2,
WARNING: 3,
ERROR: 4,
} as const;
This helps TypeScript preserve the exact literal values.
Comparisons
Here are the common ways to check membership and how they differ.
| Approach | What it checks | Readability | Notes |
|---|---|---|---|
Object.values(obj).includes(value) | Values | High | Best general readable option |
Object.values(obj).indexOf(value) !== -1 | Values | Medium | Older style, works but less clear |
value in obj | Keys | Low for this use case | Wrong if you need to check values |
Object.keys(obj).includes(key) | Keys | High | Useful only for key names |
set.has(value) | Values | High | Great when checking repeatedly |
Enum-like object vs real TypeScript enum
| Option | Example | Notes |
|---|---|---|
| Enum-like object | const MESSAGE_TYPE = { INFO: 1, ... } as const | Common in modern codebases, simple runtime object |
| TypeScript enum | enum MessageType { INFO = 1, ... } | Built-in enum syntax, but some teams avoid it |
Example with a real enum
enum MessageType {
INFO = 1,
SUCCESS = 2,
WARNING = 3,
ERROR = 4,
}
const type = 3;
const exists = Object.values(MessageType).includes(type);
Be careful: numeric enums in TypeScript generate reverse mappings at runtime, so Object.values() may include both names and numbers. That can be confusing for beginners. For many projects, an as const object is simpler and more predictable.
Cheat Sheet
Quick check for values
Object.values(MESSAGE_TYPE).includes(value)
If input may be a string
Object.values(MESSAGE_TYPE).includes(Number(value))
Recommended enum-like object
const MESSAGE_TYPE = {
INFO: 1,
SUCCESS: 2,
WARNING: 3,
ERROR: 4,
} as const;
Reusable helper
function isMessageType(value: unknown): boolean {
return Object.values(MESSAGE_TYPE).includes(Number(value));
}
Key vs value
"INFO" in MESSAGE_TYPE→ checks a keyObject.values(MESSAGE_TYPE).includes(1)→ checks a value
Prefer
includes()
over:
indexOf(...) !== -1
Common edge case
Object.values(MESSAGE_TYPE).includes("3") // false
Object.values(MESSAGE_TYPE).includes(Number("3")) // true
For repeated checks
const validTypes = new Set(Object.values(MESSAGE_TYPE));
validTypes.has(3);
FAQ
How do I check if a number exists in an enum-like object in TypeScript?
Use:
Object.values(MESSAGE_TYPE).includes(value)
This checks whether the value appears in the object's values.
Is in the right operator for this check?
No, not for values. in checks property names, not property values.
Should I use includes() or indexOf()?
Use includes() for readability. It directly expresses the idea of membership.
Why does checking "3" fail when 3 exists?
Because "3" is a string and 3 is a number. includes() uses strict equality.
Do I need Lodash for this?
Usually no. Native JavaScript methods like Object.values() and includes() are enough.
Should I use a real TypeScript enum instead?
You can, but many projects prefer as const objects because they are simpler at runtime and easier to reason about.
How can I make this reusable across my project?
Create a helper such as isMessageType(value) and call it wherever validation is needed.
Mini Project
Description
Build a small message type validator that accepts input from an external source and decides whether it is a valid message type. This demonstrates how to validate enum-like values before using them in application logic.
Goal
Create a reusable validator and label function for message type codes.
Requirements
Create an enum-like MESSAGE_TYPE object with four numeric values.
Write a function that checks whether an unknown input is a valid message type.
Convert string inputs like "3" to numbers before validation.
Write a function that returns a readable label for valid message types.
Test the code with valid and invalid inputs.
Keep learning
Related questions
@Directive vs @Component in Angular: Differences, Use Cases, and When to Use Each
Learn the difference between @Directive and @Component in Angular, including use cases, examples, and when to choose each.
Accessing Input Value from EventTarget in TypeScript
Learn why EventTarget has no value property in TypeScript and safely read values from HTML input events in Angular applications.
Angular (change) vs (ngModelChange): What’s the Difference?
Learn the difference between Angular (change) and (ngModelChange), when each fires, and which one to use in forms and inputs.