Skip to content

Commit b8712a3

Browse files
tyrielvminiksa
andcommitted
Add source-generated JSON serialization for NativeAOT
NativeAOT cannot use runtime reflection for JSON serialization. GVFSJsonContext provides source-generated System.Text.Json serializers for 25+ types used in named pipe messages and configuration. GVFSJsonOptions chains source-gen (primary) with reflection fallback for types not yet in the context, allowing incremental migration. NamedPipeMessages: add parameterless constructors required by the source generator's deserialization codegen. Co-authored-by: Michael Niksa <miniksa@microsoft.com> Assisted-by: Claude Opus 4.6 Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
1 parent c55453c commit b8712a3

4 files changed

Lines changed: 83 additions & 21 deletions

File tree

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using GVFS.Common.Http;
2+
using GVFS.Common.NamedPipes;
3+
using GVFS.Common.Tracing;
4+
using GVFS.Common.Prefetch;
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Text.Json;
8+
using System.Text.Json.Serialization;
9+
10+
namespace GVFS.Common
11+
{
12+
/// <summary>
13+
/// Source-generated JSON serializer context for all types used in GVFS serialization.
14+
/// This enables trim-safe and AOT-compatible JSON serialization without reflection.
15+
/// </summary>
16+
[JsonSourceGenerationOptions(
17+
PropertyNameCaseInsensitive = true,
18+
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
19+
Converters = new[] { typeof(VersionConverter) })]
20+
[JsonSerializable(typeof(string))]
21+
[JsonSerializable(typeof(Dictionary<string, string>))]
22+
[JsonSerializable(typeof(KeyValuePair<string, string>))]
23+
[JsonSerializable(typeof(List<string>))]
24+
[JsonSerializable(typeof(List<GitObjectsHttpRequestor.GitObjectSize>))]
25+
[JsonSerializable(typeof(ServerGVFSConfig))]
26+
[JsonSerializable(typeof(VersionResponse))]
27+
[JsonSerializable(typeof(InternalVerbParameters))]
28+
[JsonSerializable(typeof(CacheServerInfo))]
29+
[JsonSerializable(typeof(NamedPipeMessages.GetStatus.Response), TypeInfoPropertyName = "GetStatusResponse")]
30+
[JsonSerializable(typeof(NamedPipeMessages.DehydrateFolders.Request), TypeInfoPropertyName = "DehydrateFoldersRequest")]
31+
[JsonSerializable(typeof(NamedPipeMessages.DehydrateFolders.Response), TypeInfoPropertyName = "DehydrateFoldersResponse")]
32+
[JsonSerializable(typeof(NamedPipeMessages.Notification.Request), TypeInfoPropertyName = "NotificationRequest")]
33+
[JsonSerializable(typeof(NamedPipeMessages.UnregisterRepoRequest))]
34+
[JsonSerializable(typeof(NamedPipeMessages.UnregisterRepoRequest.Response), TypeInfoPropertyName = "UnregisterRepoResponse")]
35+
[JsonSerializable(typeof(NamedPipeMessages.RegisterRepoRequest))]
36+
[JsonSerializable(typeof(NamedPipeMessages.RegisterRepoRequest.Response), TypeInfoPropertyName = "RegisterRepoResponse")]
37+
[JsonSerializable(typeof(NamedPipeMessages.EnableAndAttachProjFSRequest))]
38+
[JsonSerializable(typeof(NamedPipeMessages.EnableAndAttachProjFSRequest.Response), TypeInfoPropertyName = "EnableAndAttachProjFSResponse")]
39+
[JsonSerializable(typeof(NamedPipeMessages.GetActiveRepoListRequest))]
40+
[JsonSerializable(typeof(NamedPipeMessages.GetActiveRepoListRequest.Response), TypeInfoPropertyName = "GetActiveRepoListResponse")]
41+
[JsonSerializable(typeof(NamedPipeMessages.BaseResponse<string>))]
42+
[JsonSerializable(typeof(TelemetryDaemonEventListener.PipeMessage))]
43+
[JsonSerializable(typeof(PrettyConsoleEventListener.ConsoleOutputPayload))]
44+
internal partial class GVFSJsonContext : JsonSerializerContext
45+
{
46+
}
47+
}

GVFS/GVFS.Common/GVFSJsonOptions.cs

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,47 @@
1+
using GVFS.Common.Tracing;
12
using System;
3+
using System.Diagnostics.CodeAnalysis;
24
using System.Text.Json;
5+
using System.Text.Json.Serialization;
6+
using System.Text.Json.Serialization.Metadata;
37

