16 KiB
16 KiB
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 的实现
// 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 的实现
// 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
type ClusterResponse struct {
HasCAData bool `json:"has_ca_data"`
CreatedAt string `json:"created_at"`
}
TypeScript + class-transformer
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
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
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
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
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 && <Badge>Has CA</Badge>} │
└─────────────────────────────────────────────────────────────────┘
🎯 关键相似点
| 特性 | Go | TypeScript + class-transformer |
|---|---|---|
| 元数据标记 | struct tags |
@Expose 装饰器 |
| 内部命名 | PascalCase |
camelCase |
| JSON 命名 | snake_case |
snake_case |
| 序列化 | json.Marshal() |
toJson() |
| 反序列化 | json.Unmarshal() |
fromJson() |
| 自动转换 | ✅ 内置支持 | ✅ 通过 class-transformer |
🔧 实现代码对比
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 - 完整示例
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
# 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
# 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