Elysia Tools

在一个清晰的工作台里浏览工具、案例、可视化和主题指南。

热门分类

专题

语言
ZHESPTRUFRDEEN

Elysia Tools

导航

校验库

Zod 模式验证

Zod TypeScript优先的模式验证示例,包括原语、对象、数组、转换、自定义验证器和错误处理

案例

这个案例集合中的条目

代码typescript简单

Zod 基础和原语类型

基础Zod模式验证,包括原始类型、基本验证和简单转换

难度

3/10

预计时间

25 min

标签

zod, validation, typescript, runtime

前置条件

TypeScript basics, JavaScript

typescript
// Zod Basics and Primitive Types Example

import { z } from 'zod';

// 1. Primitive Types
const stringSchema = z.string();
const numberSchema = z.number();
const booleanSchema = z.boolean();
const dateSchema = z.date();
const undefinedSchema = z.undefined();
const nullSchema = z.null();

// Test primitive validation
console.log(stringSchema.parse('hello')); // ✅ 'hello'
console.log(numberSchema.parse(42)); // ✅ 42
console.log(booleanSchema.parse(true)); // ✅ true
console.log(dateSchema.parse(new Date())); // ✅ Date object
console.log(undefinedSchema.parse(undefined)); // ✅ undefined
console.log(nullSchema.parse(null)); // ✅ null

// 2. Type Constraints
const minStringLength = z.string().min(5);
const maxStringLength = z.string().max(10);
const emailRegex = z.string().email();
const urlRegex = z.string().url();

console.log(minStringLength.parse('hello world')); // ✅
console.log(maxStringLength.parse('short')); // ✅
console.log(emailRegex.parse('[email protected]')); // ✅
console.log(urlRegex.parse('https://example.com')); // ✅

// 3. Number Constraints
const positiveNumber = z.number().positive();
const nonNegativeNumber = z.number().nonnegative();
const maxNumber = z.number().max(100);
const minNumber = z.number().min(18);
const intNumber = z.number().int();

console.log(positiveNumber.parse(10)); // ✅
console.log(nonNegativeNumber.parse(0)); // ✅
console.log(maxNumber.parse(50)); // ✅
console.log(minNumber.parse(25)); // ✅
console.log(intNumber.parse(42)); // ✅

// 4. Optional and Nullable Types
const optionalString = z.string().optional();
const nullableString = z.string().nullable();
const optionalOrNullableString = z.string().optional().nullable();

console.log(optionalString.parse('hello')); // ✅ 'hello'
console.log(optionalString.parse(undefined)); // ✅ undefined
console.log(nullableString.parse(null)); // ✅ null
console.log(nullableString.parse('hello')); // ✅ 'hello'

// 5. Default Values
const stringWithDefault = z.string().default('default value');
const optionalStringWithDefault = z.string().optional().default('optional default');

console.log(stringWithDefault.parse(undefined)); // ✅ 'default value'
console.log(optionalStringWithDefault.parse(undefined)); // ✅ 'optional default'

// 6. Enum-like Values
const directionSchema = z.enum(['north', 'south', 'east', 'west']);
const brandSchema = z.enum(['Nike', 'Adidas', 'Puma', 'Reebok']);

console.log(directionSchema.parse('north')); // ✅ 'north'
console.log(brandSchema.parse('Nike')); // ✅ 'Nike'

// 7. Literal Types
const trueSchema = z.literal(true);
const numberLiteral = z.literal(42);
const stringLiteral = z.literal('specific-value');

console.log(trueSchema.parse(true)); // ✅ true
console.log(numberLiteral.parse(42)); // ✅ 42
console.log(stringLiteral.parse('specific-value')); // ✅ 'specific-value'

// 8. Union Types
const stringOrNumber = z.union([z.string(), z.number()]);
const colorSchema = z.union([z.literal('red'), z.literal('green'), z.literal('blue')]);

console.log(stringOrNumber.parse('hello')); // ✅ 'hello'
console.log(stringOrNumber.parse(42)); // ✅ 42
console.log(colorSchema.parse('red')); // ✅ 'red'

// 9. Intersection Types
const personName = z.object({
  firstName: z.string(),
  lastName: z.string(),
});

const personAge = z.object({
  age: z.number().positive(),
});

const personWithDetails = z.intersection(personName, personAge);

console.log(personWithDetails.parse({
  firstName: 'John',
  lastName: 'Doe',
  age: 30
})); // ✅

// 10. Safe Parsing
const result = stringSchema.safeParse(123);
if (result.success) {
  console.log('Valid:', result.data);
} else {
  console.log('Invalid:', result.error);
}

// 11. Custom Validation
const customEmailSchema = z.string().email().refine((email) => {
  return email.endsWith('@company.com');
}, {
  message: 'Email must be from @company.com domain',
  path: ['domain']
});

try {
  customEmailSchema.parse('[email protected]'); // ✅
  customEmailSchema.parse('[email protected]'); // ❌
} catch (error) {
  console.error('Custom validation error:', error);
}

// 12. Transformations
const stringToNumber = z.string().transform((str) => parseInt(str, 10));
const uppercaseString = z.string().transform((str) => str.toUpperCase());
const trimmedString = z.string().transform((str) => str.trim());

const transformedNumber = stringToNumber.parse('123'); // ✅ 123 (number)
const transformedUpper = uppercaseString.parse('hello'); // ✅ 'HELLO'
const transformedTrim = trimmedString.parse('  hello  '); // ✅ 'hello'

// 13. Preprocessing and Postprocessing
const preprocessSchema = z.string().transform((str) => str.trim().toLowerCase());

const postprocessSchema = z.string().transform((str) => {
  return {
    original: str,
    length: str.length,
    uppercase: str.toUpperCase()
  };
});

const preprocessResult = preprocessSchema.parse('  HELLO  '); // ✅ 'hello'
const postprocessResult = postprocessSchema.parse('hello'); // ✅ { original: 'hello', length: 5, uppercase: 'HELLO' }

// 14. Pipeline Transformations
const dateToTimestamp = z.date().transform((date) => date.getTime());
const timestampToDate = z.number().transform((timestamp) => new Date(timestamp));

const timestamp = dateToTimestamp.parse(new Date()); // ✅ timestamp (number)
const date = timestampToDate.parse(Date.now()); // ✅ Date object

