Graph Building
Phase 3 turns the flat list of IndexedSymbol root models into a recursive ModelGraph — a directed acyclic graph (DAG) of every type reachable from each root, with inheritance resolved and polymorphic variants expanded.
Component Architecture
Graph Building Flow
processSymbol(symbol, depth) steps:
Inheritance Resolution
ModelGraphGenerator tracks where each property was originally declared so the JSON Schema can correctly attribute inherited fields:
class Animal {
let name: String // declared in Animal
}
class Dog: Animal {
let breed: String // declared in Dog
}
class Poodle: Dog {
let color: String // declared in Poodle
}
For Poodle, the graph builder:
- Calls
getInheritanceChain("Poodle")→[Dog, Animal] - Extracts
Dog's properties →[breed], taggeddeclaredIn: "Dog" - Extracts
Animal's properties →[name], taggeddeclaredIn: "Animal" - Assigns to
ModelNode.inheritedProperties Poodle.properties=[color](own only)
The JSON Schema output then emits allOf with $ref to each ancestor.
Polymorphic Variant Expansion
When a property has @PolymorphicMapping, the graph builder expands each variant type:
The converter then emits a oneOf with a discriminator mapping.
Depth Limiting
The recursion depth limit (default 20) prevents runaway expansion on pathological codebases. If a type is encountered past the limit, it is included as a terminal node without children — no data is lost for the root structure, only deep nested expansion is bounded.
guard depth < maxDepth else {
return ModelNode(name: symbol.name, isTruncated: true)
}
Graph Shape Examples
Simple linear chain
Shared type (diamond)
Cyclic reference
The CyclicNode carries the name and schema ID but has empty children and is flagged isCyclic: true, so the converter emits a $ref back to the parent definition instead of re-expanding it.