Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/Polecat/Serialization/ISerializer.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
using System.Data.Common;
using System.Diagnostics.CodeAnalysis;

namespace Polecat.Serialization;

/// <summary>
/// Serialization abstraction for Polecat. Uses System.Text.Json exclusively.
/// </summary>
/// <remarks>
/// The default <see cref="Serializer"/> implementation routes through
/// <c>System.Text.Json.JsonSerializer</c>, which the .NET trimmer cannot
/// statically analyse. AOT-publishing apps should plug in a custom
/// <c>ISerializer</c> implementation backed by an STJ source-generator
/// context (<c>JsonSerializerContext</c>) rather than the reflection-based
/// default.
/// </remarks>
public interface ISerializer
{
/// <summary>
Expand All @@ -20,45 +29,63 @@ public interface ISerializer
/// <summary>
/// Serialize an object to a JSON string.
/// </summary>
[RequiresUnreferencedCode("Default ISerializer uses STJ reflection over document.GetType()'s properties; AOT consumers should supply a source-generator-backed ISerializer impl.")]
[RequiresDynamicCode("Default ISerializer uses STJ reflection which requires runtime code generation.")]
string ToJson(object document);

/// <summary>
/// Deserialize a JSON string to an object of type T.
/// </summary>
[RequiresUnreferencedCode("Default ISerializer uses STJ reflection; AOT consumers should supply a source-generator-backed ISerializer impl.")]
[RequiresDynamicCode("Default ISerializer uses STJ reflection which requires runtime code generation.")]
T FromJson<T>(string json);

/// <summary>
/// Deserialize a JSON string to an object of the specified type.
/// </summary>
[RequiresUnreferencedCode("Default ISerializer uses STJ reflection; AOT consumers should supply a source-generator-backed ISerializer impl.")]
[RequiresDynamicCode("Default ISerializer uses STJ reflection which requires runtime code generation.")]
object FromJson(Type type, string json);

/// <summary>
/// Deserialize from a Stream.
/// </summary>
[RequiresUnreferencedCode("Default ISerializer uses STJ reflection; AOT consumers should supply a source-generator-backed ISerializer impl.")]
[RequiresDynamicCode("Default ISerializer uses STJ reflection which requires runtime code generation.")]
T FromJson<T>(Stream stream);

/// <summary>
/// Deserialize from a Stream to the specified type.
/// </summary>
[RequiresUnreferencedCode("Default ISerializer uses STJ reflection; AOT consumers should supply a source-generator-backed ISerializer impl.")]
[RequiresDynamicCode("Default ISerializer uses STJ reflection which requires runtime code generation.")]
object FromJson(Type type, Stream stream);

/// <summary>
/// Deserialize from a DbDataReader column.
/// </summary>
[RequiresUnreferencedCode("Default ISerializer uses STJ reflection; AOT consumers should supply a source-generator-backed ISerializer impl.")]
[RequiresDynamicCode("Default ISerializer uses STJ reflection which requires runtime code generation.")]
T FromJson<T>(DbDataReader reader, int index);

/// <summary>
/// Deserialize from a DbDataReader column to the specified type.
/// </summary>
[RequiresUnreferencedCode("Default ISerializer uses STJ reflection; AOT consumers should supply a source-generator-backed ISerializer impl.")]
[RequiresDynamicCode("Default ISerializer uses STJ reflection which requires runtime code generation.")]
object FromJson(Type type, DbDataReader reader, int index);

/// <summary>
/// Async deserialize from a Stream.
/// </summary>
[RequiresUnreferencedCode("Default ISerializer uses STJ reflection; AOT consumers should supply a source-generator-backed ISerializer impl.")]
[RequiresDynamicCode("Default ISerializer uses STJ reflection which requires runtime code generation.")]
ValueTask<T> FromJsonAsync<T>(Stream stream, CancellationToken cancellationToken = default);

/// <summary>
/// Async deserialize from a Stream to the specified type.
/// </summary>
[RequiresUnreferencedCode("Default ISerializer uses STJ reflection; AOT consumers should supply a source-generator-backed ISerializer impl.")]
[RequiresDynamicCode("Default ISerializer uses STJ reflection which requires runtime code generation.")]
ValueTask<object> FromJsonAsync(Type type, Stream stream, CancellationToken cancellationToken = default);
}
27 changes: 27 additions & 0 deletions src/Polecat/Serialization/Serializer.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Data.Common;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
Expand Down Expand Up @@ -74,48 +75,66 @@ public void Configure(Action<JsonSerializerOptions> configure)
configure(_options);
}