// 15. Error Customization
const customErrorSchema = z.string({
  errorMap: (issue, ctx) => {
    if (issue.code === z.ZodIssueCode.invalid_string) {
      return { message: 'Custom error message for invalid string' };
    }
    if (issue.code === z.ZodIssueCode.too_small) {
      return { message: 'String is too short!' };
    }
    return { message: ctx.defaultError };
  },
}).min(5);

try {
  customErrorSchema.parse('abc'); // ❌ Custom error
} catch (error) {
  console.log('Custom error message');
}

// 16. Advanced Primitive Examples

// Password validation with multiple requirements
const passwordSchema = z.string()
  .min(8, 'Password must be at least 8 characters')
  .max(100, 'Password must be less than 100 characters')
  .regex(/[A-Z]/, 'Password must contain at least one uppercase letter')
  .regex(/[a-z]/, 'Password must contain at least one lowercase letter')
  .regex(/[0-9]/, 'Password must contain at least one number')
  .regex(/[^A-Za-z0-9]/, 'Password must contain at least one special character');

// Username validation
const usernameSchema = z.string()
  .min(3, 'Username must be at least 3 characters')
  .max(20, 'Username must be less than 20 characters')
  .regex(/^[a-zA-Z0-9_]+$/, 'Username can only contain letters, numbers, and underscores')
  .refine((username) => !username.startsWith('_'), {
    message: 'Username cannot start with an underscore'
  })
  .refine((username) => !username.endsWith('_'), {
    message: 'Username cannot end with an underscore'
  });

// Phone number validation
const phoneNumberSchema = z.string()
  .regex(/^+?[1-9]d{1,14}$/, 'Invalid phone number format')
  .transform((phone) => {
    // Normalize phone number format
    return phone.replace(/[^0-9+]/g, '');
  });

// URL validation with protocols
const urlSchema = z.string().url().refine((url) => {
  const protocols = ['http:', 'https:', 'ftp:'];
  try {
    const parsedUrl = new URL(url);
    return protocols.includes(parsedUrl.protocol);
  } catch {
    return false;
  }
}, {
  message: 'URL must use http, https, or ftp protocol'
});

// Color validation
const colorHexSchema = z.string()
  .regex(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/, 'Invalid hex color format')
  .transform((hex) => hex.toLowerCase());

const colorRgbSchema = z.string()
  .regex(/^rgb(s*d+s*,s*d+s*,s*d+s*)$/i, 'Invalid RGB format')
  .transform((rgb) => {
    const matches = rgb.match(/d+/g);
    if (!matches || matches.length !== 3) return null;
    return {
      r: parseInt(matches[0]),
      g: parseInt(matches[1]),
      b: parseInt(matches[2])
    };
  });

// Test advanced validation
try {
  passwordSchema.parse('MySecureP@ssw0rd!'); // ✅
  usernameSchema.parse('john_doe123'); // ✅
  phoneNumberSchema.parse('+1-555-123-4567'); // ✅
  urlSchema.parse('https://example.com'); // ✅
  colorHexSchema.parse('#FF5733'); // ✅
  colorRgbSchema.parse('rgb(255, 87, 51)'); // ✅
} catch (error) {
  console.error('Advanced validation error:', error);
}

// 17. Usage Examples

// Form validation function
function validateUserInput(userData: unknown) {
  const userSchema = z.object({
    name: z.string().min(2).max(50),
    email: z.string().email(),
    age: z.number().min(18).max(120),
    website: z.string().url().optional(),
    bio: z.string().max(500).optional()
  });

  const result = userSchema.safeParse(userData);

  if (result.success) {
    return { success: true, data: result.data };
  } else {
    return {
      success: false,
      errors: result.error.issues.map(issue => ({
        field: issue.path.join('.'),
        message: issue.message
      }))
    };
  }
}

// API response validation
function validateApiResponse(response: unknown) {
  const responseSchema = z.object({
    success: z.boolean(),
    data: z.unknown().optional(),
    error: z.string().optional(),
    timestamp: z.string()
  });

  return responseSchema.parse(response);
}

// Environment variable validation
function validateEnvVars(envVars: Record<string, unknown>) {
  const envSchema = z.object({
    NODE_ENV: z.enum(['development', 'production', 'test']),
    PORT: z.string().transform(Number).pipe(z.number().positive()),
    DATABASE_URL: z.string().url(),
    JWT_SECRET: z.string().min(32),
    API_KEY: z.string().min(16),
    ENABLE_LOGGING: z.string().transform(Boolean).pipe(z.boolean()).default(true),
    MAX_CONNECTIONS: z.string().transform(Number).pipe(z.number().positive()).default(10)
  });

  return envSchema.parse(envVars);
}

// Example usage
console.log('\n--- Usage Examples ---');

const userInput = {
  name: 'John Doe',
  email: '[email protected]',
  age: 25,
  website: 'https://johndoe.com',
  bio: 'Software developer'
};

const userValidation = validateUserInput(userInput);
console.log('User validation:', userValidation);

const apiResponse = {
  success: true,
  data: { message: 'Hello World' },
  timestamp: new Date().toISOString()
};

const validatedResponse = validateApiResponse(apiResponse);
console.log('API response validation:', validatedResponse);

const envVars = {
  NODE_ENV: 'development',
  PORT: '3000',
  DATABASE_URL: 'https://localhost:5432/mydb',
  JWT_SECRET: 'my-super-secret-jwt-key-32-chars-long',
  API_KEY: 'my-api-key-16-chars'
};

const validatedEnv = validateEnvVars(envVars);
console.log('Environment validation:', validatedEnv);

export {
  stringSchema,
  numberSchema,
  booleanSchema,
  dateSchema,
  userSchema: z.object({
    name: z.string().min(2).max(50),
    email: z.string().email(),
    age: z.number().min(18).max(120),
    website: z.string().url().optional(),
    bio: z.string().max(500).optional()
  }),
  passwordSchema,
  usernameSchema,
  validateUserInput,
  validateApiResponse,
  validateEnvVars
};
代码typescript中等

Zod 对象和数组

复杂的Zod模式,用于对象、数组、嵌套结构和高级验证模式

难度

6/10

预计时间

40 min

标签

zod, validation, typescript, schemas, objects

前置条件

Zod basics, TypeScript, Advanced data structures

typescript
// Zod Objects and Arrays Example

import { z } from 'zod';

