Files
ocdp-go/docs/development/go-vs-typescript.md
mangomqy c5e51ed069 ocdp v1
2025-11-13 02:54:06 +00:00

16 KiB
Raw Blame History

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, JSONsnake_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, JSONsnake_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