FadiPhor.Result.Serialization.Json 0.0.14-preview

This is a prerelease version of FadiPhor.Result.Serialization.Json.
dotnet add package FadiPhor.Result.Serialization.Json --version 0.0.14-preview
                    
NuGet\Install-Package FadiPhor.Result.Serialization.Json -Version 0.0.14-preview
                    
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="FadiPhor.Result.Serialization.Json" Version="0.0.14-preview" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FadiPhor.Result.Serialization.Json" Version="0.0.14-preview" />
                    
Directory.Packages.props
<PackageReference Include="FadiPhor.Result.Serialization.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 FadiPhor.Result.Serialization.Json --version 0.0.14-preview
                    
#r "nuget: FadiPhor.Result.Serialization.Json, 0.0.14-preview"
                    
#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 FadiPhor.Result.Serialization.Json@0.0.14-preview
                    
#: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=FadiPhor.Result.Serialization.Json&version=0.0.14-preview&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=FadiPhor.Result.Serialization.Json&version=0.0.14-preview&prerelease
                    
Install as a Cake Tool

FadiPhor.Result.Serialization.Json

System.Text.Json serialization for Result<T> with polymorphic Error support, plus a JSON envelope transport layer for request/response protocols.


Project Structure

FadiPhor.Result.Serialization.Json
├── Converters/          Result<T> JSON converters
├── Errors/              Polymorphic Error resolution
├── Configuration/       JsonSerializerOptions setup, FadiPhorJsonOptions, DI extensions
└── Transport/           JsonEnvelope, IJsonEnvelopeSerializer, request type registry
Namespace Purpose
FadiPhor.Result.Serialization.Json DI entry points (AddResultSerialization, AddFadiPhorResultProtocol)
FadiPhor.Result.Serialization.Json.Configuration AddResultSerialization, FadiPhorJsonOptions
FadiPhor.Result.Serialization.Json.Errors IErrorPolymorphicResolver
FadiPhor.Result.Serialization.Json.Transport JsonEnvelope, IJsonEnvelopeSerializer

Registration

Standalone (no DI)

using FadiPhor.Result.Serialization.Json.Configuration;

var options = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};

// No custom errors — core types (ValidationFailure) are registered automatically.
options.AddResultSerialization();

// With custom error types:
options.AddResultSerialization(new MyErrorResolver());

// Multiple resolvers:
options.AddResultSerialization(new DomainErrorResolver(), new AuthErrorResolver());

If a TypeInfoResolver is already set on the options, AddResultSerialization preserves it and combines both.

DI registration (serialization only)

For applications using DI that need Result<T> serialization without the full transport protocol (no envelope, no request registry). Ideal for Minimal APIs, Blazor, MAUI, or any app that serializes Result<T> over HTTP, SignalR, or other transports directly:

using FadiPhor.Result.Serialization.Json;

// Built-in error types only (ValidationFailure, NotFoundError, etc.)
services.AddResultSerialization();

// With custom error resolver auto-discovery from assemblies:
services.AddResultSerialization(
    assemblies: [typeof(MyErrorResolver).Assembly]);

