Thinktecture.Runtime.Extensions.Json 9.0.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package Thinktecture.Runtime.Extensions.Json --version 9.0.0
                    
NuGet\Install-Package Thinktecture.Runtime.Extensions.Json -Version 9.0.0
                    
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="Thinktecture.Runtime.Extensions.Json" Version="9.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Thinktecture.Runtime.Extensions.Json" Version="9.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Thinktecture.Runtime.Extensions.Json" />
                    
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 Thinktecture.Runtime.Extensions.Json --version 9.0.0
                    
#r "nuget: Thinktecture.Runtime.Extensions.Json, 9.0.0"
                    
#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 Thinktecture.Runtime.Extensions.Json@9.0.0
                    
#: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=Thinktecture.Runtime.Extensions.Json&version=9.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Thinktecture.Runtime.Extensions.Json&version=9.0.0
                    
Install as a Cake Tool

Build TestResults NuGet Downloads

Thinktecture.Runtime.Extensions
Thinktecture.Runtime.Extensions.EntityFrameworkCore7
Thinktecture.Runtime.Extensions.EntityFrameworkCore8
Thinktecture.Runtime.Extensions.EntityFrameworkCore9
Thinktecture.Runtime.Extensions.Json
Thinktecture.Runtime.Extensions.Newtonsoft.Json
Thinktecture.Runtime.Extensions.MessagePack
Thinktecture.Runtime.Extensions.AspNetCore
Thinktecture.Runtime.Extensions.Swashbuckle

This library provides some interfaces, classes, Roslyn Source Generators, Roslyn Analyzers and Roslyn CodeFixes for implementation of Smart Enums, Value Objects and Discriminated Unions.

Documentation

See wiki for more documentation.

Requirements

  • C# 11 (or higher) for generated code
  • SDK 8.0.400 (or higher) for building projects

Migrations

Ideas and real-world use cases

Smart Enums:

Value objects:

Discriminated Unions:

Smart Enums

Smart Enums provide a powerful alternative to traditional C# enums, offering type-safety, extensibility, and rich behavior. Unlike regular C# enums which are limited to numeric values and lack extensibility, Smart Enums can:

  • Use any type as the underlying type (e.g., strings, integers) or none at all
  • Include additional fields, properties and behavior
  • Use polymorphism to define custom behavior for each value
  • Prevent creation of invalid values
  • Integrate seamlessly with JSON serializers, MessagePack, Entity Framework Core, ASP.NET Core and Swashbuckle (OpenAPI)

Install: Install-Package Thinktecture.Runtime.Extensions

Documentation: Smart Enums

Some of the Key Features are:

  • Choice between always-valid and maybe-valid Smart Enum
  • Reflection-free iteration over all items
  • Fast lookup/conversion from underlying type to Smart Enum and vice versa
  • Allows custom properties and methods
  • Exhaustive pattern matching with Switch/Map methods
  • Provides appropriate constructor, based on the specified properties/fields
  • Proper implementation of Equals, GetHashCode, ToString and equality operators
  • Provides implementation of IComparable, IComparable<T>, IFormattable, IParsable<T> and comparison operators <, <=, >, >= (if applicable to the underlying type)
  • Custom comparer and equality comparer

Roslyn Analyzers and CodeFixes help the developers to implement the Smart Enums correctly

Provides support for:

  • JSON (System.Text.Json and Newtonsoft)
  • Minimal Api Parameter Binding and ASP.NET Core Model Binding
  • Entity Framework Core
  • MessagePack

Definition of a Smart Enum with custom properties and methods.

[SmartEnum<string>]
public partial class ShippingMethod
{
   public static readonly ShippingMethod Standard = new(
      "STANDARD",
      basePrice: 5.99m,
      weightMultiplier: 0.5m,
      estimatedDays: 5,
      requiresSignature: false);

   public static readonly ShippingMethod Express = new(
      "EXPRESS",
      basePrice: 15.99m,
      weightMultiplier: 0.75m,
      estimatedDays: 2,
      requiresSignature: true);

   public static readonly ShippingMethod NextDay = new(
      "NEXT_DAY",
      basePrice: 29.99m,
      weightMultiplier: 1.0m,
      estimatedDays: 1,
      requiresSignature: true);

