zoobzio January 5, 2025 Edit this page

Nested Structs

Working with embedded and nested struct types.

Embedded Structs

Embedded structs are stored in the Nested map:

type Address struct {
    Street  string
    City    string
    ZipCode string
}

type User struct {
    Name    string
    Email   string
    Address Address // Embedded struct
}

atomizer, _ := atom.Use[User]()
user := &User{
    Name:  "Alice",
    Email: "alice@example.com",
    Address: Address{
        Street:  "123 Main St",
        City:    "Springfield",
        ZipCode: "12345",
    },
}

atom := atomizer.Atomize(user)
// atom.Strings["Name"] = "Alice"
// atom.Strings["Email"] = "alice@example.com"
// atom.Nested["Address"] = Atom{
//     Strings: {"Street": "123 Main St", "City": "Springfield", "ZipCode": "12345"}
// }

Accessing Nested Atoms

// Access the nested atom
addressAtom := atom.Nested["Address"]

// Read nested fields
street := addressAtom.Strings["Street"]
city := addressAtom.Strings["City"]

fmt.Println(street) // "123 Main St"
fmt.Println(city)   // "Springfield"

Pointer to Struct

Pointer fields allow optional nested structs:

type User struct {
    Name    string
    Address *Address // Optional
}

atomizer, _ := atom.Use[User]()

// With address
user := &User{
    Name:    "Alice",
    Address: &Address{Street: "123 Main St", City: "Springfield"},
}
atom := atomizer.Atomize(user)
// atom.Nested["Address"] exists

// Without address
user = &User{Name: "Bob", Address: nil}
atom = atomizer.Atomize(user)
// atom.Nested["Address"] does not exist

Checking for Nil

if addressAtom, ok := atom.Nested["Address"]; ok {
    fmt.Println("Has address:", addressAtom.Strings["City"])
} else {
    fmt.Println("No address")
}

Slice of Structs

Slices of structs are stored in NestedSlices:

type Order struct {
    ID    int64
    Items []OrderItem
}

type OrderItem struct {
    ProductID int64
    Quantity  int64
    Price     float64
}

atomizer, _ := atom.Use[Order]()
order := &Order{
    ID: 1001,
    Items: []OrderItem{
        {ProductID: 1, Quantity: 2, Price: 19.99},
        {ProductID: 2, Quantity: 1, Price: 49.99},
    },
}

atom := atomizer.Atomize(order)
// atom.Ints["ID"] = 1001
// atom.NestedSlices["Items"] = [
//     Atom{Ints: {"ProductID": 1, "Quantity": 2}, Floats: {"Price": 19.99}},
//     Atom{Ints: {"ProductID": 2, "Quantity": 1}, Floats: {"Price": 49.99}},
// ]

Iterating Nested Slices

for i, itemAtom := range atom.NestedSlices["Items"] {
    productID := itemAtom.Ints["ProductID"]
    quantity := itemAtom.Ints["Quantity"]
    price := itemAtom.Floats["Price"]
    fmt.Printf("Item %d: product=%d qty=%d price=%.2f\n",
        i, productID, quantity, price)
}

Deeply Nested Structures

Atom handles arbitrary nesting depth:

type Company struct {
    Name        string
    Departments []Department
}

type Department struct {
    Name    string
    Manager Employee
    Staff   []Employee
}

type Employee struct {
    Name   string
    Title  string
    Salary float64
}

atomizer, _ := atom.Use[Company]()
company := &Company{
    Name: "Acme Corp",
    Departments: []Department{
        {
            Name: "Engineering",
            Manager: Employee{Name: "Alice", Title: "VP", Salary: 150000},
            Staff: []Employee{
                {Name: "Bob", Title: "Senior", Salary: 120000},
                {Name: "Carol", Title: "Junior", Salary: 80000},
            },
        },
    },
}

