Configuration Guide
DataNormalizer uses a fluent API and attributes to control how your object graph is normalized. All configuration is defined in a class that inherits from NormalizationConfig.
Auto-discovery
NormalizeGraph<T>() walks the type graph starting from T and discovers all referenced complex types automatically:
builder.NormalizeGraph<Team>(); // discovers Person, Address, etc.
Any complex type reachable from Team is automatically included in the normalization graph and gets its own flat collection.
Opt-out (Inline)
Keep a type inline instead of normalizing it into a separate collection:
builder.NormalizeGraph<Person>(graph =>
{
graph.Inline<Metadata>(); // Metadata stays nested, not extracted
});
Inlined types retain their original structure within the parent DTO rather than being replaced by an index reference.
Ignore a property
Exclude a property from the generated DTO entirely.
Fluent API
builder.ForType<Person>(p => p.IgnoreProperty(x => x.Secret));
Attribute
public class Person
{
public string Name { get; set; }
[NormalizeIgnore]
public string Secret { get; set; }
}
Ignored properties are omitted from the generated {TypeName}Dto class.
ExplicitOnly mode
Only include properties that are explicitly opted-in. All other properties are excluded.
Fluent API
builder.ForType<Person>(p =>
{
p.UsePropertyMode(PropertyMode.ExplicitOnly);
p.IncludeProperty(x => x.Name);
});
Attribute
public class Person
{
[NormalizeInclude]
public string Name { get; set; }
public string NotIncluded { get; set; }
}
When ExplicitOnly mode is active, only properties marked with [NormalizeInclude] or registered via IncludeProperty() appear in the generated DTO.
Multiple root types
Register multiple roots to generate overloaded Normalize()/Denormalize() methods for each root type:
protected override void Configure(NormalizeBuilder builder)
{
builder.NormalizeGraph<Team>(); // → TeamResultDto
builder.NormalizeGraph<Order>(); // → OrderResultDto
}
Each root type gets its own Normalize and Denormalize overload on the configuration class.
Naming Policy
Control the naming of generated DTO types and their JSON serialization:
builder.UseNaming(n =>
{
n.DtoSuffix = "Dto"; // suffix for DTO types (default: "Dto")
n.DtoPrefix = ""; // prefix for DTO types (default: "")
n.ContainerSuffix = "Dto"; // suffix for container type (default: "Dto")
n.EmitJsonPropertyNames = true; // emit [JsonPropertyName] attributes (default: true)
});
Naming can be configured globally or per-graph:
builder.NormalizeGraph<Team>(graph =>
{
graph.UseNaming(n => { n.DtoSuffix = "Model"; });
});
For full details, see Naming & JSON Contracts.
JSON Contract Customization
Control the JSON wire format of the container:
builder.NormalizeGraph<SearchResponse>(graph =>
{
graph.UseJsonContract(c =>
{
c.RootPropertyName = "result";
c.Collection<Route>("routes");
c.Collection<Place>("places");
});
});
Override individual reference property JSON names:
builder.ForType<Hop>(x =>
{
x.Reference(p => p.Carrier).JsonName("carrier");
});
Or use the [NormalizeJsonName] attribute:
public class Hop
{
[NormalizeJsonName("carrier")]
public Carrier Carrier { get; set; }
}
For full details, see Naming & JSON Contracts.
Container Result API
Each NormalizeGraph<T>() produces a container DTO ({RootType}ResultDto) that provides access to the flat, deduplicated collections as typed arrays:
var result = SearchNormalizer.Normalize(team);
result.Result // TeamDto — the root DTO
result.PersonDtos // PersonDto[] (typed array)
result.AddressDtos // AddressDto[] (typed array)
// All collections are typed properties — no string-keyed lookups.
// The container serializes directly with System.Text.Json.
The root type is always accessible via Result. It only gets a list array (e.g. TeamDtos) if other types in the graph reference it; otherwise, the single root is available through Result alone.
For full API details, see the API Reference.
Circular references
The generator handles circular references in the type graph:
- The generator detects cycles at compile time and emits a DN0001 warning.
- Suppress with
<NoWarn>$(NoWarn);DN0001</NoWarn>in your.csprojif the cycle is intentional. - Normalization handles cycles correctly via value-equality-based deduplication.
- Denormalization uses a two-pass approach: create all objects first, then resolve references.
See Diagnostics Reference for details on DN0001 and other diagnostics.