   private readonly decimal _basePrice;
   private readonly decimal _weightMultiplier;
   private readonly int _estimatedDays;

   public bool RequiresSignature { get; }

   public decimal CalculatePrice(decimal orderWeight)
   {
      return _basePrice + (orderWeight * _weightMultiplier);
   }

   public DateTime GetEstimatedDeliveryDate()
   {
      return DateTime.Today.AddDays(_estimatedDays);
   }
}

Behind the scenes a Roslyn Source Generator generates additional code. Some of the features that are now available are ...

Basic Operations

[SmartEnum<string>]
public partial class ProductType
{
    // The source generator creates a private constructor
    public static readonly ProductType Groceries = new("Groceries");
}

// Enumeration over all defined items
IReadOnlyList<ProductType> allTypes = ProductType.Items;

// Value retrieval
ProductType productType = ProductType.Get("Groceries");        // Get by key (throws if not found)
ProductType productType = (ProductType)"Groceries";            // Same as above but by using a cast
bool found = ProductType.TryGet("Groceries", out var productType);  // Safe retrieval (returns false if not found)

// Validation with detailed error information
ValidationError? error = ProductType.Validate("Groceries", null, out ProductType? productType);

// IParsable<T> (useful for Minimal APIs)
bool parsed = ProductType.TryParse("Groceries", null, out ProductType? parsedType);

// IFormattable (e.g. for numeric keys)
string formatted = ProductGroup.Fruits.ToString("000", CultureInfo.InvariantCulture);  // "001"

// IComparable
int comparison = ProductGroup.Fruits.CompareTo(ProductGroup.Vegetables);
bool isGreater = ProductGroup.Fruits > ProductGroup.Vegetables;  // Comparison operators

Type Conversion and Equality

// Implicit conversion to key type
string key = ProductType.Groceries;  // Returns "Groceries"

// Equality comparison
bool equal = ProductType.Groceries.Equals(ProductType.Groceries);
bool equal = ProductType.Groceries == ProductType.Groceries;  // Operator overloading
bool notEqual = ProductType.Groceries != ProductType.Housewares;

// Methods inherited from Object
int hashCode = ProductType.Groceries.GetHashCode();
string key = ProductType.Groceries.ToString();  // Returns "Groceries"

// TypeConverter
var converter = TypeDescriptor.GetConverter(typeof(ProductType));
string? keyStr = (string?)converter.ConvertTo(ProductType.Groceries, typeof(string));
ProductType? converted = (ProductType?)converter.ConvertFrom("Groceries");

Pattern Matching with Switch/Map

All Switch/Map methods are exhaustive by default ensuring all cases are handled correctly.

ProductType productType = ProductType.Groceries;

// Execute different actions based on the enum value (void return)
productType.Switch(
    groceries: () => Console.WriteLine("Processing groceries order"),
    housewares: () => Console.WriteLine("Processing housewares order")
);

// Transform enum values into different types
string department = productType.Switch(
    groceries: () => "Food and Beverages",
    housewares: () => "Home and Kitchen"
);

// Direct mapping to values - clean and concise
decimal discount = productType.Map(
    groceries: 0.05m,    // 5% off groceries
    housewares: 0.10m    // 10% off housewares
);

For optimal performance Smart Enums provide overloads that prevent closures.

ILogger logger = ...;

// Prevent closures by passing the parameter as first method argument
productType.Switch(logger,
    groceries: static l => l.LogInformation("Processing groceries order"),
    housewares: static l => l.LogInformation("Processing housewares order")
);

// Use a tuple to pass multiple values
var context = (Logger: logger, OrderId: "123");

productType.Switch(context,
    groceries: static ctx => ctx.Logger.LogInformation("Processing groceries order {OrderId}", ctx.OrderId),
    housewares: static ctx => ctx.Logger.LogInformation("Processing housewares order {OrderId}", ctx.OrderId)
);

Value Objects

Install: Install-Package Thinktecture.Runtime.Extensions

Documentation: Value Objects