This registers:

  • FadiPhorJsonOptions — protocol-owned JsonSerializerOptions with Result converters and error polymorphism (does not collide with the consumer's own JsonSerializerOptions).
  • Error polymorphic resolvers — auto-discovers IErrorPolymorphicResolver implementations from the scanned assemblies (if provided).

Resolve FadiPhorJsonOptions from DI whenever you need to serialize or deserialize Result<T>:

var options = provider.GetRequiredService<FadiPhorJsonOptions>().SerializerOptions;
var json = JsonSerializer.Serialize(result, options);

DI-based protocol registration

For applications using the full transport protocol (envelope serialization, request type scanning, and polymorphic error resolution):

using FadiPhor.Result.Serialization.Json;

services.AddFadiPhorResultProtocol(
    assemblies: modules.SelectMany(m => m.ContractAssemblies),
    requestMarkerType: typeof(IRequest<>));

This single call registers:

  • FadiPhorJsonOptions — protocol-owned JsonSerializerOptions with Result converters and error polymorphism (does not collide with the consumer's own JsonSerializerOptions).
  • IJsonEnvelopeSerializer — symmetric Serialize / Deserialize for JsonEnvelope payloads.
  • Request type registry — scans assemblies for types implementing the consumer-provided marker interface.
  • Error polymorphic resolvers — auto-discovers IErrorPolymorphicResolver implementations from the scanned assemblies.

The requestMarkerType can be any interface — generic (e.g. typeof(IRequest<>)) or non-generic (e.g. typeof(IRequest)). The library does not depend on MediatR or any specific framework.


JSON Contract

Success

{
  "kind": "Success",
  "value": { "id": 1, "name": "Alice" }
}

Property order is fixed: kind first, then value.

Failure (plain error)

{
  "kind": "Failure",
  "error": {
    "$type": "NotFoundError",
    "code": "not_found",
    "message": "User 42 was not found.",
    "httpStatusCode": 404
  }
}

Property order: kind first, then error. The $type discriminator identifies the Error subtype.

Failure (ValidationFailure)

{
  "kind": "Failure",
  "error": {
    "$type": "ValidationFailure",
    "code": "validation.failed",
    "message": "Validation failed.",
    "httpStatusCode": 422,
    "issues": [
      {
        "identifier": "Email",
        "message": "Email is required",
        "severity": 0
      },
      {
        "identifier": "Age",
        "message": "Must be 18 or older",
        "severity": 0
      }
    ]
  }
}

ValidationFailure is registered automatically. No resolver needed.

Success with Unit

{
  "kind": "Success",
  "value": {}
}

Polymorphic Error Resolution

Errors are serialized through System.Text.Json polymorphism. The discriminator property is $type. Each Error subtype must be registered with a resolver.

All resolvers are declarative — they return the derived types they contribute via GetDerivedTypes(). AddResultSerialization aggregates every resolver's types into a single JsonPolymorphismOptions instance. This means:

  • Resolver registration order does not matter.
  • Resolvers cannot overwrite each other.
  • Adding a new resolver is safe — just implement GetDerivedTypes().

Default behavior

AddResultSerialization automatically registers the following core error types. You do not need to include them in your resolver:

  • ValidationFailure
  • NotFoundError
  • UnauthenticatedError
  • UnauthorizedError
  • ConflictError
  • UnexpectedError

Custom errors

Define your error types and implement IErrorPolymorphicResolver:

using FadiPhor.Result.Serialization.Json.Errors;

public record InsufficientFundsError(decimal Required, decimal Available) : Error("insufficient_funds")
{
    public override int HttpStatusCode => 402;
    public override string? Message => $"Required {Required:C} but only {Available:C} available";
}

public record RateLimitedError(int RetryAfterSeconds) : Error("rate_limited")
{
    public override int HttpStatusCode => 429;
    public override string? Message => $"Rate limited. Retry after {RetryAfterSeconds}s";
}

public class MyErrorResolver : IErrorPolymorphicResolver
{
    public IEnumerable<JsonDerivedType> GetDerivedTypes()
    {
        yield return new JsonDerivedType(typeof(InsufficientFundsError), nameof(InsufficientFundsError));
        yield return new JsonDerivedType(typeof(RateLimitedError), nameof(RateLimitedError));
    }
}

Register the resolver:

var options = new JsonSerializerOptions()
    .AddResultSerialization(new MyErrorResolver());

All derived types from all resolvers — including the built-in core types (ValidationFailure, NotFoundError, UnauthenticatedError, UnauthorizedError, ConflictError, UnexpectedError) — are merged into a single configuration automatically.


Round-Trip Example

var options = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase
}.AddResultSerialization(new MyErrorResolver());

// Serialize
Result<int> result = ResultFactory.Success(42);
var json = JsonSerializer.Serialize(result, options);
// {"kind":"Success","value":42}

// Deserialize
var deserialized = JsonSerializer.Deserialize<Result<int>>(json, options);
// deserialized is Success<int> { Value = 42 }

// Failure round-trip
Result<int> failure = new NotFoundError("item/7 was not found");
var failureJson = JsonSerializer.Serialize(failure, options);
// {"kind":"Failure","error":{"$type":"NotFoundError","code":"not_found","message":"item/7 was not found","httpStatusCode":404}}

var restored = JsonSerializer.Deserialize<Result<int>>(failureJson, options);
// restored is Failure<int> { Error = NotFoundError { Message = "item/7 was not found" } }

Envelope Transport

The transport layer provides symmetric serialization of request objects into JsonEnvelope payloads for JSON RPC-style protocols.

JsonEnvelope

{
  "type": "MyApp.Contracts.CreateUserRequest",
  "body": { "name": "Alice" }
}

The type property uses the full CLR type name (Type.FullName) to ensure uniqueness across namespaces.

IJsonEnvelopeSerializer

Resolve from DI after calling AddFadiPhorResultProtocol:

using FadiPhor.Result.Serialization.Json.Transport;

// Client — wrap a request into an envelope
JsonEnvelope envelope = serializer.Serialize(new CreateUserRequest("Alice"));

// Server — unwrap an envelope into the concrete request type
object request = serializer.Deserialize(envelope);

FadiPhorJsonOptions

Access the protocol-owned JsonSerializerOptions for manual JSON handling:

using FadiPhor.Result.Serialization.Json.Configuration;

var options = provider.GetRequiredService<FadiPhorJsonOptions>().SerializerOptions;
var json = JsonSerializer.Serialize(myObject, options);

The library registers its own JsonSerializerOptions instance wrapped in FadiPhorJsonOptions, so it never collides with options the consumer may register independently.


Structural Notes

  • The JSON shape reflects the current binary union (Success / Failure). If new union states are added to the core, the converter must be updated to handle them.
  • Changes to property names (kind, value, error, $type) or structure are contract-breaking.
  • Deserialization is strict: missing kind, unknown kind values, or missing value/error properties throw JsonException.
Product Compatible and additional computed target framework versions.
.NET 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.

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.0.14-preview 88 4/13/2026
0.0.13-preview 73 4/12/2026
0.0.12-preview 68 4/12/2026
0.0.11-preview 94 2/25/2026
0.0.10-preview 75 2/25/2026
0.0.9-preview 77 2/23/2026
0.0.8-preview 74 2/22/2026
0.0.7-preview 79 2/21/2026
0.0.6-preview 75 2/21/2026
0.0.5-preview 79 2/17/2026