// 1. Basic Object Schema
const userSchema = z.object({
  id: z.string().uuid(),
  name: z.string().min(2).max(50),
  email: z.string().email(),
  age: z.number().min(13).max(120),
  isActive: z.boolean().default(true),
  createdAt: z.date().default(new Date()),
});

type User = z.infer<typeof userSchema>;

const validUser = {
  id: '550e8400-e29b-41d4-a716-446655440000',
  name: 'John Doe',
  email: '[email protected]',
  age: 30,
  isActive: true,
  createdAt: new Date()
};

console.log(userSchema.parse(validUser)); // ✅

// 2. Partial Object (for updates)
const updateUserSchema = userSchema.partial();

const updateData = {
  name: 'Jane Doe',
  age: 28
};

console.log(updateUserSchema.parse(updateData)); // ✅

// 3. Required Object (no optional/extra properties)
const strictUserSchema = z.object({
  name: z.string(),
  email: z.string().email()
}).strict();

console.log(strictUserSchema.parse({ name: 'John', email: '[email protected]' })); // ✅
// strictUserSchema.parse({ name: 'John', email: '[email protected]', extra: 'value' }); // ❌

// 4. Object with Passthrough (allow extra properties)
const passthroughUserSchema = z.object({
  name: z.string(),
  email: z.string().email()
}).passthrough();

console.log(passthroughUserSchema.parse({
  name: 'John',
  email: '[email protected]',
  role: 'admin',
  department: 'IT'
})); // ✅, preserves extra properties

// 5. Object with Catchall (custom handling for extra properties)
const catchallUserSchema = z.object({
  name: z.string(),
  email: z.string().email()
}).catchall(z.string());

console.log(catchallUserSchema.parse({
  name: 'John',
  email: '[email protected]',
  role: 'admin', // ✅, string value
  score: 100 // ❌, not a string
}));

// 6. Array Schemas
const stringArraySchema = z.array(z.string());
const numberArraySchema = z.array(z.number());
const userArraySchema = z.array(userSchema);

console.log(stringArraySchema.parse(['hello', 'world'])); // ✅
console.log(numberArraySchema.parse([1, 2, 3])); // ✅
console.log(userArraySchema.parse([validUser, validUser])); // ✅

// 7. Array Constraints
const minLengthArray = z.array(z.string()).min(2);
const maxLengthArray = z.array(z.number()).max(5);
const exactLengthArray = z.array(z.string()).length(3);

console.log(minLengthArray.parse(['a', 'b', 'c'])); // ✅
console.log(maxLengthArray.parse([1, 2, 3])); // ✅
console.log(exactLengthArray.parse(['x', 'y', 'z'])); // ✅

// 8. Non-empty Array
const nonEmptyArray = z.array(z.string()).nonempty();
console.log(nonEmptyArray.parse(['hello'])); // ✅
// nonEmptyArray.parse([]); // ❌

// 9. Set-like Array (unique values)
const uniqueArray = z.array(z.string()).refine(
  (arr) => new Set(arr).size === arr.length,
  { message: 'Array must contain unique values' }
);

console.log(uniqueArray.parse(['a', 'b', 'c'])); // ✅
// uniqueArray.parse(['a', 'b', 'a']); // ❌

// 10. Nested Objects
const addressSchema = z.object({
  street: z.string(),
  city: z.string(),
  state: z.string().length(2),
  zipCode: z.string().regex(/^d{5}(-d{4})?$/),
  country: z.string().default('USA'),
  coordinates: z.object({
    lat: z.number().min(-90).max(90),
    lng: z.number().min(-180).max(180)
  }).optional()
});

const userWithAddressSchema = z.object({
  id: z.string().uuid(),
  name: z.string(),
  email: z.string().email(),
  address: addressSchema,
  phoneNumbers: z.array(z.string().regex(/^+?[1-9]d{1,14}$/)).optional(),
  metadata: z.record(z.unknown()).optional()
});

const userWithAddress = {
  id: '550e8400-e29b-41d4-a716-446655440000',
  name: 'John Doe',
  email: '[email protected]',
  address: {
    street: '123 Main St',
    city: 'New York',
    state: 'NY',
    zipCode: '10001',
    coordinates: { lat: 40.7128, lng: -74.0060 }
  },
  phoneNumbers: ['+1-555-123-4567'],
  metadata: {
    lastLogin: new Date().toISOString(),
    preferences: { theme: 'dark' }
  }
};

console.log(userWithAddressSchema.parse(userWithAddress)); // ✅

// 11. Array of Objects
const postSchema = z.object({
  id: z.string().uuid(),
  title: z.string().min(1).max(200),
  content: z.string().max(10000),
  authorId: z.string().uuid(),
  tags: z.array(z.string().min(1).max(20)).max(5),
  published: z.boolean().default(false),
  createdAt: z.date(),
  updatedAt: z.date().optional()
});

const postsSchema = z.array(postSchema);

const posts = [
  {
    id: '550e8400-e29b-41d4-a716-446655440001',
    title: 'Hello World',
    content: 'This is my first post',
    authorId: '550e8400-e29b-41d4-a716-446655440000',
    tags: ['intro', 'hello'],
    published: true,
    createdAt: new Date()
  },
  {
    id: '550e8400-e29b-41d4-a716-446655440002',
    title: 'Second Post',
    content: 'Another interesting post',
    authorId: '550e8400-e29b-41d4-a716-446655440000',
    tags: ['updates', 'announcement'],
    published: false,
    createdAt: new Date()
  }
];

console.log(postsSchema.parse(posts)); // ✅

// 12. Discriminated Unions
const baseEventSchema = z.object({
  id: z.string().uuid(),
  timestamp: z.date(),
  userId: z.string().uuid()
});

const userLoginEventSchema = baseEventSchema.extend({
  type: z.literal('user.login'),
  ipAddress: z.string().ip(),
  userAgent: z.string()
});

const userLogoutEventSchema = baseEventSchema.extend({
  type: z.literal('user.logout'),
  sessionDuration: z.number().positive()
});

const postCreatedEventSchema = baseEventSchema.extend({
  type: z.literal('post.created'),
  postId: z.string().uuid(),
  postTitle: z.string()
});

const eventSchema = z.discriminatedUnion('type', [
  userLoginEventSchema,
  userLogoutEventSchema,
  postCreatedEventSchema
]);

type Event = z.infer<typeof eventSchema>;