atom := atomizer.Atomize(company)
// atom.Strings["Name"] = "Acme Corp"
// atom.NestedSlices["Departments"][0].Strings["Name"] = "Engineering"
// atom.NestedSlices["Departments"][0].Nested["Manager"].Strings["Name"] = "Alice"
// atom.NestedSlices["Departments"][0].NestedSlices["Staff"][0].Strings["Name"] = "Bob"

Self-Referential Types

Atom handles recursive type definitions:

type Node struct {
    Value    int64
    Children []Node
}

atomizer, _ := atom.Use[Node]()
tree := &Node{
    Value: 1,
    Children: []Node{
        {Value: 2, Children: nil},
        {Value: 3, Children: []Node{
            {Value: 4, Children: nil},
        }},
    },
}

atom := atomizer.Atomize(tree)
// atom.Ints["Value"] = 1
// atom.NestedSlices["Children"][0].Ints["Value"] = 2
// atom.NestedSlices["Children"][1].Ints["Value"] = 3
// atom.NestedSlices["Children"][1].NestedSlices["Children"][0].Ints["Value"] = 4

Pointer Self-Reference

type LinkedNode struct {
    Value int64
    Next  *LinkedNode
}

atomizer, _ := atom.Use[LinkedNode]()
list := &LinkedNode{
    Value: 1,
    Next: &LinkedNode{
        Value: 2,
        Next:  nil,
    },
}

atom := atomizer.Atomize(list)
// atom.Ints["Value"] = 1
// atom.Nested["Next"].Ints["Value"] = 2
// atom.Nested["Next"].Nested["Next"] does not exist (nil)

Slice of Pointer to Struct

type Team struct {
    Name    string
    Members []*Person
}

type Person struct {
    Name string
    Role string
}

atomizer, _ := atom.Use[Team]()
team := &Team{
    Name: "Alpha",
    Members: []*Person{
        {Name: "Alice", Role: "Lead"},
        {Name: "Bob", Role: "Developer"},
    },
}

atom := atomizer.Atomize(team)
// Works the same as []Person
// atom.NestedSlices["Members"][0].Strings["Name"] = "Alice"

Building Nested Atoms Manually

// Create parent atomizer
orderAtomizer, _ := atom.Use[Order]()
itemAtomizer, _ := atom.Use[OrderItem]()

// Build nested atoms
item1 := itemAtomizer.NewAtom()
item1.Ints["ProductID"] = 1
item1.Ints["Quantity"] = 2
item1.Floats["Price"] = 19.99

item2 := itemAtomizer.NewAtom()
item2.Ints["ProductID"] = 2
item2.Ints["Quantity"] = 1
item2.Floats["Price"] = 49.99

// Assemble parent
orderAtom := orderAtomizer.NewAtom()
orderAtom.Ints["ID"] = 1001
orderAtom.NestedSlices["Items"] = []atom.Atom{*item1, *item2}

// Deatomize
order, _ := orderAtomizer.Deatomize(orderAtom)

Spec Propagation

Nested atoms carry their own type specs:

atom := atomizer.Atomize(user)

fmt.Println(atom.Spec.TypeName)                    // "User"
fmt.Println(atom.Nested["Address"].Spec.TypeName)  // "Address"

Best Practices

Flatten When Possible

For simple cases, consider flattening:

// Deeply nested (harder to query)
type User struct {
    Profile struct {
        Contact struct {
            Email string
        }
    }
}

// Flattened (easier to query)
type User struct {
    ProfileContactEmail string
}

Use Pointers for Optional

// Good - explicit optionality
type User struct {
    BillingAddress  *Address // Optional
    ShippingAddress Address  // Required
}

// Ambiguous - is empty address intentional or missing?
type User struct {
    BillingAddress  Address
    ShippingAddress Address
}

Limit Nesting Depth

Deep nesting increases complexity:

// Hard to work with
atom.NestedSlices["A"][0].Nested["B"].NestedSlices["C"][0].Strings["D"]

// Consider restructuring or using IDs/references

Next Steps