Reflection-Based Struct Serialization

Overview

User suggestion: Use Mojo’s reflection capabilities to automatically serialise/deserialise structs to/from TOML, eliminating the need for explicit .as_string(), .as_int(), etc. calls.

Ideal API

struct Config:
    var name: String
    var port: Int
    var debug: Bool
    var limits: Limits

struct Limits:
    var max_connections: Int
    var timeout: Float64

# Automatic serialisation
var config = from_toml[Config]("config.toml")  # Deserialise
var toml_str = to_toml(config)  # Serialise

Current Reflection Capabilities (Mojo 2026-01)

Mojo’s reflection module provides:

Available: - get_type_name[T]() - Get type names as strings - struct_field_count[T]() - Count fields in a struct - struct_field_names[T]() - Get field names (compile-time) - struct_field_types[T]() - Get field types - struct_field_index_by_name[T]() - Lookup field index - struct_field_type_by_name[T]() - Lookup field type

Critical Limitations: - No runtime field value access (can get names, but not read/write values) - No trait/interface querying - No enum reflection - Compile-time only operations - Early-stage stability concerns

Requirements for Auto-Serialisation

  1. Runtime field access - Read/write field values dynamically
  2. Dynamic construction - Build structs from parsed data
  3. Recursive handling - Process nested structs automatically
  4. Type conversion - Map TOML types to Mojo types safely
  5. Error handling - Validate missing/invalid fields with good errors

Assessment

Not Ready for v0.6.0-0.7.0 ❌

Blockers: 1. Cannot access field values at runtime (critical) 2. Reflection API stability concerns 3. Unknown performance characteristics 4. API will evolve and break code

Consensus: Wait for reflection to mature before implementing.

Interim Solution: Trait-Based Approach

Add a Deserializable trait that users implement manually:

trait Deserializable:
    fn from_toml(data: Dict[String, TomlValue]) raises -> Self

trait Serializable:
    fn to_toml(self) -> Dict[String, TomlValue]

struct Config(Deserializable, Serializable):
    var name: String
    var port: Int
    var debug: Bool
    
    fn from_toml(data: Dict[String, TomlValue]) raises -> Config:
        return Config(
            name=data["name"].as_string(),
            port=data["port"].as_int(),
            debug=data["debug"].as_bool()
        )
    
    fn to_toml(self) -> Dict[String, TomlValue]:
        var result = Dict[String, TomlValue]()
        result["name"] = TomlValue(self.name)
        result["port"] = TomlValue(self.port)
        result["debug"] = TomlValue(self.debug)
        return result^

# Usage - cleaner than raw Dict access:
var data = parse(toml_content)
var config = Config.from_toml(data)
var toml_str = to_toml(config.to_toml())

Benefits: - Reduces boilerplate - Type-safe at struct definition - Stable (no experimental features) - Can be implemented now

Drawbacks: - Still requires manual implementation per struct - Not as ergonomic as automatic reflection

Roadmap

v0.6.0-0.7.0: Manual Trait Approach (Possible)

  • Define Deserializable and Serializable traits
  • Provide example implementations
  • Document patterns in user guide
  • Consider helper macros if Mojo gains macro support

v0.8.0+: Automatic Reflection (When Ready)

  • Automatic serialisation: from_toml[T](str)
  • Automatic deserialisation: to_toml(struct)
  • Type-safe config loading
  • Blocked on: Runtime field access in Mojo reflection

Status: Tracking Mojo stdlib development

References

  • Mojo Reflection Docs: https://docs.modular.com/mojo/std/reflection/
  • User discussion: v0.5.0 release feedback
  • Related: serde in Rust, dataclasses in Python

Decision

Add to roadmap as future feature - Excellent idea, but wait for Mojo reflection to mature. Consider trait-based interim solution for v0.6.0.