
Whether you're in C#, Python, Java, PHP, TypeScript, or Ruby, somewhere in your codebase there's a dictionary getting passed around like a mystery grab bag at a white elephant party. "Here's some stuff. Figure it out."
Look familiar?
public void ProcessUser(IDictionary<string, object> userData)
{
string name = (string)userData["name"];
int age = (int)userData["age"];
}
def process_user(user_data: dict):
name = user_data['name']
age = user_data['age']
public void processUser(Map<String, Object> userData) {
String name = (String) userData.get("name");
int age = (int) userData.get("age");
}
function processUser(array $userData) {
$name = $userData['name'];
$age = $userData['age'];
}
function processUser(userData: { [key: string]: any }) {
const name = userData['name'] as string;
const age = userData['age'] as number;
}
def process_user(user_data)
name = user_data[:name]
age = user_data[:age]
end
Just use DTOs.
public class UserDto
{
public string Name { get; init; }
public int Age { get; init; }
}
public void ProcessUser(UserDto user)
{
string name = user.Name;
int age = user.Age;
}
@dataclass(frozen=True)
class UserDto:
name: str
age: int
def process_user(user: UserDto):
name = user.name
age = user.age
public record UserDto(String name, int age) {}
public void processUser(UserDto user) {
String name = user.name();
int age = user.age();
}
readonly class UserDto {
public function __construct(
public readonly string $name,
public readonly int $age
) {}
}
function processUser(UserDto $user) {
$name = $user->name;
$age = $user->age;
}
interface UserDto {
readonly name: string;
readonly age: number;
}
function processUser(user: UserDto) {
const name = user.name;
const age = user.age;
}
UserDto = Struct.new(:name, :age, keyword_init: true) do
def initialize(name:, age:)
super
freeze
end
end
def process_user(user)
name = user.name
age = user.age
end
They're a shape. A contract. "This is what crosses this boundary, and it looks like this." Make them immutable. If you're mutating a DTO somewhere in the pipeline, you don't have a DTO you have a ... gremlin or post-it note.
The real win: when you add a field, you change the DTO, and the compiler yells at every place that needs to care. The dictionary approach? Silent failures. Misspelled keys. Runtime surprises. You're debugging at 2am because someone fat-fingered "userID" as "userId" three sprints ago.
Stop passing bags of maybes. Define and enforce the shape. Move on with your life.
❤️
Jake