48
namespace GVFS.Common
59
{
6-
/// <summary>
7-
/// Shared JsonSerializerOptions and helpers for the GVFS codebase.
8-
/// PropertyNameCaseInsensitive preserves backward compatibility with
9-
/// Newtonsoft.Json's default case-insensitive deserialization.
10-
/// </summary>
1110
public static class GVFSJsonOptions
1211
{
12+
[UnconditionalSuppressMessage("AOT", "IL2026",
13+
Justification = "DefaultJsonTypeInfoResolver fallback handles all types at runtime.")]
14+
[UnconditionalSuppressMessage("AOT", "IL3050",
15+
Justification = "DefaultJsonTypeInfoResolver fallback handles all types at runtime.")]
1316
public static readonly JsonSerializerOptions Default = new JsonSerializerOptions
1417
{
1518
PropertyNameCaseInsensitive = true,
16-
Converters = { new VersionConverter(), new Tracing.EventMetadataConverter() },
19+
Converters = { new VersionConverter(), new EventMetadataConverter() },
20+
TypeInfoResolverChain = { GVFSJsonContext.Default, new DefaultJsonTypeInfoResolver() },
1721
};
1822

19-
/// <summary>
20-
/// Serialize using the compile-time type. Use when <typeparamref name="T"/>
21-
/// is the concrete type (not a base class with derived properties).
22-
/// </summary>
23+
[UnconditionalSuppressMessage("AOT", "IL2026",
24+
Justification = "TypeInfoResolverChain includes DefaultJsonTypeInfoResolver.")]
25+
[UnconditionalSuppressMessage("AOT", "IL3050",
26+
Justification = "TypeInfoResolverChain includes DefaultJsonTypeInfoResolver.")]
2327
public static string Serialize<T>(T value)
2428
{
2529
return JsonSerializer.Serialize(value, Default);
2630
}
2731

28-
/// <summary>
29-
/// Serialize using the runtime type. Use when calling from a base-class
30-
/// method where compile-time type would lose derived-class properties
31-
/// (e.g., BaseResponse&lt;T&gt;.ToMessage()).
32-
/// </summary>
32+
[UnconditionalSuppressMessage("AOT", "IL2026",
33+
Justification = "TypeInfoResolverChain includes DefaultJsonTypeInfoResolver.")]
34+
[UnconditionalSuppressMessage("AOT", "IL3050",
35+
Justification = "TypeInfoResolverChain includes DefaultJsonTypeInfoResolver.")]
3336
public static string Serialize(object value, Type inputType)
3437
{
3538
return JsonSerializer.Serialize(value, inputType, Default);
3639
}
3740

41+
[UnconditionalSuppressMessage("AOT", "IL2026",
42+
Justification = "TypeInfoResolverChain includes DefaultJsonTypeInfoResolver.")]
43+
[UnconditionalSuppressMessage("AOT", "IL3050",
44+
Justification = "TypeInfoResolverChain includes DefaultJsonTypeInfoResolver.")]
3845
public static T Deserialize<T>(string json)
3946
{
4047
return JsonSerializer.Deserialize<T>(json, Default);

GVFS/GVFS.Common/NamedPipes/NamedPipeMessages.cs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,10 @@ public Response(string result, string data = "")
9090
this.Data = data;
9191
}
9292

93-
public string Result { get; }
94-
public string Data { get; }
93+
public Response() { }
94+
95+
public string Result { get; set; }
96+
public string Data { get; set; }
9597

9698
public Message CreateMessage()
9799
{
@@ -129,7 +131,9 @@ public Response(string result)
129131
this.Result = result;
130132
}
131133

132-
public string Result { get; }
134+
public Response() { }
135+
136+
public string Result { get; set; }
133137

134138
public Message CreateMessage()
135139
{
@@ -185,7 +189,9 @@ public Response(string result)
185189
this.Result = result;
186190
}
187191

188-
public string Result { get; }
192+
public Response() { }
193+
194+
public string Result { get; set; }
189195

190196
public Message CreateMessage()
191197
{
@@ -296,7 +302,9 @@ public Response(string result)
296302
this.Result = result;
297303
}
298304

299-
public string Result { get; }
305+
public Response() { }
306+
307+
public string Result { get; set; }
300308

301309
public Message CreateMessage()
302310
{

GVFS/GVFS.Common/Tracing/PrettyConsoleEventListener.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ protected override void RecordMessageInternal(TraceEventMessage message)
5959
}
6060
}
6161

62-
private class ConsoleOutputPayload
62+
internal class ConsoleOutputPayload
6363
{
6464
public string ErrorMessage { get; set; }
6565
}

0 commit comments

Comments
 (0)