Serialization Fundamentals
Understand the core concepts and common scenarios of enum serialization to build a foundation for deeper learning.
What is Enum Serialization?
Enum serialization is the process of converting enum types into storable and transmittable formats (such as strings, numbers, JSON). Deserialization is the reverse process of restoring these formats back to enum types.
// Serialization: Enum → String
OrderStatus.PENDING → "pending"
// Deserialization: String → Enum
"pending" → OrderStatus.PENDING
// JSON Serialization
{ "status": OrderStatus.PENDING }
→ { "status": "pending" }
// JSON Deserialization
{ "status": "pending" }
→ { "status": OrderStatus.PENDING }
Why Serialization is Needed
-
API Data Exchange
Pass enum values between frontend and backend via JSON
-
Database Storage
Save enums as strings or integers
-
Logging
Record enum states in readable format
-
Configuration Files
Use enums in YAML/JSON configurations
-
Cross-Language Communication
Data transfer between different technology stacks
Common Serialization Strategies
String Serialization
Recommended for APIs and configuration
"pending" "confirmed" "shipped"
Pros: Good readability, easy to debug, cross-language friendly
Integer Serialization
For performance-sensitive scenarios
0 // PENDING 1 // CONFIRMED 2 // SHIPPED
Pros: Space-efficient, good performance
Cons: Not intuitive, hard to debug
Object Serialization
Includes additional metadata
{
"code": "pending",
"label": "Pending",
"order": 0
}
Pros: Information-rich
Cons: Large size, complex
Multi-Language Implementation Comparison
Enum serialization and deserialization implementation methods across mainstream programming languages.
C# Enum Conversion
Enum → String
enum OrderStatus { Pending, Confirmed, Shipped }
// Method 1: ToString()
OrderStatus status = OrderStatus.Pending;
string str = status.ToString(); // "Pending"
// Method 2: Enum.GetName()
string str = Enum.GetName(typeof(OrderStatus), status);
// Method 3: Lowercase conversion
string str = status.ToString().ToLower(); // "pending"
// JSON Serialization (System.Text.Json)
using System.Text.Json;
using System.Text.Json.Serialization;
var options = new JsonSerializerOptions
{
Converters = { new JsonStringEnumConverter() }
};
string json = JsonSerializer.Serialize(status, options);
// "Pending"
// Newtonsoft.Json
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
[JsonConverter(typeof(StringEnumConverter))]
public OrderStatus Status { get; set; }
string json = JsonConvert.SerializeObject(order);
// { "status": "Pending" }
String → Enum
// Method 1: Enum.Parse()
string str = "Pending";
OrderStatus status = (OrderStatus)Enum.Parse(
typeof(OrderStatus), str);
// Method 2: Enum.TryParse() (Recommended)
if (Enum.TryParse(str, out var status))
{
// Parse successful
}
// Method 3: Ignore case
Enum.TryParse(
str, ignoreCase: true, out var status);
// JSON Deserialization
var order = JsonSerializer.Deserialize(
json, options);
// Handle invalid values
if (!Enum.IsDefined(typeof(OrderStatus), status))
{
throw new ArgumentException("Invalid status");
}
Python Enum Conversion
Enum → String
from enum import Enum
class OrderStatus(str, Enum):
PENDING = "pending"
CONFIRMED = "confirmed"
SHIPPED = "shipped"
status = OrderStatus.PENDING
# Method 1: .value attribute
str_value = status.value # "pending"
# Method 2: .name attribute (enum name)
name = status.name # "PENDING"
# Method 3: str() conversion
str_value = str(status.value)
# JSON Serialization (built-in json)
import json
data = {"status": status.value}
json_str = json.dumps(data)
# '{"status": "pending"}'
# Using custom JSONEncoder
class EnumEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, Enum):
return obj.value
return super().default(obj)
json_str = json.dumps(
{"status": status},
cls=EnumEncoder
)
# Pydantic automatic serialization
from pydantic import BaseModel
class Order(BaseModel):
status: OrderStatus
order = Order(status=OrderStatus.PENDING)
json_str = order.model_dump_json()
# '{"status":"pending"}'
String → Enum
# Method 1: Direct construction
str_value = "pending"
status = OrderStatus(str_value)
# Method 2: By name
status = OrderStatus["PENDING"]
# Method 3: Safe parsing
def parse_status(value: str) -> OrderStatus:
try:
return OrderStatus(value)
except ValueError:
raise ValueError(f"Invalid status: {value}")
# Method 4: With default value
def parse_status_safe(
value: str,
default: OrderStatus = OrderStatus.PENDING
) -> OrderStatus:
try:
return OrderStatus(value)
except ValueError:
return default
# JSON Deserialization
data = json.loads(json_str)
status = OrderStatus(data["status"])
# Pydantic automatic deserialization
order = Order.model_validate_json(json_str)
# order.status is OrderStatus.PENDING
# Validate validity
def is_valid_status(value: str) -> bool:
try:
OrderStatus(value)
return True
except ValueError:
return False
Java Enum Conversion
Enum → String
public enum OrderStatus {
PENDING("pending"),
CONFIRMED("confirmed"),
SHIPPED("shipped");
private final String value;
OrderStatus(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
OrderStatus status = OrderStatus.PENDING;
// Method 1: name() - enum name
String name = status.name(); // "PENDING"
// Method 2: getValue() - custom value
String value = status.getValue(); // "pending"
// Method 3: toString() (can be overridden)
String str = status.toString();
// JSON Serialization (Jackson)
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.annotation.JsonValue;
public enum OrderStatus {
PENDING("pending"),
CONFIRMED("confirmed");
@JsonValue // Use this method for serialization
public String getValue() {
return value;
}
}
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(status);
// "pending"
// Gson Serialization
import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName;
public enum OrderStatus {
@SerializedName("pending")
PENDING,
@SerializedName("confirmed")
CONFIRMED
}
String → Enum
// Method 1: valueOf() - by name
String name = "PENDING";
OrderStatus status = OrderStatus.valueOf(name);
// Method 2: Custom fromValue() method
public enum OrderStatus {
PENDING("pending"),
CONFIRMED("confirmed");
private final String value;
public static OrderStatus fromValue(String value) {
for (OrderStatus s : values()) {
if (s.value.equals(value)) {
return s;
}
}
throw new IllegalArgumentException(
"Unknown status: " + value
);
}
}
OrderStatus status = OrderStatus.fromValue("pending");
// Method 3: Using Stream API
public static Optional fromValue(
String value
) {
return Arrays.stream(values())
.filter(s -> s.value.equals(value))
.findFirst();
}
// JSON Deserialization (Jackson)
import com.fasterxml.jackson.annotation.JsonCreator;
public enum OrderStatus {
PENDING("pending");
@JsonCreator // Use this method for deserialization
public static OrderStatus fromValue(String value) {
return fromValue(value);
}
}
Order order = mapper.readValue(json, Order.class);
// Safe parsing
public static OrderStatus parse(
String value,
OrderStatus defaultValue
) {
try {
return fromValue(value);
} catch (IllegalArgumentException e) {
return defaultValue;
}
}
TypeScript Enum Conversion
Enum → String
// String enum (recommended)
enum OrderStatus {
Pending = "pending",
Confirmed = "confirmed",
Shipped = "shipped"
}
const status = OrderStatus.Pending;
// Method 1: Direct use (string enum)
const str: string = status; // "pending"
// Method 2: Explicit conversion
const str = status.toString();
// JSON Serialization (automatic)
const data = { status };
const json = JSON.stringify(data);
// '{"status":"pending"}'
// Numeric enum conversion
enum NumericStatus {
Pending, // 0
Confirmed, // 1
Shipped // 2
}
const status = NumericStatus.Pending;
// Get enum name
const name = NumericStatus[status]; // "Pending"
// Using mapping object
const statusMap = {
[NumericStatus.Pending]: "pending",
[NumericStatus.Confirmed]: "confirmed",
[NumericStatus.Shipped]: "shipped"
};
const str = statusMap[status]; // "pending"
// Using as const (type-safe strings)
const OrderStatus = {
Pending: "pending",
Confirmed: "confirmed",
Shipped: "shipped"
} as const;
type OrderStatus =
typeof OrderStatus[keyof typeof OrderStatus];
const status: OrderStatus = "pending";
String → Enum
// Method 1: Type assertion (string enum)
const str = "pending";
const status = str as OrderStatus;
// Method 2: Convert after validation
function parseStatus(value: string): OrderStatus {
if (Object.values(OrderStatus).includes(
value as OrderStatus
)) {
return value as OrderStatus;
}
throw new Error(`Invalid status: ${value}`);
}
// Method 3: Type guard
function isOrderStatus(
value: string
): value is OrderStatus {
return Object.values(OrderStatus).includes(
value as OrderStatus
);
}
if (isOrderStatus(str)) {
const status: OrderStatus = str;
}
// JSON Deserialization
interface Order {
status: OrderStatus;
}
const order: Order = JSON.parse(json);
// Validation required
// Using Zod validation
import { z } from "zod";
const OrderStatusSchema = z.enum([
"pending",
"confirmed",
"shipped"
]);
const OrderSchema = z.object({
status: OrderStatusSchema
});
const order = OrderSchema.parse(JSON.parse(json));
// Numeric enum deserialization
const numericValue = 0;
const status = numericValue as NumericStatus;
// Parse by name
const name = "Pending";
const status = OrderStatus[
name as keyof typeof OrderStatus
];
Rust Enum Conversion
Enum → String
use serde::{Serialize, Deserialize};
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum OrderStatus {
Pending,
Confirmed,
Shipped,
}
// Method 1: Display trait
use std::fmt;
impl fmt::Display for OrderStatus {
fn fmt(&self, f: &mut fmt::Formatter)
-> fmt::Result {
match self {
OrderStatus::Pending =>
write!(f, "pending"),
OrderStatus::Confirmed =>
write!(f, "confirmed"),
OrderStatus::Shipped =>
write!(f, "shipped"),
}
}
}
let status = OrderStatus::Pending;
let str = status.to_string(); // "pending"
// Method 2: Manual implementation
impl OrderStatus {
pub fn as_str(&self) -> &str {
match self {
OrderStatus::Pending => "pending",
OrderStatus::Confirmed => "confirmed",
OrderStatus::Shipped => "shipped",
}
}
}
let str = status.as_str();
// JSON Serialization (serde_json)
let json = serde_json::to_string(&status)?;
// "\"pending\""
let order = Order {
status: OrderStatus::Pending
};
let json = serde_json::to_string(&order)?;
// "{\"status\":\"pending\"}"
String → Enum
// Method 1: FromStr trait
use std::str::FromStr;
impl FromStr for OrderStatus {
type Err = String;
fn from_str(s: &str) -> Result {
match s {
"pending" => Ok(OrderStatus::Pending),
"confirmed" => Ok(OrderStatus::Confirmed),
"shipped" => Ok(OrderStatus::Shipped),
_ => Err(format!("Invalid status: {}", s)),
}
}
}
let str = "pending";
let status = OrderStatus::from_str(str)?;
// Or use parse()
let status: OrderStatus = str.parse()?;
// Method 2: Using strum
use strum_macros::{EnumString, Display};
#[derive(EnumString, Display)]
#[strum(serialize_all = "lowercase")]
pub enum OrderStatus {
Pending,
Confirmed,
Shipped,
}
let status: OrderStatus = "pending".parse()?;
// JSON Deserialization
let status: OrderStatus =
serde_json::from_str("\"pending\"")?;
let order: Order = serde_json::from_str(json)?;
// Custom deserialization
#[derive(Deserialize)]
struct Order {
#[serde(deserialize_with = "parse_status")]
status: OrderStatus,
}
fn parse_status<'de, D>(
deserializer: D
) -> Result
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
OrderStatus::from_str(&s)
.map_err(serde::de::Error::custom)
}
Go Enum Conversion
Enum → String
package main
type OrderStatus string
const (
OrderStatusPending OrderStatus = "pending"
OrderStatusConfirmed OrderStatus = "confirmed"
OrderStatusShipped OrderStatus = "shipped"
)
status := OrderStatusPending
// Method 1: Direct conversion (type alias)
str := string(status) // "pending"
// Method 2: String() method
func (s OrderStatus) String() string {
return string(s)
}
str := status.String()
// JSON Serialization (automatic)
import "encoding/json"
type Order struct {
Status OrderStatus `json:"status"`
}
order := Order{Status: OrderStatusPending}
json, _ := json.Marshal(order)
// {"status":"pending"}
// Using integer enum
type NumericStatus int
const (
Pending NumericStatus = iota
Confirmed
Shipped
)
// Implement MarshalJSON
func (s NumericStatus) MarshalJSON() (
[]byte, error
) {
statuses := []string{
"pending", "confirmed", "shipped"
}
if int(s) < len(statuses) {
return json.Marshal(statuses[s])
}
return nil, fmt.Errorf("invalid status")
}
String → Enum
// Method 1: Direct conversion
str := "pending"
status := OrderStatus(str)
// Method 2: Validation function
func ParseOrderStatus(s string) (
OrderStatus, error
) {
switch s {
case "pending":
return OrderStatusPending, nil
case "confirmed":
return OrderStatusConfirmed, nil
case "shipped":
return OrderStatusShipped, nil
default:
return "", fmt.Errorf(
"invalid status: %s", s
)
}
}
status, err := ParseOrderStatus(str)
// Method 3: Using map
var statusMap = map[string]OrderStatus{
"pending": OrderStatusPending,
"confirmed": OrderStatusConfirmed,
"shipped": OrderStatusShipped,
}
func ParseStatus(s string) (
OrderStatus, bool
) {
status, ok := statusMap[s]
return status, ok
}
// JSON Deserialization
var order Order
err := json.Unmarshal(jsonData, &order)
// Custom UnmarshalJSON
func (s *NumericStatus) UnmarshalJSON(
data []byte
) error {
var str string
if err := json.Unmarshal(data, &str);
err != nil {
return err
}
switch str {
case "pending":
*s = Pending
case "confirmed":
*s = Confirmed
case "shipped":
*s = Shipped
default:
return fmt.Errorf("invalid status")
}
return nil
}
API Integration & Specifications
Properly use enums in different architectures like REST API, GraphQL, gRPC, etc., to ensure consistency and type safety in frontend-backend data exchange.
API Enum Design Principles
Consistency
Frontend and backend use the same enum values, maintaining unified naming and format
Extensibility
Consider future enum additions during design, use strings instead of numbers
Documentation
Clearly list all possible values and their meanings in API documentation
OpenAPI / Swagger
Standard enum definition and documentation generation for RESTful APIs
Use Cases
- • RESTful API design and documentation
- • Frontend-backend contract definition
- • Automatic client SDK generation
- • API testing and mock data
OpenAPI 3.0 Enum Definition
openapi: 3.0.0
info:
title: Order API
version: 1.0.0
components:
schemas:
OrderStatus:
type: string
enum:
- pending
- confirmed
- shipped
- delivered
- cancelled
description: Order status
example: pending
Order:
type: object
properties:
id:
type: integer
status:
$ref: '#/components/schemas/OrderStatus'
items:
type: array
items:
type: object
required:
- id
- status
paths:
/orders:
post:
summary: Create order
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
example:
id: 123
status: pending
responses:
'201':
description: Order created successfully
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
REST API Implementation Examples
GET /api/orders?status=pending
{
"data": [
{
"id": 123,
"status": "pending",
"createdAt": "2024-01-01T00:00:00Z"
}
],
"meta": {
"total": 1
}
}
PATCH /api/orders/123
// Request
{
"status": "confirmed"
}
// Response (200 OK)
{
"id": 123,
"status": "confirmed",
"updatedAt": "2024-01-01T00:01:00Z"
}
// Error Response (400 Bad Request)
{
"error": "INVALID_ENUM_VALUE",
"message": "Invalid status value",
"allowedValues": [
"pending", "confirmed", "shipped"
]
}
Auto-generated Client Code
TypeScript (openapi-generator)
export enum OrderStatus {
Pending = 'pending',
Confirmed = 'confirmed',
Shipped = 'shipped'
}
export interface Order {
id: number;
status: OrderStatus;
}
// Type-safe API calls
const order = await api.updateOrder(123, {
status: OrderStatus.Confirmed
});
Python (openapi-generator)
class OrderStatus(str, Enum):
PENDING = "pending"
CONFIRMED = "confirmed"
SHIPPED = "shipped"
# Type hints and validation
def update_order(
order_id: int,
status: OrderStatus
) -> Order:
...
OpenAPI Best Practices
- ✓ Use
$refto reference enum definitions, avoid duplication - ✓ Add
descriptionandexampleto improve documentation readability - ✓ Provide default values
defaultfor enum values - ✓ When returning errors in responses, list all allowed enum values
- ✓ Use code generation tools to keep frontend and backend in sync
JSON Schema
Powerful JSON data validation and documentation tool
Use Cases
- • Frontend form validation
- • API request/response validation
- • Configuration file validation
- • Data import/export validation
Schema Definition
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Order",
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"status": {
"type": "string",
"enum": [
"pending",
"confirmed",
"shipped",
"delivered",
"cancelled"
],
"description": "Order status",
"default": "pending"
},
"priority": {
"type": "string",
"enum": ["low", "medium", "high"],
"enumNames": ["Low", "Medium", "High"]
}
},
"required": ["id", "status"],
"definitions": {
"OrderStatus": {
"type": "string",
"enum": [
"pending",
"confirmed",
"shipped"
]
}
}
}
Validation Examples
// JavaScript (Ajv)
import Ajv from "ajv";
const ajv = new Ajv();
const validate = ajv.compile(schema);
const data = {
id: 123,
status: "pending"
};
const valid = validate(data);
if (!valid) {
console.log(validate.errors);
}
// Python (jsonschema)
from jsonschema import validate, ValidationError
data = {
"id": 123,
"status": "pending"
}
try:
validate(instance=data, schema=schema)
except ValidationError as e:
print(f"Validation error: {e.message}")
// TypeScript (Zod)
import { z } from "zod";
const OrderStatusSchema = z.enum([
"pending",
"confirmed",
"shipped",
"delivered",
"cancelled"
]);
const OrderSchema = z.object({
id: z.number(),
status: OrderStatusSchema.default("pending")
});
type Order = z.infer;
const order = OrderSchema.parse(data);
JSON Schema Best Practices
- ✓ Use
enumNamesto provide friendly display names for enum values - ✓ Define reusable enum types in
definitions - ✓ Combine with UI Schema to generate form selection controls
- ✓ Use modern libraries like Zod for better TypeScript support
- ✓ Validate on both client and server side
GraphQL
Type-safe query language with built-in enum support
Use Cases
- • Modern API development
- • Precise data queries
- • Strongly typed schema definitions
- • Automatic documentation and type generation
Schema Definition
type Query {
orders(status: OrderStatus): [Order!]!
order(id: ID!): Order
}
type Mutation {
createOrder(input: CreateOrderInput!): Order!
updateOrderStatus(
id: ID!,
status: OrderStatus!
): Order!
}
enum OrderStatus {
PENDING
CONFIRMED
SHIPPED
DELIVERED
CANCELLED
}
type Order {
id: ID!
status: OrderStatus!
items: [OrderItem!]!
createdAt: DateTime!
}
input CreateOrderInput {
status: OrderStatus = PENDING
items: [OrderItemInput!]!
}
# Enum with descriptions
enum OrderStatus {
"""Pending"""
PENDING
"""Confirmed"""
CONFIRMED
"""Shipped"""
SHIPPED
"""Delivered"""
DELIVERED
"""Cancelled"""
CANCELLED
}
Resolver Implementation
// TypeScript (Apollo Server)
enum OrderStatus {
PENDING = "PENDING",
CONFIRMED = "CONFIRMED",
SHIPPED = "SHIPPED",
DELIVERED = "DELIVERED",
CANCELLED = "CANCELLED"
}
const resolvers = {
Query: {
orders: (
parent,
{ status },
context
) => {
return context.db.orders.findMany({
where: { status }
});
}
},
Mutation: {
updateOrderStatus: (
parent,
{ id, status },
context
) => {
return context.db.orders.update({
where: { id },
data: { status }
});
}
}
};
// Python (Graphene)
import graphene
class OrderStatus(graphene.Enum):
PENDING = "pending"
CONFIRMED = "confirmed"
SHIPPED = "shipped"
DELIVERED = "delivered"
CANCELLED = "cancelled"
class Order(graphene.ObjectType):
id = graphene.ID()
status = graphene.Field(OrderStatus)
class Query(graphene.ObjectType):
orders = graphene.List(
Order,
status=graphene.Argument(OrderStatus)
)
GraphQL Best Practices
- ✓ Use uppercase for enum values, consistent with GraphQL conventions
- ✓ Use triple quotes
"""to add descriptions - ✓ Provide default values in Input types
- ✓ Use code generation tools to sync TypeScript/Python types
- ✓ Validate enum value validity in Resolvers
gRPC / Protocol Buffers
High-performance RPC framework, suitable for microservice communication
Use Cases
- • Inter-service communication
- • High-performance RPC calls
- • Strongly typed interface definitions
- • Cross-language service integration
Proto Definition
syntax = "proto3";
package order;
enum OrderStatus {
// First value must be 0
ORDER_STATUS_UNSPECIFIED = 0;
ORDER_STATUS_PENDING = 1;
ORDER_STATUS_CONFIRMED = 2;
ORDER_STATUS_SHIPPED = 3;
ORDER_STATUS_DELIVERED = 4;
ORDER_STATUS_CANCELLED = 5;
}
message Order {
int64 id = 1;
OrderStatus status = 2;
repeated OrderItem items = 3;
}
message CreateOrderRequest {
OrderStatus status = 1;
repeated OrderItem items = 2;
}
message UpdateStatusRequest {
int64 id = 1;
OrderStatus status = 2;
}
service OrderService {
rpc CreateOrder(CreateOrderRequest)
returns (Order);
rpc UpdateStatus(UpdateStatusRequest)
returns (Order);
rpc ListOrders(ListOrdersRequest)
returns (ListOrdersResponse);
}
Usage Examples
// Go (protoc-gen-go)
import pb "path/to/generated/order"
// Create order
req := &pb.CreateOrderRequest{
Status: pb.OrderStatus_ORDER_STATUS_PENDING,
}
order, err := client.CreateOrder(ctx, req)
// Check status
if order.Status ==
pb.OrderStatus_ORDER_STATUS_SHIPPED {
// Handle shipped orders
}
// Python (grpcio)
import order_pb2
# Create order
request = order_pb2.CreateOrderRequest(
status=order_pb2.ORDER_STATUS_PENDING
)
order = stub.CreateOrder(request)
# Check status
if order.status == \
order_pb2.ORDER_STATUS_SHIPPED:
# Handle shipped orders
pass
// TypeScript (ts-proto)
import {
OrderStatus,
CreateOrderRequest
} from "./generated/order";
const request: CreateOrderRequest = {
status: OrderStatus.ORDER_STATUS_PENDING,
items: []
};
const order = await client.createOrder(request);
if (order.status ===
OrderStatus.ORDER_STATUS_SHIPPED) {
// Handle shipped orders
}
gRPC Best Practices
- ✓ First enum value must be 0, typically named
UNSPECIFIED - ✓ Use prefixes to avoid naming conflicts (e.g.,
ORDER_STATUS_) - ✓ Add descriptions in comments for team understanding
- ✓ Use
protocto generate code for multiple languages - ✓ Be cautious when deleting or renaming enum values, consider backward compatibility
API Comparison Matrix
| Feature | REST/OpenAPI | GraphQL | gRPC |
|---|---|---|---|
| Enum Format | String | String (uppercase) | Integer |
| Documentation | Excellent | Excellent | Moderate |
| Performance | Moderate | Moderate | Excellent |
| Type Safety | Tool-dependent | Built-in | Built-in |
| Browser Support | Native | Native | Requires proxy |
| Learning Curve | Low | Medium | Medium |
Selection advice: Use REST/OpenAPI for public APIs, GraphQL for frontend applications, gRPC for inter-service communication
Best Practices & Common Pitfalls
Avoid common mistakes and adopt proven serialization strategies.
Common Pitfalls
Pitfall 1: Case Inconsistency
// Frontend sends
{ "status": "Pending" }
// Backend expects
{ "status": "pending" }
// Result: Deserialization fails
Solution: Use lowercase consistently or specify case rules
Pitfall 2: Numeric Serialization Ambiguity
// Database stores: 1 represents CONFIRMED // New requirement inserts status between PENDING and CONFIRMED // Causes data inconsistency
Solution: Always use strings instead of numeric indices
Pitfall 3: Unvalidated Input
// Directly convert unknown string const status = input as OrderStatus; // May cause runtime errors
Solution: Always validate input is a valid enum value
Pitfall 4: Ignoring null/undefined
{ "status": null }
// Not handling null case
const status = OrderStatus(data["status"])
// Throws exception
Solution: Handle null/undefined, provide default values
Best Practices
Practice 1: Use String Enums
// TypeScript
enum Status {
Pending = "pending"
}
// Python
class Status(str, Enum):
PENDING = "pending"
Benefits: Readable, stable, easy to debug
Practice 2: Unified Naming Convention
// API uses snake_case "pending_payment" // Frontend uses camelCase PendingPayment // Backend uses UPPER_SNAKE_CASE PENDING_PAYMENT
Convert at serialization layer
Practice 3: Add Validation Layer
function parseStatus(
value: unknown
): OrderStatus {
if (!isValidStatus(value)) {
throw new ValidationError(
`Invalid status: ${value}`
);
}
return value as OrderStatus;
}
Always validate external input
Practice 4: Use Schema Validation
// OpenAPI, JSON Schema, Zod
// Auto-generate validation code
const OrderSchema = z.object({
status: z.enum([
"pending", "confirmed"
])
});
Leverage tools for automated validation
Serialization Strategy Decision Tree
Scenario 1: Public API
→ Use string enums
→ Provide OpenAPI documentation
→ Version APIs to avoid breaking changes
Reason: Readability, stability, documentation
Scenario 2: Internal Microservices
→ Use gRPC + Protocol Buffers
→ Numeric enums (performance priority)
→ Strict schema definitions
Reason: Performance, type safety, code generation
Scenario 3: Database Storage
→ String enums (PostgreSQL, MySQL)
→ Or use Lookup Table
→ Add migration scripts
Reason: Easy maintenance, easy querying, extensible
Scenario 4: Frontend State Management
→ TypeScript string enums
→ Or as const objects
→ Sync with backend API schema
Reason: Type safety, autocomplete, refactoring-friendly
Testing Checklist
Serialization Tests
- ✓ All enum values serialize correctly
- ✓ Serialized format conforms to API specification
- ✓ Case handling is correct
- ✓ Special character handling (e.g., underscores, hyphens)
- ✓ null/undefined handling
- ✓ Enums in nested objects
- ✓ Enums in arrays
Deserialization Tests
- ✓ Valid values parse correctly
- ✓ Invalid values throw errors or return defaults
- ✓ Case tolerance (if needed)
- ✓ Empty string handling
- ✓ null/undefined handling
- ✓ Clear error messages
- ✓ Backward compatibility (adding new values)
Tools & Resources
Use tools to validate serialization compatibility, generate code and documentation.
Enum Serializer Checker
Validate enum compatibility across different formats
Try it now →
Enum Name Linter
Check naming conventions, ensure serialization-friendly
Try it now →
Database Enum Guide
Database enum storage and ORM integration
View guide →
Recommended Resources
Code Generation Tools
- • OpenAPI Generator - Generate clients from OpenAPI
- • Swagger Codegen - Multi-language code generation
- • protoc - Protocol Buffers compiler
- • GraphQL Code Generator - GraphQL type generation
Validation Libraries
- • Zod (TypeScript) - Schema validation
- • Pydantic (Python) - Data validation
- • Joi (Node.js) - Object validation
- • Ajv (JavaScript) - JSON Schema validation
Summary: Enum Serialization Best Practices
Choose the Right Strategy
Use strings for public APIs, numbers for internal services, balance based on scenarios
Unified Naming Convention
Keep frontend and backend consistent, use tools for automatic sync, avoid case issues
Strict Input Validation
Always validate external input, use Schema tools, provide clear error messages
Remember: Readability first (use strings), Type safety (leverage tools), Backward compatibility (change cautiously). The right serialization strategy makes APIs more stable, debugging easier, and team collaboration smoother.