diff --git a/src/Polecat/Serialization/ISerializer.cs b/src/Polecat/Serialization/ISerializer.cs index c506dea..f308c7f 100644 --- a/src/Polecat/Serialization/ISerializer.cs +++ b/src/Polecat/Serialization/ISerializer.cs @@ -1,10 +1,19 @@ using System.Data.Common; +using System.Diagnostics.CodeAnalysis; namespace Polecat.Serialization; /// /// Serialization abstraction for Polecat. Uses System.Text.Json exclusively. /// +/// +/// The default implementation routes through +/// System.Text.Json.JsonSerializer, which the .NET trimmer cannot +/// statically analyse. AOT-publishing apps should plug in a custom +/// ISerializer implementation backed by an STJ source-generator +/// context (JsonSerializerContext) rather than the reflection-based +/// default. +/// public interface ISerializer { /// @@ -20,45 +29,63 @@ public interface ISerializer /// /// Serialize an object to a JSON string. /// + [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); /// /// Deserialize a JSON string to an object of type T. /// + [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(string json); /// /// Deserialize a JSON string to an object of the specified type. /// + [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); /// /// Deserialize from a Stream. /// + [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(Stream stream); /// /// Deserialize from a Stream to the specified type. /// + [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); /// /// Deserialize from a DbDataReader column. /// + [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(DbDataReader reader, int index); /// /// Deserialize from a DbDataReader column to the specified type. /// + [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); /// /// Async deserialize from a Stream. /// + [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 FromJsonAsync(Stream stream, CancellationToken cancellationToken = default); /// /// Async deserialize from a Stream to the specified type. /// + [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 FromJsonAsync(Type type, Stream stream, CancellationToken cancellationToken = default); } diff --git a/src/Polecat/Serialization/Serializer.cs b/src/Polecat/Serialization/Serializer.cs index ab6aab4..871a72e 100644 --- a/src/Polecat/Serialization/Serializer.cs +++ b/src/Polecat/Serialization/Serializer.cs @@ -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; @@ -74,48 +75,66 @@ public void Configure(Action 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 uses reflection over T.")] + [RequiresDynamicCode("STJ JsonSerializer.Deserialize requires runtime code generation for non-source-generated types.")] public T FromJson(string json) { return JsonSerializer.Deserialize(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 uses reflection over T.")] + [RequiresDynamicCode("STJ JsonSerializer.Deserialize requires runtime code generation for non-source-generated types.")] public T FromJson(Stream stream) { return JsonSerializer.Deserialize(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 uses reflection over T.")] + [RequiresDynamicCode("STJ JsonSerializer.Deserialize requires runtime code generation for non-source-generated types.")] public T FromJson(DbDataReader reader, int index) { var json = reader.GetString(index); return FromJson(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 uses reflection over T.")] + [RequiresDynamicCode("STJ JsonSerializer.DeserializeAsync requires runtime code generation for non-source-generated types.")] public async ValueTask FromJsonAsync(Stream stream, CancellationToken cancellationToken = default) { return (await JsonSerializer.DeserializeAsync(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 FromJsonAsync(Type type, Stream stream, CancellationToken cancellationToken = default) { @@ -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 per-enum.")] private void ApplyEnumStorage() { // Remove any existing JsonStringEnumConverter @@ -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)