Value objects help solve several common problems in software development:

  1. Type Safety: Prevent mixing up different concepts that share the same primitive type

    // Problem: Easy to accidentally swap parameters
    void ProcessOrder(int customerId, int orderId) { ... }
    ProcessOrder(orderId, customerId); // Compiles but wrong!
    
    // Solution: Value objects make it type-safe
    [ValueObject<int>]
    public partial struct CustomerId { }
    
    [ValueObject<int>]
    public partial struct OrderId { }
    
    void ProcessOrder(CustomerId customerId, OrderId orderId) { ... }
    ProcessOrder(orderId, customerId); // Won't compile!
    
  2. Built-in Validation: Ensure data consistency at creation time

    [ValueObject<decimal>]
    public partial struct Amount
    {
        static partial void ValidateFactoryArguments(ref ValidationError? validationError, ref decimal value)
        {
            if (value < 0)
            {
                validationError = new ValidationError("Amount cannot be negative");
                return;
            }
            // Normalize to two decimal places
            value = Math.Round(value, 2);
        }
    }
    
    var amount = Amount.Create(100.50m);     // Success: 100.50
    var invalid = Amount.Create(-50m);       // Throws ValidationException
    
  3. Immutability: Prevent accidental modifications and ensure thread safety

  4. Complex Value Objects: Encapsulate multiple related values with validation

    [ComplexValueObject]
    public partial class DateRange
    {
        public DateOnly Start { get; }
        public DateOnly End { get; }
    
        static partial void ValidateFactoryArguments(
            ref ValidationError? validationError,
            ref DateOnly start,
            ref DateOnly end)
        {
            if (end < start)
            {
                validationError = new ValidationError(
                    $"End date '{end}' cannot be before start date '{start}'");
                return;
            }
    
            // Ensure dates are not in the past
            var today = DateOnly.FromDateTime(DateTime.Today);
            if (start < today)
            {
                validationError = new ValidationError("Start date cannot be in the past");
                return;
            }
        }
    
        public int DurationInDays => End.DayNumber - Start.DayNumber + 1;
    
        public bool Contains(DateOnly date) => date >= Start && date <= End;
    }
    
    // Usage
    var range = DateRange.Create(
        start: DateOnly.FromDateTime(DateTime.Today),
        end: DateOnly.FromDateTime(DateTime.Today.AddDays(7))
    );
    
    Console.WriteLine(range.DurationInDays);    // 8
    Console.WriteLine(range.Contains(range.Start));  // true
    

Key Features:

  • Two types of value objects:
    • Simple value objects (wrapper around a single value with validation)
    • Complex value objects (multiple properties representing a single concept)
  • Comprehensive validation support with descriptive error messages
  • Framework integration:
    • JSON serialization (System.Text.Json and Newtonsoft.Json)
    • Entity Framework Core support
    • ASP.NET Core Model Binding
    • Swashbuckle (OpenAPI)
    • MessagePack serialization
  • Rich feature set:
    • Type conversion and comparison operators
    • Custom equality comparison
    • Proper implementation of standard interfaces (IComparable, IFormattable, etc.)
    • Configurable null and empty string handling
  • Development support:
    • Roslyn Analyzers and CodeFixes for correct implementation
    • Logging for debugging and insights

For more examples and detailed documentation, see the wiki.

Discriminated Unions

Install: Install-Package Thinktecture.Runtime.Extensions

Documentation: Discriminated Unions

Discriminated unions are a powerful feature that allows a type to hold a value that could be one of several different types. They provide type safety, exhaustive pattern matching, and elegant handling of complex domain scenarios. Key benefits include:

  • Type-safe representation of values that can be one of several types
  • Exhaustive pattern matching ensuring all cases are handled
  • Elegant modeling of domain concepts with multiple states
  • Clean handling of success/failure scenarios without exceptions

The library provides two types of unions to suit different needs:

Ad hoc unions

Perfect for simple scenarios where you need to combine a few types quickly. Features:

  • Type-safe combination of up to 5 different types
  • Implicit conversions and type checking
  • Exhaustive pattern matching with Switch/Map methods
  • Built-in equality comparison
  • Support for class, struct, or ref struct implementations
// Quick combination of types
[Union<string, int>]
public partial class TextOrNumber;

// Create and use the union
TextOrNumber value = "Hello";           // Implicit conversion
TextOrNumber number = 42;               // Works with any defined type

// Type-safe access
if (value.IsString)
{
    string text = value.AsString;       // Type-safe access
    Console.WriteLine(text);
}

