# Go vs TypeScript + class-transformer 对比 ## 命名约定和自动转换实现 本文档展示 Go 和 TypeScript 如何实现类似的自动类型转换机制。 --- ## 📋 命名约定总结 ### OpenAPI 规范 | 元素 | 命名约定 | 示例 | |------|---------|------| | Fixed Fields | `camelCase` | `operationId`, `requestBody` | | Schema 名称 | `PascalCase` | `ClusterResponse`, `CreateClusterRequest` | | Schema 属性 | `snake_case` | `has_ca_data`, `created_at`, `cluster_id` | ### Backend Go | 元素 | 命名约定 | 示例 | |------|---------|------| | 导出变量/字段 | `PascalCase` | `HasCAData`, `CreatedAt` | | 非导出变量 | `camelCase` | `hasCAData`, `createdAt` | | 类型名 | `PascalCase` | `ClusterResponse`, `CreateClusterRequest` | | JSON 标签 | `snake_case` | `json:"has_ca_data"`, `json:"created_at"` | ### Frontend TypeScript | 元素 | 命名约定 | 示例 | |------|---------|------| | 变量 | `camelCase` | `hasCAData`, `createdAt` | | 类型名 | `PascalCase` | `Cluster`, `CreateClusterRequest` | | JSON | `snake_case` | `has_ca_data`, `created_at` | --- ## 🔄 自动转换对比 ### Go 的实现 ```go // backend/internal/adapter/input/http/dto/cluster_dto.go package dto // 类型定义:PascalCase type ClusterResponse struct { // 导出字段:PascalCase ID string `json:"id"` Name string `json:"name"` Host string `json:"host"` // 字段名:PascalCase, JSON:snake_case HasCAData bool `json:"has_ca_data"` // ← struct tag HasCertData bool `json:"has_cert_data"` // ← struct tag HasKeyData bool `json:"has_key_data"` // ← struct tag CAData string `json:"ca_data"` // ← struct tag CertData string `json:"cert_data"` // ← struct tag KeyData string `json:"key_data"` // ← struct tag CreatedAt string `json:"created_at"` // ← struct tag UpdatedAt string `json:"updated_at"` // ← struct tag } // 使用 - Go 自动转换 func GetCluster() ClusterResponse { cluster := ClusterResponse{ ID: "cluster-123", Name: "Production", HasCAData: true, // 内部使用 PascalCase CAData: "••••••••", CreatedAt: "2025-11-10", } // json.Marshal 自动转换为 snake_case // {"id":"cluster-123","has_ca_data":true,"ca_data":"••••••••","created_at":"2025-11-10"} return cluster } ``` ### TypeScript + class-transformer 的实现 ```typescript // frontend/src/api/models/cluster.model.ts import { Expose } from 'class-transformer'; // 类型定义:PascalCase export class Cluster { @Expose() id!: string; @Expose() name!: string; @Expose() host!: string; // 字段名:camelCase, JSON:snake_case @Expose({ name: 'has_ca_data' }) // ← @Expose decorator (类似 struct tag) hasCAData?: boolean; @Expose({ name: 'has_cert_data' }) // ← @Expose decorator hasCertData?: boolean; @Expose({ name: 'has_key_data' }) // ← @Expose decorator hasKeyData?: boolean; @Expose({ name: 'ca_data' }) // ← @Expose decorator caData?: string; @Expose({ name: 'cert_data' }) // ← @Expose decorator certData?: string; @Expose({ name: 'key_data' }) // ← @Expose decorator keyData?: string; @Expose({ name: 'created_at' }) // ← @Expose decorator createdAt!: string; @Expose({ name: 'updated_at' }) // ← @Expose decorator updatedAt!: string; } // 使用 - TypeScript + class-transformer 自动转换 import { fromJson, toJson } from '@/api/serializer'; function getCluster(): Cluster { // JSON → 类实例 (snake_case → camelCase) const apiResponse = { id: "cluster-123", name: "Production", has_ca_data: true, // JSON: snake_case ca_data: "••••••••", created_at: "2025-11-10", }; const cluster = fromJson(Cluster, apiResponse); // 内部使用 camelCase console.log(cluster.hasCAData); // true console.log(cluster.caData); // "••••••••" console.log(cluster.createdAt); // "2025-11-10" return cluster; } ``` --- ## 🔍 详细对比 ### 1. 结构定义 #### Go ```go type ClusterResponse struct { HasCAData bool `json:"has_ca_data"` CreatedAt string `json:"created_at"` } ``` #### TypeScript + class-transformer ```typescript class Cluster { @Expose({ name: 'has_ca_data' }) hasCAData?: boolean; @Expose({ name: 'created_at' }) createdAt!: string; } ``` **对应关系**: - Go 的 `struct tag` ↔ TypeScript 的 `@Expose` 装饰器 - Go 的 `json:"field_name"` ↔ TypeScript 的 `{ name: 'field_name' }` --- ### 2. JSON 序列化(结构体/类 → JSON) #### Go ```go cluster := ClusterResponse{ HasCAData: true, // PascalCase CreatedAt: "2025-11-10", } jsonBytes, _ := json.Marshal(cluster) // 自动转换为: {"has_ca_data":true,"created_at":"2025-11-10"} ``` #### TypeScript + class-transformer ```typescript const cluster = new Cluster(); cluster.hasCAData = true; // camelCase cluster.createdAt = "2025-11-10"; const json = toJson(cluster); // 自动转换为: {has_ca_data: true, created_at: "2025-11-10"} ``` **对应关系**: - Go 的 `json.Marshal()` ↔ TypeScript 的 `toJson()` - 都实现了:内部字段名 → JSON snake_case --- ### 3. JSON 反序列化(JSON → 结构体/类) #### Go ```go jsonStr := `{"has_ca_data":true,"created_at":"2025-11-10"}` var cluster ClusterResponse json.Unmarshal([]byte(jsonStr), &cluster) // 自动映射到 PascalCase 字段 fmt.Println(cluster.HasCAData) // true fmt.Println(cluster.CreatedAt) // "2025-11-10" ``` #### TypeScript + class-transformer ```typescript const apiResponse = { has_ca_data: true, created_at: "2025-11-10" }; const cluster = fromJson(Cluster, apiResponse); // 自动映射到 camelCase 字段 console.log(cluster.hasCAData); // true console.log(cluster.createdAt); // "2025-11-10" ``` **对应关系**: - Go 的 `json.Unmarshal()` ↔ TypeScript 的 `fromJson()` - 都实现了:JSON snake_case → 内部字段名 --- ## 📊 完整数据流转示例 ### Scenario: 创建集群 ``` ┌─────────────────────────────────────────────────────────────────┐ │ 1. Frontend 组件 (TypeScript camelCase) │ ├─────────────────────────────────────────────────────────────────┤ │ const request = new CreateClusterRequest(); │ │ request.name = "Production"; │ │ request.caData = "LS0t..."; // camelCase │ │ request.certData = "LS0t..."; │ └─────────────────────────────────────────────────────────────────┘ ↓ toJson(request) ┌─────────────────────────────────────────────────────────────────┐ │ 2. HTTP Request Body (JSON snake_case) │ ├─────────────────────────────────────────────────────────────────┤ │ { │ │ "name": "Production", │ │ "ca_data": "LS0t...", // snake_case │ │ "cert_data": "LS0t..." │ │ } │ └─────────────────────────────────────────────────────────────────┘ ↓ HTTP POST ┌─────────────────────────────────────────────────────────────────┐ │ 3. Backend Go (PascalCase struct) │ ├─────────────────────────────────────────────────────────────────┤ │ type CreateClusterRequest struct { │ │ Name string `json:"name"` │ │ CAData string `json:"ca_data"` // PascalCase │ │ CertData string `json:"cert_data"` │ │ } │ │ │ │ // json.Unmarshal 自动映射 │ │ var req CreateClusterRequest │ │ json.Unmarshal(body, &req) │ │ // req.CAData = "LS0t..." // 自动转换! │ └─────────────────────────────────────────────────────────────────┘ ↓ 处理业务逻辑 ┌─────────────────────────────────────────────────────────────────┐ │ 4. Backend Response (JSON snake_case) │ ├─────────────────────────────────────────────────────────────────┤ │ { │ │ "id": "cluster-123", │ │ "name": "Production", │ │ "has_ca_data": true, // snake_case │ │ "created_at": "2025-11-10T08:00:00Z" │ │ } │ └─────────────────────────────────────────────────────────────────┘ ↓ fromJson(Cluster, response) ┌─────────────────────────────────────────────────────────────────┐ │ 5. Frontend 使用 (TypeScript camelCase) │ ├─────────────────────────────────────────────────────────────────┤ │ const cluster: Cluster = await createCluster(request); │ │ │ │ // 使用 camelCase │ │ console.log(cluster.hasCAData); // true │ │ console.log(cluster.createdAt); // "2025-11-10T08:00:00Z" │ │ │ │ // 在 React 组件中 │ │ {cluster.hasCAData && Has CA} │ └─────────────────────────────────────────────────────────────────┘ ``` --- ## 🎯 关键相似点 | 特性 | Go | TypeScript + class-transformer | |------|----|---------------------------------| | **元数据标记** | `struct tags` | `@Expose` 装饰器 | | **内部命名** | `PascalCase` | `camelCase` | | **JSON 命名** | `snake_case` | `snake_case` | | **序列化** | `json.Marshal()` | `toJson()` | | **反序列化** | `json.Unmarshal()` | `fromJson()` | | **自动转换** | ✅ 内置支持 | ✅ 通过 class-transformer | --- ## 🔧 实现代码对比 ### Go - 完整示例 ```go package dto import "encoding/json" type ClusterResponse struct { ID string `json:"id"` Name string `json:"name"` HasCAData bool `json:"has_ca_data"` CreatedAt string `json:"created_at"` } func main() { // 创建实例 cluster := ClusterResponse{ ID: "123", Name: "Test", HasCAData: true, CreatedAt: "2025-11-10", } // 序列化 jsonBytes, _ := json.Marshal(cluster) // Output: {"id":"123","name":"Test","has_ca_data":true,"created_at":"2025-11-10"} // 反序列化 var newCluster ClusterResponse json.Unmarshal(jsonBytes, &newCluster) // newCluster.HasCAData = true } ``` ### TypeScript - 完整示例 ```typescript import { Expose } from 'class-transformer'; import { fromJson, toJson } from '@/api/serializer'; class Cluster { @Expose() id!: string; @Expose() name!: string; @Expose({ name: 'has_ca_data' }) hasCAData?: boolean; @Expose({ name: 'created_at' }) createdAt!: string; } function main() { // 创建实例 const cluster = new Cluster(); cluster.id = "123"; cluster.name = "Test"; cluster.hasCAData = true; cluster.createdAt = "2025-11-10"; // 序列化 const json = toJson(cluster); // Output: {id:"123",name:"Test",has_ca_data:true,created_at:"2025-11-10"} // 反序列化 const newCluster = fromJson(Cluster, json); // newCluster.hasCAData === true } ``` --- ## 📝 OpenAPI 驱动开发 ### OpenAPI 规范 → Go ```yaml # backend/docs/openapi.yaml components: schemas: ClusterResponse: # → type ClusterResponse struct properties: id: # → ID string `json:"id"` type: string has_ca_data: # → HasCAData bool `json:"has_ca_data"` type: boolean created_at: # → CreatedAt string `json:"created_at"` type: string ``` ### OpenAPI 规范 → TypeScript ```yaml # backend/docs/openapi.yaml components: schemas: ClusterResponse: # → class Cluster properties: id: # → @Expose() id!: string type: string has_ca_data: # → @Expose({ name: 'has_ca_data' }) hasCAData?: boolean type: boolean created_at: # → @Expose({ name: 'created_at' }) createdAt!: string type: string ``` --- ## ✨ 总结 ### Go 的优势 - ✅ 内置支持,无需额外库 - ✅ 编译时生成代码 - ✅ 零运行时开销 ### TypeScript + class-transformer 的优势 - ✅ 与 Go 相似的开发体验 - ✅ 装饰器语法清晰 - ✅ 类型安全 - ✅ 运行时开销极小 ### 共同点 - ✅ 都使用元数据标记字段映射 - ✅ 都实现了自动类型转换 - ✅ 都保持了代码的可读性和可维护性 - ✅ 都支持 OpenAPI 驱动开发 --- **结论**: TypeScript + class-transformer 成功复现了 Go 的 struct tags 机制,为前端开发提供了同样优雅的类型转换体验! --- **创建日期**: 2025-11-10 **作者**: AI Assistant