DataNormalizer 0.0.1

There is a newer version of this package available.
See the version list below for details.
dotnet add package DataNormalizer --version 0.0.1
                    
NuGet\Install-Package DataNormalizer -Version 0.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="DataNormalizer" Version="0.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="DataNormalizer" Version="0.0.1" />
                    
Directory.Packages.props
<PackageReference Include="DataNormalizer" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add DataNormalizer --version 0.0.1
                    
#r "nuget: DataNormalizer, 0.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package DataNormalizer@0.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=DataNormalizer&version=0.0.1
                    
Install as a Cake Addin
#tool nuget:?package=DataNormalizer&version=0.0.1
                    
Install as a Cake Tool

DataNormalizer

A .NET source generator that normalizes nested object graphs into flat, deduplicated, JSON-serializable containers.

CI NuGet License: MIT

Documentation | API Reference

What It Does

Given an object graph with shared references:

var sharedAddress = new Address { City = "Seattle", Zip = "98101" };

var team = new Team
{
    Name = "Engineering",
    Members = new[]
    {
        new Person { Name = "Alice", Home = sharedAddress },
        new Person { Name = "Bob",   Home = sharedAddress },
    },
};

One call normalizes the entire graph into a flat, deduplicated container:

var result = AppNormalization.Normalize(team);
var json = JsonSerializer.Serialize(result, new JsonSerializerOptions { WriteIndented = true });
{
  "TeamList": [
    { "Name": "Engineering", "MembersIndices": [0, 1] }
  ],
  "PersonList": [
    { "Name": "Alice", "HomeIndex": 0 },
    { "Name": "Bob",   "HomeIndex": 0 }
  ],
  "AddressList": [
    { "City": "Seattle", "Zip": "98101" }
  ]
}

The shared Address is stored once. Nested objects become integer indices into typed arrays. The whole container serializes directly with System.Text.Json and is straightforward to reverse on any frontend.

Installation

dotnet add package DataNormalizer

Supports .NET 6, .NET 7, .NET 8, .NET 9, and .NET 10.

Quick Start

1. Define your domain types

public class Team
{
    public string Name { get; set; }
    public Person[] Members { get; set; }
}

public class Person
{
    public string Name { get; set; }
    public Address Home { get; set; }
}

public class Address
{
    public string City { get; set; }
    public string Zip { get; set; }
}

2. Create a configuration class

using DataNormalizer.Attributes;
using DataNormalizer.Configuration;

[NormalizeConfiguration]
public partial class AppNormalization : NormalizationConfig
{
    protected override void Configure(NormalizeBuilder builder)
    {
        builder.NormalizeGraph<Team>();  // discovers Person, Address
    }
}

3. Normalize, use, and denormalize

// Normalize
var result = AppNormalization.Normalize(team);

// Access the root entity (always at index 0)
var root = result.TeamList[0];
Console.WriteLine(root.Name);             // "Engineering"
Console.WriteLine(root.MembersIndices);   // [0, 1]

// Access entity lists directly
Console.WriteLine(result.PersonList.Length);   // 2
Console.WriteLine(result.AddressList.Length);  // 1 (deduplicated)

// Serialize the entire container to JSON
var json = JsonSerializer.Serialize(result, new JsonSerializerOptions { WriteIndented = true });

// Denormalize back to the original object graph
var restored = AppNormalization.Denormalize(result);

The source generator produces Normalize and Denormalize static methods, per-type DTOs, and the container result class at compile time.

How It Works

For each NormalizeGraph<T>() call, the generator produces:

  1. Per-type DTOs (Normalized{TypeName}) — partial classes implementing IEquatable<T> for value-based deduplication. Nested object references become int indices ({Name}Index), collections become int[] ({Name}Indices).

  2. A container result (Normalized{TypeName}Result) — holds a {TypeName}List array for every entity type in the graph. The root entity is always at index 0 in the root type's list. This is the primary output of Normalize() and the input to Denormalize().

  3. Normalize(T) / Denormalize(Normalized{T}Result) — static methods on the configuration class.

All generated types are partial, so you can extend them with additional members.

Target Frameworks

Component Targets
Runtime library net6.0, net7.0, net8.0, net9.0, net10.0
Source generator netstandard2.0 (Roslyn requirement, bundled in NuGet package)

The generator runs at compile time regardless of your target framework. The runtime library provides the NormalizationContext used internally by generated code.

Configuration Options

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.

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
});

Ignore a property

Exclude a property from the generated DTO:

builder.ForType<Person>(p => p.IgnoreProperty(x => x.Secret));

Or use the attribute:

public class Person
{
    public string Name { get; set; }

    [NormalizeIgnore]
    public string Secret { get; set; }
}

ExplicitOnly mode

Only include properties that are explicitly opted-in:

builder.ForType<Person>(p =>
{
    p.UsePropertyMode(PropertyMode.ExplicitOnly);
    p.IncludeProperty(x => x.Name);
});

Or use attributes:

public class Person
{
    [NormalizeInclude]
    public string Name { get; set; }

    public string NotIncluded { get; set; }
}

Multiple root types

Register multiple roots to generate separate container types and Normalize()/Denormalize() overloads:

protected override void Configure(NormalizeBuilder builder)
{
    builder.NormalizeGraph<Team>();   // → NormalizedTeamResult
    builder.NormalizeGraph<Order>();  // → NormalizedOrderResult
}

Each container includes only the entity lists reachable from its root type.

Reversing Normalized Data

Any consumer (frontend, API client, other language) can reconstruct the original object graph from the serialized container:

  1. Parse JSON into the container shape
  2. Reconstruct leaf entities from their lists
  3. Reconstruct composite entities by resolving index references into entity lists
  4. The root entity is always at index 0 in the root type's list

Shared references are preserved: multiple indices pointing to the same list entry reconstruct as the same object reference.

Circular References

  • The generator detects cycles at compile time and emits a DN0001 warning.
  • Suppress with <NoWarn>$(NoWarn);DN0001</NoWarn> in your .csproj if 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.

Diagnostics

ID Severity Description Resolution
DN0001 Warning Circular reference detected Add <NoWarn>DN0001</NoWarn> if intentional
DN0002 Error Configuration class must be partial Add the partial keyword to the class declaration
DN0003 Error Type has no public properties Add public properties or exclude the type
DN0004 Info Unmapped complex type will be inlined Use graph.Inline<T>() explicitly, or add to the graph

Known Constraints

  • For circular types, back-edge properties (those creating the cycle) use shape-based comparison (null/non-null, collection count) rather than full structural comparison. All non-circular properties — including nested complex subtrees — are fully compared. False dedup only occurs if two objects in a cycle have identical simple properties, identical non-circular subtree structure, AND identical circular reference shapes.

License

MIT — see LICENSE.

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.
  • net6.0

    • No dependencies.
  • net7.0

    • No dependencies.
  • net8.0

    • No dependencies.
  • net9.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.4 83 4/3/2026
0.1.3 90 4/3/2026
0.1.2 100 3/25/2026
0.1.1 75 3/25/2026
0.1.0 79 3/25/2026
0.0.1 84 3/23/2026