// Valid events
const loginEvent = {
  id: '550e8400-e29b-41d4-a716-446655440003',
  timestamp: new Date(),
  userId: '550e8400-e29b-41d4-a716-446655440000',
  type: 'user.login' as const,
  ipAddress: '192.168.1.1',
  userAgent: 'Mozilla/5.0...'
};

const logoutEvent = {
  id: '550e8400-e29b-41d4-a716-446655440004',
  timestamp: new Date(),
  userId: '550e8400-e29b-41d4-a716-446655440000',
  type: 'user.logout' as const,
  sessionDuration: 3600
};

const postEvent = {
  id: '550e8400-e29b-41d4-a716-446655440005',
  timestamp: new Date(),
  userId: '550e8400-e29b-41d4-a716-446655440000',
  type: 'post.created' as const,
  postId: '550e8400-e29b-41d4-a716-446655440001',
  postTitle: 'Hello World'
};

console.log(eventSchema.parse(loginEvent)); // ✅
console.log(eventSchema.parse(logoutEvent)); // ✅
console.log(eventSchema.parse(postEvent)); // ✅

// 13. Record Schema (key-value object)
const configSchema = z.record(z.string(), z.unknown());

const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000,
  retries: 3,
  features: {
    darkMode: true,
    notifications: false
  }
};

console.log(configSchema.parse(config)); // ✅

// More specific record schema
const themeConfigSchema = z.record(z.enum(['primary', 'secondary', 'success', 'danger', 'warning', 'info']), z.string());

const themeConfig = {
  header: 'primary',
  sidebar: 'secondary',
  buttons: 'success',
  alerts: 'danger',
  notifications: 'warning',
  links: 'info'
};

console.log(themeConfigSchema.parse(themeConfig)); // ✅

// 14. Tuple Schema
const coordinateSchema = z.tuple([z.number(), z.number()]);
const rgbColorSchema = z.tuple([z.number().min(0).max(255), z.number().min(0).max(255), z.number().min(0).max(255)]);

console.log(coordinateSchema.parse([10, 20])); // ✅
console.log(rgbColorSchema.parse([255, 128, 0])); // ✅

// 15. Optional Array Elements
const profileSchema = z.object({
  username: z.string(),
  email: z.string().email(),
  socialLinks: z.array(z.object({
    platform: z.string(),
    url: z.string().url()
  })).optional(),
  skills: z.array(z.string()).optional()
});

const profile = {
  username: 'johndoe',
  email: '[email protected]',
  socialLinks: [
    { platform: 'twitter', url: 'https://twitter.com/johndoe' },
    { platform: 'github', url: 'https://github.com/johndoe' }
  ],
  skills: ['TypeScript', 'React', 'Node.js']
};

console.log(profileSchema.parse(profile)); // ✅

// 16. Recursive Schema (self-referencing)
const categorySchema = z.object({
  id: z.string(),
  name: z.string(),
  description: z.string().optional(),
  parentCategory: z.string().optional()
});

const categoryWithChildren = categorySchema.extend({
  subcategories: z.lazy(() => z.array(categoryWithChildren)).optional()
});

// Valid category with nested children
const categoryTree = {
  id: 'cat1',
  name: 'Technology',
  description: 'All things tech',
  subcategories: [
    {
      id: 'cat2',
      name: 'Programming',
      subcategories: [
        {
          id: 'cat3',
          name: 'JavaScript',
          parentCategory: 'cat2'
        },
        {
          id: 'cat4',
          name: 'Python',
          parentCategory: 'cat2'
        }
      ]
    },
    {
      id: 'cat5',
      name: 'Hardware',
      parentCategory: 'cat1'
    }
  ]
};

console.log(categoryWithChildren.parse(categoryTree)); // ✅

// 17. Complex Validation Examples

// Product schema with inventory management
const productSchema = z.object({
  id: z.string().uuid(),
  name: z.string().min(1).max(100),
  description: z.string().max(1000),
  price: z.number().positive(),
  categories: z.array(z.string().uuid()).min(1),
  variants: z.array(z.object({
    id: z.string().uuid(),
    name: z.string(),
    sku: z.string(),
    price: z.number().positive().optional(),
    inventory: z.object({
      quantity: z.number().min(0),
      lowStockThreshold: z.number().min(0),
      trackInventory: z.boolean()
    }),
    attributes: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])),
    images: z.array(z.object({
      url: z.string().url(),
      alt: z.string().optional(),
      isPrimary: z.boolean().default(false)
    }))
  })).min(1),
  tags: z.array(z.string()).max(10).optional(),
  metadata: z.object({
    weight: z.number().positive().optional(),
    dimensions: z.object({
      length: z.number().positive(),
      width: z.number().positive(),
      height: z.number().positive(),
      unit: z.enum(['cm', 'in', 'mm']).default('cm')
    }).optional(),
    seo: z.object({
      title: z.string().max(60).optional(),
      description: z.string().max(160).optional(),
      keywords: z.array(z.string()).max(10).optional()
    }).optional()
  }).optional()
});

// Order schema with line items
const orderItemSchema = z.object({
  productId: z.string().uuid(),
  variantId: z.string().uuid(),
  quantity: z.number().positive().max(100),
  price: z.number().positive(),
  discounts: z.array(z.object({
    type: z.enum(['percentage', 'fixed']),
    value: z.number().positive(),
    reason: z.string().optional()
  })).optional()
});

const orderSchema = z.object({
  id: z.string().uuid(),
  customerId: z.string().uuid(),
  items: z.array(orderItemSchema).min(1),
  status: z.enum(['pending', 'confirmed', 'processing', 'shipped', 'delivered', 'cancelled']),
  shippingAddress: addressSchema,
  billingAddress: addressSchema,
  paymentMethod: z.object({
    type: z.enum(['credit_card', 'debit_card', 'paypal', 'stripe']),
    lastFour: z.string().length(4).regex(/^d+$/),
    brand: z.string().optional()
  }),
  subtotal: z.number().positive(),
  tax: z.number().nonnegative(),
  shipping: z.number().nonnegative(),
  total: z.number().positive(),
  createdAt: z.date(),
  shippedAt: z.date().optional(),
  deliveredAt: z.date().optional()
});

