OptionalValues 0.1.4
dotnet add package OptionalValues --version 0.1.4
NuGet\Install-Package OptionalValues -Version 0.1.4
<PackageReference Include="OptionalValues" Version="0.1.4" />
paket add OptionalValues --version 0.1.4
#r "nuget: OptionalValues, 0.1.4"
// Install OptionalValues as a Cake Addin #addin nuget:?package=OptionalValues&version=0.1.4 // Install OptionalValues as a Cake Tool #tool nuget:?package=OptionalValues&version=0.1.4
OptionalValues
A .NET library that provides an OptionalValue<T>
type, representing a value that may or may not be specified, with comprehensive support for JSON serialization.
Overview
The OptionalValue<T>
struct is designed to represent a value that can be in one of three states:
- Unspecified: The value has not been specified.
- Specified with a non-null value: The value has been specified and is not
null
. - Specified with a
null
value: The value has been specified and isnull
.
This differs from Nullable<T>
, which can only distinguish between the presence or absence of a value, and cannot differentiate between an unspecified value and a specified null
value.
Installation
Install the package using the .NET CLI:
dotnet add package OptionalValues
For JSON serialization support, configure the JsonSerializerOptions
to include the OptionalValue<T>
converter:
var options = new JsonSerializerOptions()
.AddOptionalValueSupport();
Optionally, install the FluentValidation extensions package:
dotnet add package OptionalValues.FluentValidation
Features
- Distinguish Between Unspecified and Null Values: Clearly differentiate when a value is intentionally
null
versus when it has not been specified at all. - JSON Serialization Support: Includes a custom JSON converter and TypeResolverModifier that correctly handles serialization and deserialization, ensuring unspecified values are omitted from JSON outputs.
- FluentValidation Extensions: Provides extension methods to simplify the validation of
OptionalValue<T>
properties using FluentValidation. - Patch Operation Support: Ideal for API patch operations where fields can be updated to
null
or remain unchanged.
Usage
Creating an OptionalValue
You can create an OptionalValue<T>
in several ways:
Unspecified Value:
var unspecified = new OptionalValue<string>(); // or var unspecified = OptionalValue<string>.Unspecified; // or OptionalValue<string> unspecified = default;
Specified Value:
var specifiedValue = new OptionalValue<string>("Hello, World!"); // or using implicit conversion OptionalValue<string> specifiedValue = "Hello, World!";
Specified Null Value:
var specifiedNull = new OptionalValue<string?>(null); // or using implicit conversion OptionalValue<string?> specifiedNull = null;
Checking If a Value Is Specified
Use the IsSpecified
property to determine if the value has been specified:
if (optionalValue.IsSpecified)
{
Console.WriteLine("Value is specified.");
}
else
{
Console.WriteLine("Value is unspecified.");
}
Accessing the Value
- Value: Gets the value if specified; returns
null
if unspecified. - SpecifiedValue: Gets the specified value; throws
InvalidOperationException
if the value is unspecified. - GetSpecifiedValueOrDefault(): Gets the specified value or the default value of
T
if unspecified. - GetSpecifiedValueOrDefault(T defaultValue): Gets the specified value or the provided default value if unspecified.
var optionalValue = new OptionalValue<string>("Example");
// Using Value
string? value = optionalValue.Value;
// Using SpecifiedValue
string specifiedValue = optionalValue.SpecifiedValue;
// Using GetSpecifiedValueOrDefault
string valueOrDefault = optionalValue.GetSpecifiedValueOrDefault("Default Value");
Implicit Conversions
OptionalValue<T>
supports implicit conversions to and from T
:
// From T to OptionalValue<T>
OptionalValue<int> optionalInt = 42;
// From OptionalValue<T> to T (returns null if unspecified)
int? value = optionalInt;
Equality Comparisons
Equality checks consider both the IsSpecified
property and the Value
:
var value1 = new OptionalValue<string>("Test");
var value2 = new OptionalValue<string>("Test");
var unspecified = new OptionalValue<string>();
bool areEqual = value1 == value2; // True
bool areUnspecifiedEqual = unspecified == new OptionalValue<string>(); // True
JSON Serialization with System.Text.Json
OptionalValue<T>
includes a custom JSON converter and JsonTypeInfoResolver Modifier to handle serialization and deserialization of optional values.
To properly serialize OptionalValue<T>
properties, add it to the JsonSerializerOptions
:
var newOptionsWithSupport = JsonSerializerOptions.Default
.WithOptionalValueSupport();
// or
var options = new JsonSerializerOptions();
options.AddOptionalValueSupport();
Serialization Behavior
- Unspecified Values: Omitted from the JSON output.
- Specified Null Values: Serialized with a
null
value. - Specified Non-Null Values: Serialized with the actual value.
public class Person
{
public OptionalValue<string> FirstName { get; set; }
public OptionalValue<string> LastName { get; set; }
}
// Creating a Person instance
var person = new Person
{
FirstName = "John", // Specified non-null value
LastName = new OptionalValue<string>() // Unspecified
};
// Serializing to JSON
string json = JsonSerializer.Serialize(person);
// Output: {"FirstName":"John"}
Deserialization Behavior
- Missing Properties: Deserialized as unspecified values.
- Properties with
null
: Deserialized as specified with anull
value. - Properties with Values: Deserialized as specified with the given value.
string jsonInput = @"{""FirstName"":""John"",""LastName"":null}";
var person = JsonSerializer.Deserialize<Person>(jsonInput);
bool isFirstNameSpecified = person.FirstName.IsSpecified; // True
string firstName = person.FirstName.SpecifiedValue; // "John"
bool isLastNameSpecified = person.LastName.IsSpecified; // True
string lastName = person.LastName.SpecifiedValue; // null
FluentValidation Extensions
The PACKAGE_NAME.FluentValidation
package provides extension methods to simplify the validation of OptionalValue<T>
properties using FluentValidation.
Installation
Install the package using the .NET CLI:
dotnet add package OptionalValues.FluentValidation
Using OptionalRuleFor
The OptionalRuleFor
extension method allows you to define validation rules for OptionalValue<T>
properties that are only applied when the value is specified.
using FluentValidation;
using OptionalValues.FluentValidation;
public class UpdateUserRequest
{
public OptionalValue<string?> Email { get; set; }
public OptionalValue<int> Age { get; set; }
}
public class UpdateUserRequestValidator : AbstractValidator<UpdateUserRequest>
{
public UpdateUserRequestValidator()
{
this.OptionalRuleFor(x => x.Email, x => x
.NotEmpty()
.EmailAddress());
this.OptionalRuleFor(x => x.Age, x => x
.GreaterThan(18));
}
}
In this example:
- The validation rules for
Email
andAge
are applied only if the correspondingOptionalValue<T>
is specified. - If the value is unspecified, the validation rules are skipped.
How It Works
The OptionalRuleFor
method:
- Takes an expression specifying the
OptionalValue<T>
property. - Accepts a configuration function where you define your validation rules using the standard FluentValidation syntax.
- Internally, it checks if the value is specified (
IsSpecified
) before applying the validation rules.
Example Usage
var validator = new UpdateUserRequestValidator();
// Valid request with specified values
var validRequest = new UpdateUserRequest
{
Email = "user@example.com",
Age = 25
};
var result = validator.Validate(validRequest);
// result.IsValid == true
// Invalid request with specified values
var invalidRequest = new UpdateUserRequest
{
Email = "invalid-email",
Age = 17
};
var resultInvalid = validator.Validate(invalidRequest);
// resultInvalid.IsValid == false
// Errors for Email and Age
// Request with unspecified values
var unspecifiedRequest = new UpdateUserRequest
{
Email = default,
Age = default
};
var resultUnspecified = validator.Validate(unspecifiedRequest);
// resultUnspecified.IsValid == true
// Validation rules are skipped for unspecified values
Use Cases
API Patch Operations
When updating resources via API endpoints, it's crucial to distinguish between fields that should be updated to null
and fields that should remain unchanged.
public class UpdateUserRequest
{
public OptionalValue<string?> Email { get; set; }
public OptionalValue<string?> PhoneNumber { get; set; }
}
[HttpPatch("{id}")]
public IActionResult UpdateUser(int id, UpdateUserRequest request)
{
if (request.Email.IsSpecified)
{
// Update email to request.Email.SpecifiedValue
}
if (request.PhoneNumber.IsSpecified)
{
// Update phone number to request.PhoneNumber.SpecifiedValue
}
// Unspecified fields remain unchanged
return Ok();
}
Limitations
- DataAnnotations: The
OptionalValue<T>
type does not support DataAnnotations validation attributes because they are tied to specific .NET Types (e.g. string).- "Workaround": Use the FluentValidation extensions to define validation rules for
OptionalValue<T>
properties.
- "Workaround": Use the FluentValidation extensions to define validation rules for
Contributing
Contributions are welcome! Please feel free to submit issues or pull requests on the GitHub repository.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | 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. |
-
net8.0
- No dependencies.
-
net9.0
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on OptionalValues:
Package | Downloads |
---|---|
OptionalValues.FluentValidation
FluentValidation extensions to for OptionalValues. |
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
0.1.4 | 38 | 11/21/2024 |