// Exhaustive pattern matching
var result = value.Switch(
    @string: text => $"Text: {text}",
    int32: num => $"Number: {num}"
);

// Custom property names for clarity
[Union<string, int>(T1Name = "Text", T2Name = "Number")]
public partial class BetterNamed;
// Now use .IsText, .IsNumber, .AsText, .AsNumber

Regular unions

Ideal for modeling domain concepts and complex hierarchies. Features:

  • Inheritance-based approach for complex scenarios
  • Support for both classes and records
  • Integration with value objects
  • Generic type support
  • Exhaustive pattern matching

Perfect for modeling domain concepts:

// Model domain concepts clearly
[Union]
public partial record OrderStatus
{
    public record Pending : OrderStatus;
    public record Processing(DateTime StartedAt) : OrderStatus;
    public record Completed(DateTime CompletedAt, string TrackingNumber) : OrderStatus;
    public record Cancelled(string Reason) : OrderStatus;
}

// Generic result type for error handling
[Union]
public partial record Result<T>
{
    public record Success(T Value) : Result<T>;
    public record Failure(string Error) : Result<T>;

    // Implicit conversions from T and string are implemented automatically 
}

// Usage
Result<int> result = await GetDataAsync();

var message = result.Switch(
    success: s => $"Got value: {s.Value}",
    failure: f => $"Error: {f.Error}"
);
Product Compatible and additional computed target framework versions.
.NET 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 was computed.  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 was computed.  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 was computed.  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.

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
10.0.0-beta02 2,297 11/30/2025
10.0.0-beta01 894 11/19/2025
9.7.1 2,131 11/20/2025
9.7.0 1,085 11/12/2025
9.7.0-rc1 272 11/11/2025
9.7.0-beta08 190 11/9/2025
9.7.0-beta07 179 10/27/2025
9.7.0-beta06 165 10/27/2025
9.7.0-beta05 172 10/27/2025
9.7.0-beta04 162 10/21/2025
9.7.0-beta03 166 10/19/2025
9.7.0-beta02 162 10/16/2025
9.7.0-beta01 163 10/15/2025
9.6.6 906 11/8/2025
9.6.5 746 11/3/2025
9.6.4 567 10/21/2025
9.6.3 319 10/19/2025
9.6.2 2,328 9/30/2025
9.6.1 189 9/27/2025
9.6.0 447 9/18/2025
9.5.3 388 9/16/2025
9.5.2 1,066 8/26/2025
9.5.1 257 8/24/2025
9.5.0 2,520 8/5/2025
9.4.0 2,331 6/23/2025
9.4.0-beta02 195 6/19/2025
9.4.0-beta01 181 6/18/2025
9.3.1 671 6/17/2025
9.3.0 187 6/16/2025
9.2.0 332 6/12/2025
9.1.0 205 5/23/2025
9.0.1 244 5/21/2025
9.0.0 1,531 5/11/2025
8.10.1 183 6/17/2025
8.10.0 1,730 5/23/2025
8.9.2 209 5/21/2025
8.9.1 618 5/8/2025
8.9.0 278 5/7/2025
8.8.1 231 5/4/2025
8.8.0 599 4/27/2025
8.8.0-beta02 211 4/22/2025
8.8.0-beta01 464 4/21/2025
8.7.0 332 4/16/2025
8.7.0-beta03 262 4/15/2025
8.7.0-beta02 222 4/10/2025
8.7.0-beta01 236 4/9/2025
8.6.1 1,425 4/2/2025
8.6.0 718 3/29/2025
8.5.5 339 3/27/2025
8.5.4 836 3/24/2025
8.5.3 1,204 3/13/2025
8.5.2 222 3/12/2025
8.5.1 621 3/8/2025
8.5.0 474 3/4/2025
8.5.0-beta01 160 2/26/2025
8.4.1 419 2/24/2025
8.4.0 173 2/23/2025
8.3.0 1,084 2/16/2025
8.2.0 445 1/23/2025
8.1.0 183 1/17/2025
8.0.2 2,666 12/18/2024
8.0.1 284 12/11/2024
8.0.0 222 12/9/2024
8.0.0-beta13 143 11/21/2024
8.0.0-beta12 134 11/19/2024
8.0.0-beta11 146 11/15/2024
8.0.0-beta10 151 10/25/2024
8.0.0-beta09 205 10/23/2024
8.0.0-beta08 151 10/23/2024
8.0.0-beta07 165 9/27/2024
8.0.0-beta06 180 9/19/2024
8.0.0-beta05 181 9/9/2024
8.0.0-beta04 179 9/9/2024
8.0.0-beta03 154 9/8/2024
8.0.0-beta02 147 9/3/2024
8.0.0-beta01 174 8/13/2024
7.6.1 337 11/21/2024
7.6.0 18,195 11/13/2024
7.5.3 2,134 10/23/2024
7.5.2 5,743 9/27/2024
7.5.1 467 9/5/2024
7.5.0 1,875 7/9/2024
7.4.0 867 6/13/2024
7.3.0 561 4/18/2024
7.2.1 177 4/14/2024
7.2.0 22,723 1/31/2024
7.1.0 3,912 12/11/2023
7.0.0 271 12/10/2023
7.0.0-beta10 184 11/30/2023
7.0.0-beta09 208 11/26/2023
7.0.0-beta08 221 11/19/2023
7.0.0-beta07 206 11/17/2023
7.0.0-beta06 181 11/14/2023
7.0.0-beta05 205 11/14/2023
7.0.0-beta04 121 11/7/2023
7.0.0-beta03 184 10/19/2023
7.0.0-beta02 232 10/10/2023
7.0.0-beta01 201 10/8/2023
6.6.0 267 11/30/2023
6.5.2 295 11/17/2023
6.5.1 242 11/11/2023
6.5.0 240 11/11/2023
6.5.0-beta03 192 11/6/2023
6.5.0-beta02 206 11/5/2023
6.5.0-beta01 188 11/5/2023
6.4.1 210 11/9/2023
6.4.0 2,142 9/3/2023
6.3.0 370 8/31/2023
6.2.0 1,869 4/2/2023
6.1.0 375 3/22/2023
6.1.0-beta02 249 3/19/2023
6.1.0-beta01 253 3/19/2023
6.0.1 393 3/12/2023
6.0.0 389 3/9/2023
6.0.0-beta03 243 3/6/2023
6.0.0-beta02 269 3/5/2023
6.0.0-beta01 261 3/2/2023
5.2.0 1,051 2/6/2023
5.1.0 32,529 11/25/2022
5.0.1 2,483 10/6/2022
5.0.0 562 9/28/2022
5.0.0-beta03 287 9/7/2022
5.0.0-beta02 271 9/4/2022
5.0.0-beta01 284 9/4/2022
4.4.0-beta02 286 8/31/2022
4.4.0-beta01 278 8/30/2022
4.3.3 658 8/17/2022
4.3.3-beta01 294 8/17/2022
4.3.2 2,864 6/15/2022
4.3.2-beta02 315 6/15/2022
4.3.2-beta01 329 5/18/2022
4.3.1 10,620 5/17/2022
4.3.0 648 5/16/2022
4.2.0 1,738 4/24/2022
4.2.0-beta01 355 4/23/2022
4.1.3 629 4/13/2022
4.1.2 2,360 2/10/2022
4.1.1 655 2/6/2022
4.1.0 639 2/5/2022
4.0.1 36,893 1/16/2022
4.0.0 636 1/14/2022
4.0.0-beta03 387 12/17/2021
4.0.0-beta02 331 12/14/2021
4.0.0-beta01 354 12/13/2021
3.1.0 2,908 9/25/2021
3.0.0 841 7/10/2021
3.0.0-beta10 496 7/10/2021
3.0.0-beta09 401 4/1/2021
3.0.0-beta08 497 3/15/2021
3.0.0-beta07 425 3/14/2021
3.0.0-beta06 477 3/11/2021
3.0.0-beta05 908 2/12/2021
3.0.0-beta04 388 2/7/2021
3.0.0-beta03 431 1/30/2021
3.0.0-beta02 601 1/11/2021
3.0.0-beta01 492 1/10/2021
2.1.0-beta02 478 11/6/2020
2.0.1 5,835 10/8/2019
2.0.0 779 10/8/2019
2.0.0-beta02 520 10/8/2019
2.0.0-beta01 620 9/30/2019