Nested Objects

When a source type has a property whose type is also mapped by a Facette DTO, the generator automatically uses the nested DTO in FromSource, ToSource, and Projection.

Auto-detection

If there is exactly one DTO targeting a nested source type, Facette resolves it automatically:

public class Address
{
    public string Street { get; set; } = "";
    public string City { get; set; } = "";
    public string State { get; set; } = "";
    public string ZipCode { get; set; } = "";
}

public class Employee
{
    public int Id { get; set; }
    public string FirstName { get; set; } = "";
    public Address? HomeAddress { get; set; }
}

[Facette(typeof(Address))]
public partial record AddressDto;

[Facette(typeof(Employee))]
public partial record EmployeeDto;
// EmployeeDto.HomeAddress is of type AddressDto

The generated FromSource will call AddressDto.FromSource(source.HomeAddress), and the Projection will inline AddressDto.Projection as a nested member-init expression.

Resolving ambiguity with NestedDtos

When multiple DTOs target the same source type, Facette cannot auto-detect which one to use and emits FCT010. Resolve this by specifying which DTOs to use:

[Facette(typeof(Employee),
    NestedDtos = new[] { typeof(AddressDto) })]
public partial record EmployeeDto;

This tells the generator to use AddressDto whenever it encounters an Address property.

Nullable nested objects

When a source property is nullable (e.g., Address? HomeAddress), the generated mapping handles null correctly:

  • FromSource: source.HomeAddress is not null ? AddressDto.FromSource(source.HomeAddress) : null
  • ToSource: similar null-guarded reverse mapping
  • Projection: null-conditional in the expression tree

The generated DTO property will also be nullable to match.

Circular references

If nested DTOs form a cycle (A → B → A), Facette detects this and emits FCT006 to prevent stack overflows during mapping.