Elarion
JSON-RPC

Serialization

Compose JSON-RPC envelope, host, and per-module JSON contexts into one runtime serializer for AOT-friendly serialization.

Elarion uses System.Text.Json source-generated metadata so request/response serialization is AOT-friendly and follows module boundaries. Each module contributes its own JsonSerializerContext; the host composes them into the runtime serializer.

Per-module JSON contexts

Each module declares a source-generated context for its request/response and nested DTO types:

using System.Text.Json.Serialization;

namespace MyApp.Application.Modules.Clients;

[JsonSerializable(typeof(GetClient.Query))]
[JsonSerializable(typeof(GetClient.Response))]
[JsonSerializable(typeof(CreateClient.Command))]
[JsonSerializable(typeof(CreateClient.Response))]
public sealed partial class ClientsJsonContext : JsonSerializerContext;

The module exposes it through GetJsonTypeInfoResolver():

public static IJsonTypeInfoResolver GetJsonTypeInfoResolver() => ClientsJsonContext.Default;

Composing the runtime serializer

The host combines the JSON-RPC envelope metadata, host metadata, every module resolver, and a fallback resolver:

var moduleResolvers = ModuleBootstrapper.GetAllJsonTypeInfoResolvers(configuration);

var resolvers = new IJsonTypeInfoResolver[2 + moduleResolvers.Length + 1];
resolvers[0] = JsonRpcJsonContext.Default;
resolvers[1] = HostJsonContext.Default;
Array.Copy(moduleResolvers, 0, resolvers, 2, moduleResolvers.Length);
resolvers[^1] = new DefaultJsonTypeInfoResolver();

var options = new JsonSerializerOptions {
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    PropertyNameCaseInsensitive = true,
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
    TypeInfoResolver = JsonTypeInfoResolver.Combine(resolvers),
};

The same JsonSerializerOptions instance must be used by the runtime dispatcher and by schema export, so the exported schema matches what the server actually serializes.

AOT considerations

For AOT-sensitive apps, register every command, query, response, and nested DTO type explicitly with [JsonSerializable(...)]. The trailing DefaultJsonTypeInfoResolver() is a reflection-based fallback — convenient in development, but do not rely on it for production-only types under AOT.

The project also sets JsonSerializerIsReflectionEnabledByDefault=false, which surfaces missing type metadata early rather than silently falling back to reflection.

On this page