// Validation functions for complex scenarios
function validateProductInventory(product: unknown, requestedQuantity: number) {
  const parsedProduct = productSchema.parse(product);

  for (const variant of parsedProduct.variants) {
    if (variant.inventory.quantity < requestedQuantity) {
      throw new Error(`Insufficient inventory for variant ${variant.name}. Available: ${variant.inventory.quantity}, Requested: ${requestedQuantity}`);
    }

    if (variant.inventory.trackInventory && variant.inventory.quantity <= variant.inventory.lowStockThreshold) {
      console.warn(`Low stock warning for variant ${variant.name}. Current stock: ${variant.inventory.quantity}`);
    }
  }

  return parsedProduct;
}

function validateOrderTotal(order: unknown) {
  const parsedOrder = orderSchema.parse(order);

  const calculatedSubtotal = parsedOrder.items.reduce((sum, item) => {
    const itemTotal = item.price * item.quantity;
    const discountTotal = (item.discounts || []).reduce((discountSum, discount) => {
      if (discount.type === 'percentage') {
        return discountSum + (itemTotal * discount.value / 100);
      } else {
        return discountSum + discount.value;
      }
    }, 0);
    return sum + (itemTotal - discountTotal);
  }, 0);

  if (Math.abs(calculatedSubtotal - parsedOrder.subtotal) > 0.01) {
    throw new Error(`Subtotal mismatch. Calculated: ${calculatedSubtotal}, Provided: ${parsedOrder.subtotal}`);
  }

  const calculatedTotal = calculatedSubtotal + parsedOrder.tax + parsedOrder.shipping;
  if (Math.abs(calculatedTotal - parsedOrder.total) > 0.01) {
    throw new Error(`Total mismatch. Calculated: ${calculatedTotal}, Provided: ${parsedOrder.total}`);
  }

  return parsedOrder;
}

// Test complex validation
try {
  const testProduct = {
    id: '550e8400-e29b-41d4-a716-446655440006',
    name: 'Wireless Headphones',
    description: 'High-quality wireless headphones with noise cancellation',
    price: 99.99,
    categories: ['cat1', 'cat2'],
    variants: [
      {
        id: 'var1',
        name: 'Black',
        sku: 'WH-BLK-001',
        inventory: {
          quantity: 50,
          lowStockThreshold: 10,
          trackInventory: true
        },
        attributes: { color: 'black', wireless: true },
        images: [
          { url: 'https://example.com/headphones-black.jpg', alt: 'Black wireless headphones', isPrimary: true }
        ]
      }
    ]
  };

  validateProductInventory(testProduct, 5); // ✅

  const testOrder = {
    id: 'order1',
    customerId: 'customer1',
    items: [
      {
        productId: 'product1',
        variantId: 'var1',
        quantity: 2,
        price: 99.99
      }
    ],
    status: 'pending' as const,
    shippingAddress: {
      street: '123 Main St',
      city: 'New York',
      state: 'NY',
      zipCode: '10001'
    },
    billingAddress: {
      street: '123 Main St',
      city: 'New York',
      state: 'NY',
      zipCode: '10001'
    },
    paymentMethod: {
      type: 'credit_card' as const,
      lastFour: '1234',
      brand: 'Visa'
    },
    subtotal: 199.98,
    tax: 16.00,
    shipping: 5.00,
    total: 220.98,
    createdAt: new Date()
  };

  validateOrderTotal(testOrder); // ✅
} catch (error) {
  console.error('Complex validation error:', error);
}

export {
  userSchema,
  addressSchema,
  userWithAddressSchema,
  postSchema,
  postsSchema,
  eventSchema,
  productSchema,
  orderItemSchema,
  orderSchema,
  validateProductInventory,
  validateOrderTotal,
  type User,
  type Event
};
代码typescript复杂

Zod 高级模式

高级Zod模式,包括自定义验证器、错误处理、模式组合、中间件和实际用例

难度

9/10

预计时间

60 min

标签

zod, validation, patterns, typescript, advanced

前置条件

Advanced Zod, TypeScript, Design patterns, Async/await

typescript
// Zod Advanced Patterns and Real-World Examples

import { z, ZodIssue, ZodSchema } from 'zod';

// 1. Custom Validation with Detailed Error Messages

// Enhanced string validation with multiple custom rules
const enhancedStringSchema = z.string()
  .min(3, 'String must be at least 3 characters long')
  .max(100, 'String must be at most 100 characters long')
  .regex(/^[a-zA-Z0-9\s-_]+$/, 'String can only contain letters, numbers, spaces, hyphens, and underscores')
  .refine((val) => val.trim() === val, {
    message: 'String cannot have leading or trailing whitespace'
  })
  .refine((val) => {
    const wordCount = val.trim().split(/\s+/).length;
    return wordCount >= 2;
  }, {
    message: 'String must contain at least 2 words'
  })
  .transform((val) => val.trim());

// Custom ID validator with format checking
const idSchema = z.string()
  .regex(/^[a-zA-Z0-9_-]+$/, 'ID can only contain letters, numbers, hyphens, and underscores')
  .min(3, 'ID must be at least 3 characters long')
  .max(50, 'ID must be at most 50 characters long')
  .refine((val) => /^[a-zA-Z]/.test(val), {
    message: 'ID must start with a letter'
  })
  .refine((val) => !/[-_]{2,}/.test(val), {
    message: 'ID cannot contain consecutive hyphens or underscores'
  });

// 2. Schema Composition and Reuse

// Base field schemas for reuse
const baseFieldSchemas = {
  id: idSchema,
  name: z.string().min(1).max(100),
  description: z.string().max(500).optional(),
  createdAt: z.date(),
  updatedAt: z.date().optional(),
  isActive: z.boolean().default(true)
};

// Timestamp schema with validation
const timestampSchema = z.date()
  .refine((date) => date <= new Date(), {
    message: 'Date cannot be in the future'
  })
  .refine((date) => date >= new Date('2000-01-01'), {
    message: 'Date must be after year 2000'
  });

// 3. Advanced Error Handling

// Custom error formatting
const formatZodError = (error: z.ZodError) => {
  return {
    success: false,
    error: {
      code: error.issues[0]?.code || 'VALIDATION_ERROR',
      message: error.issues[0]?.message || 'Validation failed',
      field: error.issues[0]?.path?.join('.') || 'unknown',
      details: error.issues.map(issue => ({
        field: issue.path?.join('.') || 'unknown',
        code: issue.code,
        message: issue.message,
        received: issue.received,
        expected: issue.expected
      }))
    }
  };
};

