Agents patterns-serialization-dev
Cross-cutting patterns for serialization and validation across languages. Use when translating JSON handling between languages, converting struct tags to derive macros, mapping validation libraries, or designing schema-based serialization for language conversions.
install
source · Clone the upstream repo
git clone https://github.com/aRustyDev/agents
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/aRustyDev/agents "$T" && mkdir -p ~/.claude/skills && cp -r "$T/content/skills/patterns-serialization-dev" ~/.claude/skills/arustydev-agents-patterns-serialization-dev && rm -rf "$T"
manifest:
content/skills/patterns-serialization-dev/SKILL.mdsource content
Serialization Patterns
Cross-language reference for serialization, deserialization, and validation patterns. This skill helps translate data handling patterns between languages during code conversion.
Overview
This skill covers:
- JSON/YAML/TOML serialization comparison
- Struct tags, derive macros, dataclasses
- Validation library mappings
- Schema generation and enforcement
- Custom serializer/deserializer patterns
This skill does NOT cover:
- Building applications with serialization (see
skills)lang-*-dev - Protocol buffers/gRPC (see dedicated skills)
- Database ORM mapping (see database-specific skills)
Serialization Mechanism Comparison
| Language | Primary Approach | Configuration | Validation |
|---|---|---|---|
| TypeScript | Class decorators | | , |
| Python | Dataclasses/Pydantic | Type hints + decorators | Built into Pydantic |
| Rust | Derive macros | attributes | crate |
| Go | Struct tags | | package |
| Java/Kotlin | Annotations | | Bean Validation |
| C# | Attributes | | Data Annotations |
JSON Serialization by Language
TypeScript
// class-transformer + class-validator import { Type, Expose, Transform } from 'class-transformer'; import { IsEmail, Length, IsOptional } from 'class-validator'; class User { @Expose({ name: 'user_id' }) id: string; @Length(1, 100) name: string; @IsEmail() @IsOptional() email?: string; @Type(() => Date) @Transform(({ value }) => new Date(value), { toClassOnly: true }) createdAt: Date; } // Usage const user = plainToInstance(User, jsonData); const errors = await validate(user);
Python (Pydantic)
from pydantic import BaseModel, Field, EmailStr, field_validator from datetime import datetime from typing import Optional class User(BaseModel): id: str = Field(alias='user_id') name: str = Field(min_length=1, max_length=100) email: Optional[EmailStr] = None created_at: datetime @field_validator('name') @classmethod def name_must_not_be_empty(cls, v: str) -> str: if not v.strip(): raise ValueError('name cannot be empty') return v class Config: populate_by_name = True # Allow both 'id' and 'user_id' # Usage user = User.model_validate(json_data) json_str = user.model_dump_json()
Python (dataclasses + dacite)
from dataclasses import dataclass, field from typing import Optional from datetime import datetime from dacite import from_dict, Config @dataclass class User: id: str name: str email: Optional[str] = None created_at: datetime = field(default_factory=datetime.now) # Usage user = from_dict(User, json_data, config=Config( cast=[datetime], strict=True ))
Rust (Serde)
use serde::{Deserialize, Serialize}; use chrono::{DateTime, Utc}; use validator::Validate; #[derive(Debug, Serialize, Deserialize, Validate)] #[serde(rename_all = "snake_case")] struct User { #[serde(rename = "user_id")] id: String, #[validate(length(min = 1, max = 100))] name: String, #[serde(skip_serializing_if = "Option::is_none")] #[validate(email)] email: Option<String>, #[serde(with = "chrono::serde::ts_seconds")] created_at: DateTime<Utc>, } // Usage let user: User = serde_json::from_str(&json_str)?; user.validate()?; let json = serde_json::to_string(&user)?;
Go
import ( "encoding/json" "time" "github.com/go-playground/validator/v10" ) type User struct { ID string `json:"user_id"` Name string `json:"name" validate:"required,min=1,max=100"` Email *string `json:"email,omitempty" validate:"omitempty,email"` CreatedAt time.Time `json:"created_at"` } // Usage var user User err := json.Unmarshal(jsonData, &user) validate := validator.New() err = validate.Struct(user)
Java (Jackson)
import com.fasterxml.jackson.annotation.*; import jakarta.validation.constraints.*; import java.time.Instant; public class User { @JsonProperty("user_id") private String id; @NotBlank @Size(min = 1, max = 100) private String name; @Email private String email; @JsonFormat(shape = JsonFormat.Shape.STRING) private Instant createdAt; // getters, setters... } // Usage ObjectMapper mapper = new ObjectMapper(); User user = mapper.readValue(jsonStr, User.class);
Field Mapping Translation
Rename Fields
| Language | Syntax | Example |
|---|---|---|
| TypeScript | | |
| Python | | |
| Rust | | |
| Go | | |
| Java | | |
Case Conversion
| Language | Syntax | Result |
|---|---|---|
| TypeScript | Manual or transformer | N/A |
| Python | | |
| Rust | | |
| Go | (manual) | |
| Java | | |
Skip Null/Empty
| Language | Syntax | Effect |
|---|---|---|
| TypeScript | with condition | Excludes field |
| Python | in | Skips None |
| Rust | | Skips None |
| Go | | Skips zero values |
| Java | | Skips null |
Validation Pattern Translation
String Validation
| Validation | TypeScript | Python | Rust | Go |
|---|---|---|---|---|
| Required | | (not Optional) | Not Option | |
| Min length | | | | |
| Max length | | | | |
| Regex | | | | |
| | | | |
| URL | | | | |
Numeric Validation
| Validation | TypeScript | Python | Rust | Go |
|---|---|---|---|---|
| Min value | | | | |
| Max value | | | | |
| Positive | | | | |
| Negative | | | | |
Custom Validation
TypeScript:
@ValidatorConstraint() class IsAdultConstraint implements ValidatorConstraintInterface { validate(age: number) { return age >= 18; } defaultMessage() { return 'Must be 18 or older'; } } @Validate(IsAdultConstraint) age: number;
Python (Pydantic):
@field_validator('age') @classmethod def must_be_adult(cls, v: int) -> int: if v < 18: raise ValueError('Must be 18 or older') return v
Rust:
fn validate_adult(age: &i32) -> Result<(), validator::ValidationError> { if *age < 18 { return Err(validator::ValidationError::new("must_be_adult")); } Ok(()) } #[validate(custom(function = "validate_adult"))] age: i32,
Go:
func isAdult(fl validator.FieldLevel) bool { return fl.Field().Int() >= 18 } validate.RegisterValidation("adult", isAdult) type Person struct { Age int `validate:"adult"` }
Nested Object Handling
TypeScript
class Address { @IsString() street: string; } class User { @ValidateNested() @Type(() => Address) address: Address; @ValidateNested({ each: true }) @Type(() => Address) addresses: Address[]; }
Python
class Address(BaseModel): street: str class User(BaseModel): address: Address addresses: list[Address]
Rust
#[derive(Serialize, Deserialize, Validate)] struct Address { street: String, } #[derive(Serialize, Deserialize, Validate)] struct User { #[validate(nested)] address: Address, #[validate(nested)] addresses: Vec<Address>, }
Go
type Address struct { Street string `json:"street" validate:"required"` } type User struct { Address Address `json:"address" validate:"required"` Addresses []Address `json:"addresses" validate:"dive"` }
Default Values
| Language | Syntax | Example |
|---|---|---|
| TypeScript | Property initializer | |
| Python | | |
| Rust | | |
| Go | Not in struct tags | Manual in constructor |
| Java | Field initializer | |
Rust default function:
fn default_status() -> String { "pending".to_string() } #[derive(Deserialize)] struct Order { #[serde(default = "default_status")] status: String, }
Library Mapping
Serialization Libraries
| Category | TypeScript | Python | Rust | Go |
|---|---|---|---|---|
| JSON | Built-in | | | |
| YAML | | | | |
| TOML | | / | | |
| MessagePack | | | | |
Validation Libraries
| TypeScript | Python | Rust | Go | Java |
|---|---|---|---|---|
| Pydantic (built-in) | | | Bean Validation |
| | | | Hibernate Validator |
| | - | - | - |
| | - | - | - |
Schema Generation
| TypeScript | Python | Rust | Go |
|---|---|---|---|
| (built-in) | | |
| | - | - |
Common Patterns
Optional vs Required
TypeScript: name?: string → Optional Python: name: Optional[str] → Optional Rust: name: Option<String> → Optional Go: Name *string → Optional (pointer)
Date/Time Handling
| Language | Type | JSON Format | Custom Format |
|---|---|---|---|
| TypeScript | | ISO string | |
| Python | | ISO string | |
| Rust | | ISO string | |
| Go | | RFC3339 | Custom |
Enums
TypeScript:
enum Status { Pending = 'pending', Active = 'active' } class Order { @IsEnum(Status) status: Status; }
Python:
from enum import Enum class Status(str, Enum): pending = 'pending' active = 'active' class Order(BaseModel): status: Status
Rust:
#[derive(Serialize, Deserialize)] #[serde(rename_all = "lowercase")] enum Status { Pending, Active, }
Go:
type Status string const ( StatusPending Status = "pending" StatusActive Status = "active" )
Anti-Patterns
1. Mixing Validation and Business Logic
# ❌ Business logic in validator @field_validator('age') def check_age(cls, v): if v < 18: send_notification("underage user attempted") # Side effect! raise ValueError('underage') return v # ✓ Separate concerns @field_validator('age') def check_age(cls, v): if v < 18: raise ValueError('underage') return v # Business logic elsewhere if not user.is_adult(): send_notification(...)
2. Over-validation
# ❌ Validating internal types class InternalData(BaseModel): # This is only used internally, no need for extensive validation temp_id: str = Field(regex=r'^[a-f0-9]{32}$') # ✓ Validate at boundaries only class UserInput(BaseModel): # External input email: EmailStr # Validate here class InternalData: # Internal use temp_id: str # Trust internal code
3. Ignoring Serialization Errors
// ❌ Ignoring errors json.Unmarshal(data, &user) // Error ignored! // ✓ Handle errors if err := json.Unmarshal(data, &user); err != nil { return fmt.Errorf("invalid user data: %w", err) }
Best Practices
- Validate at boundaries - Only validate external input, trust internal types
- Use schema-first when possible for API contracts
- Prefer declarative over imperative validation
- Keep serialization pure - No side effects in serializers
- Document format expectations in struct/class comments
- Test edge cases - Empty strings, nulls, malformed dates
- Version your schemas for backward compatibility
Related Skills
- Code conversion patternsmeta-convert-dev
- Decorators/macros used for serializationpatterns-metaprogramming-dev
skills - Language-specific serialization detailslang-*-dev