Objects to Structs
Objects & Structs
Introduction
In this lesson, you'll learn about objects to structs in Go. Coming from TypeScript, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.
In TypeScript, you're familiar with objects & structs.
Go has its own approach to objects & structs, which we'll explore step by step.
The Go Way
Let's see how Go handles this concept. Here's a typical example:
type User struct {
ID int
Name string
Email string
}
type UserService struct {
users map[int]User
}
func NewUserService() *UserService {
return &UserService{users: make(map[int]User)}
}
func (s *UserService) Add(user User) {
s.users[user.ID] = user
}
func (s *UserService) Find(id int) (User, bool) {
user, ok := s.users[id]
return user, ok
}Comparing to TypeScript
Here's how you might have written similar code in TypeScript:
interface User {
id: number;
name: string;
email: string;
}
class UserService {
private users: Map<number, User> = new Map();
add(user: User): void {
this.users.set(user.id, user);
}
find(id: number): User | undefined {
return this.users.get(id);
}
}You may be used to different syntax or behavior.
TypeScript interface/class → Go struct + methods
You may be used to different syntax or behavior.
Constructor pattern: Go uses a New() factory function by convention
You may be used to different syntax or behavior.
Methods use a receiver (s *UserService) instead of this
You may be used to different syntax or behavior.
Pointer receivers (*Type) allow mutation; value receivers (Type) get a copy
Step-by-Step Breakdown
1. Struct + Methods Replace Classes
A Go struct holds data. Methods are defined separately with a receiver. This is functionally equivalent to a TypeScript class.
class Point {
constructor(public x: number, public y: number) {}
distance(): number { return Math.sqrt(this.x**2 + this.y**2); }
}type Point struct {
X, Y float64
}
func (p Point) Distance() float64 {
return math.Sqrt(p.X*p.X + p.Y*p.Y)
}2. Pointer vs Value Receivers
If a method needs to modify the struct, use a pointer receiver (*Type). If it only reads, a value receiver (Type) works. Consistency matters — pick one per type.
class Counter {
private count = 0;
increment() { this.count++; }
}type Counter struct{ count int }
func (c *Counter) Increment() { c.count++ } // pointer — mutates
func (c Counter) Value() int { return c.count } // value — reads only3. Factory Functions (New...)
Go has no constructor syntax. By convention, create a NewTypeName() function that returns an initialized pointer to the struct.
const service = new UserService();func NewUserService() *UserService {
return &UserService{
users: make(map[int]User),
}
}
service := NewUserService()4. struct Tags for JSON
Go struct fields can have tags for JSON marshaling — like TypeScript's zod schema or class-transformer decorators.
interface User {
id: number; // serializes as "id"
firstName: string; // you'd use camelCase in JSON
}type User struct {
ID int `json:"id"`
FirstName string `json:"first_name"`
Password string `json:"-"` // omit from JSON
}Common Mistakes
When coming from TypeScript, developers often make these mistakes:
- TypeScript interface/class → Go struct + methods
- Constructor pattern: Go uses a New() factory function by convention
- Methods use a receiver (s *UserService) instead of this
Key Takeaways
- Go struct + separate methods replaces TypeScript class
- Use NewTypeName() factory function instead of constructors
- Pointer receivers mutate; value receivers read-only — be consistent
- Struct tags control JSON serialization