// Custom error class for validation
class ValidationError extends Error {
  constructor(
    message: string,
    public readonly code: string,
    public readonly field?: string,
    public readonly details?: ZodIssue[]
  ) {
    super(message);
    this.name = 'ValidationError';
  }
}

// Enhanced safe parse with custom error handling
function safeParseWithCustomError<T>(schema: ZodSchema<T>, data: unknown): T {
  const result = schema.safeParse(data);

  if (!result.success) {
    const firstError = result.error.issues[0];
    throw new ValidationError(
      firstError.message,
      firstError.code,
      firstError.path?.join('.'),
      result.error.issues
    );
  }

  return result.data;
}

// 4. Middleware Pattern for Validation

// Validation middleware type
type ValidationMiddleware<T> = (data: unknown) => T;

// Create validation middleware
function createValidationMiddleware<T>(schema: ZodSchema<T>): ValidationMiddleware<T> {
  return (data: unknown): T => {
    return safeParseWithCustomError(schema, data);
  };
}

// Higher-order function for validation
function withValidation<T>(
  schema: ZodSchema<T>,
  fn: (validatedData: T) => unknown
) {
  const validate = createValidationMiddleware(schema);

  return (data: unknown) => {
    const validatedData = validate(data);
    return fn(validatedData);
  };
}

// 5. Async Validation Patterns

// Async validator for checking uniqueness
async function createUniqueEmailValidator(existingEmails: Set<string>) {
  return async (email: string) => {
    // Simulate database check
    await new Promise(resolve => setTimeout(resolve, 100));

    if (existingEmails.has(email.toLowerCase())) {
      throw new Error('Email already exists');
    }

    return true;
  };
}

// Async schema with custom validation
async function createUserSchema(existingEmails: Set<string> = new Set()) {
  const uniqueEmailValidator = await createUniqueEmailValidator(existingEmails);

  return z.object({
    email: z.string().email().refine(uniqueEmailValidator, {
      message: 'Email must be unique'
    }),
    username: z.string().min(3).max(20).regex(/^[a-zA-Z0-9_]+$/),
    password: z.string()
      .min(8)
      .regex(/[A-Z]/, 'Password must contain uppercase')
      .regex(/[a-z]/, 'Password must contain lowercase')
      .regex(/[0-9]/, 'Password must contain number')
      .regex(/[^A-Za-z0-9]/, 'Password must contain special character')
  });
}

// 6. Conditional Validation

// Conditional object schema based on user type
function createUserProfileSchema(isAdmin: boolean) {
  const baseProfile = z.object({
    firstName: z.string().min(1),
    lastName: z.string().min(1),
    email: z.string().email(),
    bio: z.string().max(500).optional()
  });

  if (isAdmin) {
    return baseProfile.extend({
      adminLevel: z.enum(['junior', 'senior', 'lead']),
      permissions: z.array(z.string()).min(1),
      department: z.string().min(1)
    });
  }

  return baseProfile.extend({
    preferences: z.object({
      theme: z.enum(['light', 'dark']),
      notifications: z.boolean(),
      language: z.string().length(2)
    })
  });
}

// 7. Schema Refactoring with Composition

// Address schema
const addressSchema = z.object({
  street: z.string().min(1),
  city: z.string().min(1),
  state: z.string().length(2),
  zipCode: z.string().regex(/^\d{5}(-\d{4})?$/),
  country: z.string().default('USA'),
  addressType: z.enum(['shipping', 'billing', 'both'])
});

// Contact schema
const contactSchema = z.object({
  phone: z.string().regex(/^\+?[1-9]\d{1,14}$/).optional(),
  email: z.string().email(),
  website: z.string().url().optional(),
  socialMedia: z.record(z.string().url()).optional()
});

// User schema composed from smaller schemas
const createUserSchema = () => z.object({
  ...baseFieldSchemas,
  contact: contactSchema,
  addresses: z.array(addressSchema).min(1),
  role: z.enum(['user', 'admin', 'moderator']).default('user'),
  profile: z.object({
    avatar: z.string().url().optional(),
    timezone: z.string(),
    locale: z.string().default('en-US')
  })
});

// 8. Real-World API Request/Response Validation

// API Request schemas
const PaginationSchema = z.object({
  page: z.coerce.number().positive().default(1),
  limit: z.coerce.number().positive().max(100).default(20),
  sortBy: z.enum(['createdAt', 'updatedAt', 'name']).default('createdAt'),
  sortOrder: z.enum(['asc', 'desc']).default('desc')
});

const FilterSchema = z.object({
  status: z.enum(['active', 'inactive', 'all']).optional(),
  category: z.string().optional(),
  dateFrom: z.string().datetime().optional(),
  dateTo: z.string().datetime().optional(),
  search: z.string().optional()
});

// API Request wrapper
const ApiRequestSchema = z.object({
  pagination: PaginationSchema,
  filters: FilterSchema,
  include: z.array(z.string()).optional()
});

// API Response schemas
const ApiSuccessSchema = <T>(dataSchema: ZodSchema<T>) => z.object({
  success: z.literal(true),
  data: dataSchema,
  pagination: z.object({
    currentPage: z.number(),
    totalPages: z.number(),
    totalItems: z.number(),
    hasNext: z.boolean(),
    hasPrev: z.boolean()
  }).optional(),
  meta: z.record(z.unknown()).optional()
});

const ApiErrorSchema = z.object({
  success: z.literal(false),
  error: z.object({
    code: z.string(),
    message: z.string(),
    details: z.array(z.object({
      field: z.string(),
      code: z.string(),
      message: z.string()
    })).optional()
  }),
  meta: z.record(z.unknown()).optional()
});

// Generic API response schema
const ApiResponseSchema = <T>(dataSchema: ZodSchema<T>) =>
  z.union([ApiSuccessSchema(dataSchema), ApiErrorSchema]);

// 9. Database Model Validation

// UUID validator with fallback
const uuidSchema = z.string().uuid().or(z.string().length(36)).transform((val) => {
  // Handle both UUID and string representation
  return val.length === 36 ? val : val;
});

// Entity status validator
const EntityStatusSchema = z.enum(['active', 'inactive', 'archived', 'pending']).default('active');

// Soft delete mixin
const withSoftDelete = <T extends z.ZodRawShape>(
  schema: T
) => z.object(schema).extend({
  deletedAt: z.date().nullable().default(null),
  deletedBy: uuidSchema.nullable().default(null)
});