[RequiresUnreferencedCode("STJ JsonSerializer.Serialize uses reflection over document's runtime type.")]
[RequiresDynamicCode("STJ JsonSerializer.Serialize requires runtime code generation for non-source-generated types.")]
public string ToJson(object document)
{
return JsonSerializer.Serialize(document, document.GetType(), _options);
}

[RequiresUnreferencedCode("STJ JsonSerializer.Deserialize<T> uses reflection over T.")]
[RequiresDynamicCode("STJ JsonSerializer.Deserialize requires runtime code generation for non-source-generated types.")]
public T FromJson<T>(string json)
{
return JsonSerializer.Deserialize<T>(json, _options)!;
}

[RequiresUnreferencedCode("STJ JsonSerializer.Deserialize uses reflection over the supplied type.")]
[RequiresDynamicCode("STJ JsonSerializer.Deserialize requires runtime code generation for non-source-generated types.")]
public object FromJson(Type type, string json)
{
return JsonSerializer.Deserialize(json, type, _options)!;
}

[RequiresUnreferencedCode("STJ JsonSerializer.Deserialize<T> uses reflection over T.")]
[RequiresDynamicCode("STJ JsonSerializer.Deserialize requires runtime code generation for non-source-generated types.")]
public T FromJson<T>(Stream stream)
{
return JsonSerializer.Deserialize<T>(stream, _options)!;
}

[RequiresUnreferencedCode("STJ JsonSerializer.Deserialize uses reflection over the supplied type.")]
[RequiresDynamicCode("STJ JsonSerializer.Deserialize requires runtime code generation for non-source-generated types.")]
public object FromJson(Type type, Stream stream)
{
return JsonSerializer.Deserialize(stream, type, _options)!;
}

[RequiresUnreferencedCode("STJ JsonSerializer.Deserialize<T> uses reflection over T.")]
[RequiresDynamicCode("STJ JsonSerializer.Deserialize requires runtime code generation for non-source-generated types.")]
public T FromJson<T>(DbDataReader reader, int index)
{
var json = reader.GetString(index);
return FromJson<T>(json);
}

[RequiresUnreferencedCode("STJ JsonSerializer.Deserialize uses reflection over the supplied type.")]
[RequiresDynamicCode("STJ JsonSerializer.Deserialize requires runtime code generation for non-source-generated types.")]
public object FromJson(Type type, DbDataReader reader, int index)
{
var json = reader.GetString(index);
return FromJson(type, json);
}

[RequiresUnreferencedCode("STJ JsonSerializer.DeserializeAsync<T> uses reflection over T.")]
[RequiresDynamicCode("STJ JsonSerializer.DeserializeAsync requires runtime code generation for non-source-generated types.")]
public async ValueTask<T> FromJsonAsync<T>(Stream stream, CancellationToken cancellationToken = default)
{
return (await JsonSerializer.DeserializeAsync<T>(stream, _options, cancellationToken))!;
}

[RequiresUnreferencedCode("STJ JsonSerializer.DeserializeAsync uses reflection over the supplied type.")]
[RequiresDynamicCode("STJ JsonSerializer.DeserializeAsync requires runtime code generation for non-source-generated types.")]
public async ValueTask<object> FromJsonAsync(Type type, Stream stream,
CancellationToken cancellationToken = default)
{
Expand All @@ -132,6 +151,8 @@ public static JsonSerializerOptions DefaultOptions()
};
}

[UnconditionalSuppressMessage("AOT", "IL3050:RequiresDynamicCode",
Justification = "JsonStringEnumConverter(JsonNamingPolicy, bool) requires runtime code generation for non-source-generated enum types. The default Serializer is reflection-based by design; AOT consumers should supply a custom ISerializer with the generic JsonStringEnumConverter<TEnum> per-enum.")]
private void ApplyEnumStorage()
{
// Remove any existing JsonStringEnumConverter
Expand Down Expand Up @@ -160,6 +181,12 @@ private void ApplyCasing()
};
}

[UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode",
Justification = "DefaultJsonTypeInfoResolver uses reflection. The whole NonPublicMembers feature is reflection-driven by intent; AOT consumers should supply a custom ISerializer with an STJ source-generator context.")]
[UnconditionalSuppressMessage("AOT", "IL3050:RequiresDynamicCode",
Justification = "Same as IL2026.")]
[UnconditionalSuppressMessage("Trimming", "IL2075:DynamicallyAccessedMembers",
Justification = "Falls back to scanning typeInfo.Type.GetProperties when the JSON property has no usable AttributeProvider. Reflection on user types — keeping public/non-public properties alive is the user's responsibility when opting into NonPublicMembers.")]
private void ApplyNonPublicMembers()
{
if (_nonPublicMembersStorage == NonPublicMembersStorage.Default)
Expand Down
Loading