Class ResultRequiresExplicitHttpMappingConverter
- Namespace
- Trellis
- Assembly
- Trellis.Core.dll
Default JsonConverterFactory attached to Result<TValue>,
IResult, and IResult<TValue> that fails fast when a result is
JSON-serialized without going through Trellis's HTTP-mapping path
(Result<T>.ToHttpResponse() in Trellis.Asp) or an explicit consumer
converter. Without this converter, direct serialization could only emit public status/error
metadata such as {"IsSuccess": bool, "IsFailure": bool, "Error": …} — a struct dump
with no success value, not a stable wire shape, and not the HTTP problem-details / status-code
mapping consumers expect from Trellis.
public sealed class ResultRequiresExplicitHttpMappingConverter : JsonConverterFactory
- Inheritance
-
ResultRequiresExplicitHttpMappingConverter
- Inherited Members
- Extension Methods
Remarks
The factory matches Result<TValue> AND the public result interfaces
(IResult, IResult<TValue>). The attribute lives on both the
struct and the interfaces because [JsonConverter] resolution is by static declared
type — a controller signature like Task<IResult<int>> GetAsync() picks
up the interface attribute, not the struct attribute (the runtime Result<int>
is never consulted for converter resolution).
The intended controller / minimal-API pattern is to call .ToHttpResponse() on the
result, which returns a Microsoft.AspNetCore.Http.IResult that writes the response
body itself (so the Result<TValue> struct is never serialized by STJ).
Returning a raw Result<TValue> from a controller action — and letting MVC's
default object formatter pick it up — used to silently produce the struct-dump JSON shape
with no HTTP status-code mapping for Error cases. With this converter
attached, that misuse throws NotSupportedException on the first request with
an actionable message naming .ToHttpResponse().
AOT-safe. The factory throws directly from CreateConverter(Type, JsonSerializerOptions) rather
than instantiating a typed throwing converter via MakeGenericType /
Activator.CreateInstance. This means no reflection-dependent paths run in Native
AOT — consumers get the actionable Trellis message instead of an AOT-generated "native
code not available" error before the message can fire.
Consumers who legitimately want to serialize a result for logging, IPC, or storage can register a converter (or another JsonConverterFactory) in Converters — option-registered converters take precedence over the type's JsonConverterAttribute. The override must match the declared static type of the value being serialized:
- Value declared as Result<TValue> → register
JsonConverter<Result<T>>. - Value declared as IResult<TValue> → register
JsonConverter<IResult<T>>. - Value declared as IResult → register
JsonConverter<IResult>. - Mixed shapes → register a JsonConverterFactory whose
CanConvertreturns true for every shape the consumer needs.
STJ resolves [JsonConverter] against the static declared type, not the runtime
type, so a JsonConverter<Result<T>> alone does not cover
IResult<T>-declared properties / return signatures.
Methods
CanConvert(Type)
Returns true for Result<TValue> and IResult<TValue> shapes, and for the non-generic IResult interface.
public override bool CanConvert(Type typeToConvert)
Parameters
typeToConvertType
Returns
CreateConverter(Type, JsonSerializerOptions)
Always throws NotSupportedException with the actionable Trellis message.
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
Parameters
typeToConvertTypeoptionsJsonSerializerOptions
Returns
Remarks
Throwing directly from CreateConverter(Type, JsonSerializerOptions) sidesteps the typed-converter
instantiation entirely. The previous design used MakeGenericType +
Activator.CreateInstance to materialise a typed JsonConverter<Result<T>>
that would throw on its first Read / Write call — but those reflection
paths are AOT-unsafe (IL2055 / IL3050) and could fail with a "native code not available"
error before the Trellis message had a chance to surface. Throwing here keeps the
converter AOT-safe and ensures consumers see the actionable Trellis message in every
runtime configuration.