// Audit fields mixin
const withAuditFields = <T extends z.ZodRawShape>(
  schema: T
) => z.object(schema).extend({
  createdAt: z.date().default(() => new Date()),
  updatedAt: z.date().default(() => new Date()),
  createdBy: uuidSchema,
  updatedBy: uuidSchema.optional()
});

// 10. Environment Configuration Validation

// Environment type discriminator
const EnvironmentSchema = z.enum(['development', 'staging', 'production', 'test']);

// Database configuration
const DatabaseConfigSchema = z.object({
  url: z.string().url(),
  ssl: z.boolean().default(true),
  maxConnections: z.number().positive().default(10),
  connectionTimeout: z.number().positive().default(30000),
  idleTimeout: z.number().positive().default(300000)
});

// Server configuration
const ServerConfigSchema = z.object({
  port: z.number().positive().default(3000),
  host: z.string().default('localhost'),
  cors: z.object({
    origin: z.union([z.string(), z.array(z.string())]).default('*'),
    credentials: z.boolean().default(false),
    methods: z.array(z.enum(['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'])).default(['GET', 'POST'])
  }).default({}),
  rateLimit: z.object({
    windowMs: z.number().positive().default(900000), // 15 minutes
    maxRequests: z.number().positive().default(100)
  }).optional()
});

// Complete environment configuration
const EnvironmentConfigSchema = z.object({
  nodeEnv: EnvironmentSchema,
  server: ServerConfigSchema,
  database: DatabaseConfigSchema,
  auth: z.object({
    jwtSecret: z.string().min(32),
    jwtExpiresIn: z.string().default('24h'),
    bcryptRounds: z.number().positive().min(10).max(12).default(12)
  }),
  redis: z.object({
    url: z.string().url().optional(),
    host: z.string().default('localhost'),
    port: z.number().positive().default(6379),
    password: z.string().optional(),
    db: z.number().default(0)
  }).optional(),
  features: z.object({
    enableMetrics: z.boolean().default(false),
    enableLogging: z.boolean().default(true),
    enableCache: z.boolean().default(true),
    enableRateLimit: z.boolean().default(true)
  }).default({})
});

// 11. Form Validation Patterns

// Multi-step form schema
const step1Schema = z.object({
  firstName: z.string().min(1),
  lastName: z.string().min(1),
  email: z.string().email(),
  phone: z.string().regex(/^\+?[1-9]\d{1,14}$/)
});

const step2Schema = z.object({
  address: addressSchema,
  preferences: z.object({
    newsletter: z.boolean().default(false),
    marketing: z.boolean().default(false)
  })
});

const step3Schema = z.object({
  paymentMethod: z.enum(['credit_card', 'paypal', 'bank_transfer']),
  terms: z.boolean().refine(val => val === true, {
    message: 'You must accept the terms and conditions'
  })
});

// Multi-step form validator
function validateMultiStepForm(step: number, data: unknown) {
  switch (step) {
    case 1:
      return step1Schema.parse(data);
    case 2:
      return step2Schema.parse(data);
    case 3:
      return step3Schema.parse(data);
    default:
      throw new Error('Invalid step number');
  }
}

// 12. Real-World Usage Examples

// User registration service
class UserService {
  private existingEmails = new Set<string>();
  private existingUsernames = new Set<string>();

  async registerUser(userData: unknown) {
    const userSchema = await this.createUserRegistrationSchema();
    const validatedUser = safeParseWithCustomError(userSchema, userData);

    // Additional business logic
    await this.checkDuplicateEmail(validatedUser.email);
    await this.checkDuplicateUsername(validatedUser.username);

    const user = await this.createUserInDatabase(validatedUser);

    // Update tracking sets
    this.existingEmails.add(user.email.toLowerCase());
    this.existingUsernames.add(user.username.toLowerCase());

    return user;
  }

  private async createUserRegistrationSchema() {
    const uniqueEmailValidator = await this.createUniqueEmailValidator();

    return z.object({
      username: z.string()
        .min(3)
        .max(20)
        .regex(/^[a-zA-Z0-9_]+$/)
        .refine(async (username) => {
          await new Promise(resolve => setTimeout(resolve, 50)); // Simulate async check
          return !this.existingUsernames.has(username.toLowerCase());
        }, {
          message: 'Username already exists'
        }),
      email: z.string().email(),
      password: z.string().min(8),
      confirmPassword: z.string()
    }).refine((data) => data.password === data.confirmPassword, {
      message: 'Passwords do not match',
      path: ['confirmPassword']
    });
  }

  private async createUniqueEmailValidator() {
    return async (email: string) => {
      await new Promise(resolve => setTimeout(resolve, 50));
      return !this.existingEmails.has(email.toLowerCase());
    };
  }

  private async checkDuplicateEmail(email: string) {
    if (this.existingEmails.has(email.toLowerCase())) {
      throw new ValidationError('Email already exists', 'DUPLICATE_EMAIL', 'email');
    }
  }

  private async checkDuplicateUsername(username: string) {
    if (this.existingUsernames.has(username.toLowerCase())) {
      throw new ValidationError('Username already exists', 'DUPLICATE_USERNAME', 'username');
    }
  }

  private async createUserInDatabase(userData: any) {
    // Simulate database insertion
    await new Promise(resolve => setTimeout(resolve, 100));

    return {
      id: Math.random().toString(36).substr(2, 9),
      ...userData,
      createdAt: new Date(),
      updatedAt: new Date()
    };
  }
}

// API request/response handler
class ApiController {
  handleRequest(request: unknown, responseSchema: ZodSchema<any>) {
    try {
      const validatedRequest = safeParseWithCustomError(
        ApiRequestSchema,
        request
      );

      // Process request...
      const data = await this.processRequest(validatedRequest);

      const validatedResponse = ApiSuccessSchema(responseSchema).parse({
        success: true,
        data
      });

      return validatedResponse;

    } catch (error) {
      if (error instanceof ValidationError) {
        return ApiErrorSchema.parse({
          success: false,
          error: {
            code: error.code,
            message: error.message,
            details: error.details?.map(issue => ({
              field: issue.field,
              code: issue.code,
              message: issue.message
            }))
          }
        });
      }

      // Handle other unexpected errors
      return ApiErrorSchema.parse({
        success: false,
        error: {
          code: 'INTERNAL_ERROR',
          message: 'An unexpected error occurred'
        }
      });
    }
  }

