Reflection
Runtime types: TypeScript vs Go
หัวข้อที่มีชื่อว่า “Runtime types: TypeScript vs Go”ใน TypeScript คุณตรวจ type metadata ที่ runtime ได้ผ่าน reflect-metadata และ decorator — แต่ต้อง opt-in เอง: เปิด experimentalDecorators กับ emitDecoratorMetadata ใน tsconfig.json และ metadata ก็ต้อง attach ด้วยมือจาก decorator function เอง JavaScript ลบข้อมูล type ออกเกือบหมดตอน runtime decorator จึงเป็นแค่ workaround ไม่ใช่ language primitive
Go เป็นภาษา compile เหมือนกัน แต่ต่างจาก TypeScript ตรงที่เก็บข้อมูล type ไว้ครบที่ runtime ผ่าน reflect package ทุกค่าใน Go รู้ type ของตัวเองตอน runtime — ไม่ต้องมี annotation ไม่ต้องตั้ง build flag
// TypeScript — ต้องใช้ reflect-metadata + decorator compiler flagsimport 'reflect-metadata';
function Column(target: any, key: string) { const type = Reflect.getMetadata('design:type', target, key); console.log(`${key}: ${type.name}`);}
class User { @Column name: string = ''; @Column age: number = 0;}// Go — built-in ไม่ต้องใช้ flagsimport "reflect"
type User struct { Name string Age int}
u := User{Name: "Alice", Age: 30}t := reflect.TypeOf(u)
for i := 0; i < t.NumField(); i++ { f := t.Field(i) fmt.Printf("%s: %s\n", f.Name, f.Type)}// Name: string// Age: intStruct tags
หัวข้อที่มีชื่อว่า “Struct tags”Struct tag คือ string literal ที่ฝังอยู่ในการประกาศ field มองไม่เห็นตอน compile time และอ่านได้เฉพาะตอน runtime ผ่าน reflect นี่คือวิธีที่ encoding/json, scanner ของ database/sql และ validation library อย่าง go-playground/validator รู้ว่า JSON key ไหน map กับ field ไหน
// TypeScript — class-validator ใช้ decoratorsimport { IsEmail, IsString, Min } from 'class-validator';
class CreateUserDto { @IsString() name: string;
@IsEmail() email: string;
@Min(0) age: number;}// Go — struct tags (plain strings อ่านผ่าน reflect)type CreateUserDTO struct { Name string `json:"name" validate:"required"` Email string `json:"email" validate:"required,email"` Age int `json:"age" validate:"min=0"`}
// encoding/json ใช้ "json" tag อัตโนมัติ// Validator libraries อ่าน "validate" ที่ runtimeอ่าน struct tag ที่ runtime
หัวข้อที่มีชื่อว่า “อ่าน struct tag ที่ runtime”pattern มาตรฐานสำหรับอ่าน tag คือ reflect.Type.Field(i).Tag.Get("key"):
func printJSONTags(v any) { t := reflect.TypeOf(v) if t.Kind() == reflect.Ptr { t = t.Elem() // dereference pointer } for i := 0; i < t.NumField(); i++ { f := t.Field(i) jsonKey := f.Tag.Get("json") fmt.Printf("Field: %-10s json=%q\n", f.Name, jsonKey) }}reflect.Value vs reflect.Type
หัวข้อที่มีชื่อว่า “reflect.Value vs reflect.Type”reflect.TypeOf ให้ type descriptor (kind, field, method) ส่วน reflect.ValueOf ให้ boxed value ที่คุณตรวจดูได้ และ — ถ้าระวังพอ — แก้ค่าได้ด้วย
u := User{Name: "Alice", Age: 30}
// Type introspectiont := reflect.TypeOf(u)fmt.Println(t.Kind()) // structfmt.Println(t.NumField()) // 2
// Value introspectionv := reflect.ValueOf(u)fmt.Println(v.Field(0)) // Alicefmt.Println(v.Field(1)) // 30
// Mutation ต้องใช้ pointer และ CanSet()vp := reflect.ValueOf(&u).Elem()vp.Field(0).SetString("Bob")fmt.Println(u.Name) // Bobลองดูเลย
หัวข้อที่มีชื่อว่า “ลองดูเลย”package main
import ( "fmt" "reflect")
type User struct { Name string `json:"name" validate:"required"` Email string `json:"email" validate:"required,email"` Age int `json:"age"`}
func printStructTags(v any) { t := reflect.TypeOf(v) if t.Kind() == reflect.Ptr { t = t.Elem() } fmt.Println("--- Struct tags ---") for i := 0; i < t.NumField(); i++ { f := t.Field(i) fmt.Printf("%-8s json=%q validate=%q\n", f.Name, f.Tag.Get("json"), f.Tag.Get("validate"), ) }}
func printValues(v any) { rv := reflect.ValueOf(v) rt := rv.Type() fmt.Println("--- Values ---") for i := 0; i < rv.NumField(); i++ { fmt.Printf("%-8s = %v\n", rt.Field(i).Name, rv.Field(i)) }}
func main() { printStructTags(u) printValues(u)}Loading Go runtime (first run only, ~8 MB)…