  private async processRequest(request: any) {
    // Simulate async processing
    await new Promise(resolve => setTimeout(resolve, 50));
    return { message: 'Request processed successfully' };
  }
}

// 13. Testing with Zod

// Test utilities
function expectValidationSuccess<T>(schema: ZodSchema<T>, data: unknown): T {
  const result = schema.safeParse(data);
  if (!result.success) {
    throw new Error(`Expected validation to succeed, but it failed: ${result.error.message}`);
  }
  return result.data;
}

function expectValidationFailure<T>(schema: ZodSchema<T>, data: unknown, expectedError?: string): void {
  const result = schema.safeParse(data);
  if (result.success) {
    throw new Error('Expected validation to fail, but it succeeded');
  }
  if (expectedError && !result.error.message.includes(expectedError)) {
    throw new Error(`Expected error message to contain "${expectedError}", but got: ${result.error.message}`);
  }
}

// Test examples
function runTests() {
  console.log('Running Zod validation tests...');

  // Test success cases
  const validUser = expectValidationSuccess(userSchema, {
    id: '550e8400-e29b-41d4-a716-446655440000',
    name: 'John Doe',
    email: '[email protected]',
    age: 30
  });
  console.log('✅ Valid user test passed');

  // Test failure cases
  expectValidationFailure(
    userSchema,
    { id: 'invalid-uuid', name: '', email: 'invalid-email', age: -5 },
    'Expected string'
  );
  console.log('✅ Invalid user test passed');

  console.log('All tests completed successfully!');
}

// Execute tests
runTests();

// 14. Schema Documentation Generator

function generateSchemaDocumentation(schema: ZodSchema<any>, name: string): string {
  const description = schema._def.description || name;
  let docs = `# ${name}\n\n${description}\n\n## Schema Definition\n\n`;

  if (schema._def.typeName === 'ZodObject') {
    docs += '| Property | Type | Required | Description |\n';
    docs += '|---------|------|----------|-------------|\n';

    for (const [key, def] of Object.entries(schema._def.shape())) {
      const type = def._def.typeName;
      const required = !def._def.defaultValue;
      const description = def._def.description || '';

      docs += `| ${key} | ${type} | ${required ? 'Yes' : 'No'} | ${description} |\n`;
    }
  }

  return docs;
}

export {
  enhancedStringSchema,
  idSchema,
  formatZodError,
  ValidationError,
  safeParseWithCustomError,
  createValidationMiddleware,
  withValidation,
  ApiRequestSchema,
  ApiResponseSchema,
  EnvironmentConfigSchema,
  validateMultiStepForm,
  UserService,
  ApiController,
  expectValidationSuccess,
  expectValidationFailure,
  generateSchemaDocumentation
};

工具

经常与该案例搭配使用的工具

相关内容

相关案例集合

工具使用指南

帮助搜索与理解工作流的案例说明

标题

Zod 模式验证

描述

Zod TypeScript优先的模式验证示例,包括原语、对象、数组、转换、自定义验证器和错误处理

概览

Zod TypeScript优先的模式验证示例,包括原语、对象、数组、转换、自定义验证器和错误处理

适用场景

  • Use this sample set when you want reference inputs for Zod 模式验证.
  • Use it to preview output formats and compare related sample variants.

使用方式

  1. 1Open a sample entry to inspect the prepared example payload or asset.
  2. 2Compare it with related tools to understand how the workflow connects.

常见用途

  • Zod 基础和原语类型
  • Zod 对象和数组
  • Zod 高级模式

常见问题

What is included in Zod 模式验证?

This page includes curated sample entries, related tools, and neighboring sample collections for Zod 模式验证.

首页
工具3197
案例593
可视化461
文件查看器103
专题229
标签333
图像、音频与视频655

图像、音频、视频处理、转换与优化工具

数学、日期与金融644

计算器、数值计算、日期逻辑、统计与金融工具

设计与颜色296

颜色、版式、图形、视觉样式与设计辅助工具

文本与写作209

写作、清洗、格式化、提取与文本分析工具

开发与 Web193

开发者工具、网络处理、Web 调试与自动化辅助工具

转换与编码175

格式、文件、压缩包、单位与编码转换工具

界面截图结构化、人工校对与开发交付
钢琴旋律草稿创作、MIDI 核验与试听样稿交付
复古游戏纹理制作、候选评审与素材交付
操作截图到可复核图文指南与培训交付
网页改版响应式视觉回归审核与证据交付

© 2026 Elysia Tools. 保留所有权利。

文档价格关于我们服务条款隐私政策Cookie 政策联系我们

Development

JSON Schema 转 Zod Schema 转换器

将标准 JSON Schema 的 JSON/YAML 定义转换为可直接在 TypeScript 项目中使用的 Zod 运行时校验代码,支持嵌套结构、数组、枚举和常见校验规则

Validation

JSON模式校验器

验证JSON数据是否符合JSON Schema结构定义

AI Tools

MCP Server 契约测试器(stdio + WebSocket 双传输 · JSON Schema 2020-12 工具 schema 校验)

对 Model Context Protocol 服务器做端到端契约验证:JSON-RPC 2.0 握手与版本协商、capabilities 交换、tools/list 输入 schema 的 object 根类型与 2020-12 草案编译校验、resources 读写、prompts 获取、ping、未知方法 -32601 语义、sampling/createMessage 往返与 stdio 换行分帧纪律;默认内置参考服务器离线自测,亦可对你的 stdio 命令或 ws:// 端点实测。

Development

JSON Schema 生成器

从示例 JSON 自动推断 JSON Schema,支持手动调整并验证数据

Format Conversion

XLSX Sheet 转 JSON Schema

根据 Excel 表头与样例行推断 JSON Schema(类型/枚举/约束)

Development

数据库 Schema 对比

比较两个数据库 Schema 并生成迁移草案

Development

表结构生成器

生成SQL表结构定义

AI Tools

LLM 工具调用 JSON Schema 构建器

一次定义 LLM 工具,同时生成 OpenAI、Anthropic、Gemini 三家经过校验的函数调用 payload。

Rust

Web Rust 错误处理示例

Web Rust 错误处理示例,包括异常捕获、日志记录和参数验证

TypeScript

Web TypeScript 错误处理示例

Web TypeScript 错误处理示例,包括异常捕获、日志记录和参数验证