Skip to content

Commit c83beab

Browse files
committed
Migrate from Newtonsoft.Json to System.Text.Json
Replace Newtonsoft.Json 13.0.1 with System.Text.Json 8.0.5 across the entire codebase. This eliminates the reflection-heavy Newtonsoft dependency, a prerequisite for NativeAOT compilation. Add GVFSJsonOptions.cs with shared JsonSerializerOptions, centralized Serialize/Deserialize helpers, and registered custom converters. Add EventMetadataConverter for AOT-safe Dictionary<string,object> handling. Add VersionConverter for System.Version (no built-in STJ support). Use runtime type dispatch in BaseResponse<T>.ToMessage() to preserve derived-class properties during polymorphic serialization. Replace [JsonProperty] with [JsonPropertyName] in telemetry types. Replace JObject.Parse with JsonDocument in functional tests. Change GitObjectSize readonly fields to properties (STJ ignores fields). Add parameterless constructors where STJ deserialization requires them. Set LangVersion=latest in Directory.Build.props. Remove Newtonsoft.Json from all projects and Directory.Packages.props. Assisted-by: Claude Opus 4.6 Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
1 parent 7d0389b commit c83beab

32 files changed

Lines changed: 413 additions & 160 deletions

Directory.Build.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
<!-- Managed project properties -->
2424
<PropertyGroup Condition="'$(MSBuildProjectExtension)' == '.csproj'">
25+
<LangVersion>latest</LangVersion>
2526
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
2627
<PlatformTarget>x64</PlatformTarget>
2728
<BaseOutputPath>$(ProjectOutPath)bin\</BaseOutputPath>

Directory.Packages.props

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<ItemGroup>
88
<!-- Serialization -->
9-
<PackageVersion Include="Newtonsoft.Json" Version="13.0.1" />
9+
<PackageVersion Include="System.Text.Json" Version="8.0.5" />
1010

1111
<!-- Storage -->
1212
<PackageVersion Include="Microsoft.Data.Sqlite" Version="2.2.4" />
@@ -42,7 +42,6 @@
4242
-->
4343
<PackageVersion Include="System.CommandLine" Version="2.0.5" />
4444
<PackageVersion Include="System.IO.Pipes.AccessControl" Version="8.0.0" />
45-
<PackageVersion Include="System.Text.Json" Version="8.0.5" />
4645
</ItemGroup>
4746

4847
</Project>

GVFS/GVFS.Common/FileBasedDictionary.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
using GVFS.Common.FileSystem;
1+
using GVFS.Common.FileSystem;
22
using GVFS.Common.Tracing;
3-
using Newtonsoft.Json;
43
using System;
54
using System.Collections.Concurrent;
65
using System.Collections.Generic;
6+
using System.Text.Json;
77

88
namespace GVFS.Common
99
{
@@ -120,7 +120,7 @@ private bool TryParseAddLine(string line, out TKey key, out TValue value, out st
120120
{
121121
try
122122
{
123-
KeyValuePair<TKey, TValue> kvp = JsonConvert.DeserializeObject<KeyValuePair<TKey, TValue>>(line);
123+
KeyValuePair<TKey, TValue> kvp = GVFSJsonOptions.Deserialize<KeyValuePair<TKey, TValue>>(line);
124124
key = kvp.Key;
125125
value = kvp.Value;
126126
}
@@ -140,7 +140,7 @@ private bool TryParseRemoveLine(string line, out TKey key, out string error)
140140
{
141141
try
142142
{
143-
key = JsonConvert.DeserializeObject<TKey>(line);
143+
key = GVFSJsonOptions.Deserialize<TKey>(line);
144144
}
145145
catch (JsonException ex)
146146
{
@@ -162,7 +162,7 @@ private IEnumerable<string> GenerateDataLines()
162162
{
163163
foreach (KeyValuePair<TKey, TValue> kvp in this.data)
164164
{
165-
yield return this.FormatAddLine(JsonConvert.SerializeObject(kvp).Trim());
165+
yield return this.FormatAddLine(GVFSJsonOptions.Serialize(kvp).Trim());
166166
}
167167
}
168168
}

GVFS/GVFS.Common/GVFS.Common.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<ItemGroup>
99
<PackageReference Include="LibGit2Sharp.NativeBinaries" />
1010
<PackageReference Include="Microsoft.Data.Sqlite" />
11-
<PackageReference Include="Newtonsoft.Json" />
11+
<PackageReference Include="System.Text.Json" />
1212
<PackageReference Include="SharpZipLib" />
1313
</ItemGroup>
1414

GVFS/GVFS.Common/GVFSEnlistment.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
using GVFS.Common.Git;
33
using GVFS.Common.NamedPipes;
44
using GVFS.Common.Tracing;
5-
using Newtonsoft.Json;
65
using System;
76
using System.IO;
7+
using System.Text.Json;
88
using System.Threading;
99

1010
namespace GVFS.Common
@@ -270,7 +270,7 @@ public static bool WaitUntilMounted(ITracer tracer, string pipeName, string enli
270270
tracer.RelatedError($"{nameof(WaitUntilMounted)}: {errorMessage}");
271271
return false;
272272
}
273-
catch (JsonReaderException e)
273+
catch (JsonException e)
274274
{
275275
errorMessage = string.Format("Failed to parse response from GVFS.Mount.\n {0}", e);
276276
tracer.RelatedError($"{nameof(WaitUntilMounted)}: {errorMessage}");
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using System;
2+
using System.Text.Json;
3+
4+
namespace GVFS.Common
5+
{
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>
11+
public static class GVFSJsonOptions
12+
{
13+
public static readonly JsonSerializerOptions Default = new JsonSerializerOptions
14+
{
15+
PropertyNameCaseInsensitive = true,
16+
Converters = { new VersionConverter(), new Tracing.EventMetadataConverter() },
17+
};
18+
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+
public static string Serialize<T>(T value)
24+
{
25+
return JsonSerializer.Serialize(value, Default);
26+
}
27+
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>
33+
public static string Serialize(object value, Type inputType)
34+
{
35+
return JsonSerializer.Serialize(value, inputType, Default);
36+
}
37+
38+
public static T Deserialize<T>(string json)
39+
{
40+
return JsonSerializer.Deserialize<T>(json, Default);
41+
}
42+
}
43+
}

GVFS/GVFS.Common/Http/CacheServerInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
using Newtonsoft.Json;
2-
using System;
1+
using System;
2+
using System.Text.Json.Serialization;
33

44
namespace GVFS.Common.Http
55
{

GVFS/GVFS.Common/Http/ConfigHttpRequestor.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
using GVFS.Common.Tracing;
2-
using Newtonsoft.Json;
1+
using GVFS.Common.Tracing;
32
using System;
43
using System.Net;
54
using System.Net.Http;
5+
using System.Text.Json;
66
using System.Threading;
77

88
namespace GVFS.Common.Http
@@ -66,10 +66,10 @@ public bool TryQueryGVFSConfig(bool logErrors, out ServerGVFSConfig serverGVFSCo
6666
try
6767
{
6868
string configString = response.RetryableReadToEnd();
69-
ServerGVFSConfig config = JsonConvert.DeserializeObject<ServerGVFSConfig>(configString);
69+
ServerGVFSConfig config = GVFSJsonOptions.Deserialize<ServerGVFSConfig>(configString);
7070
return new RetryWrapper<ServerGVFSConfig>.CallbackResult(config);
7171
}
72-
catch (JsonReaderException e)
72+
catch (JsonException e)
7373
{
7474
return new RetryWrapper<ServerGVFSConfig>.CallbackResult(e, shouldRetry: false);
7575
}

GVFS/GVFS.Common/Http/GitObjectsHttpRequestor.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
using GVFS.Common.Git;
1+
using GVFS.Common.Git;
22
using GVFS.Common.Tracing;
3-
using Newtonsoft.Json;
43
using System;
54
using System.Collections.Generic;
5+
using System.Text.Json.Serialization;
66
using System.Linq;
77
using System.Net;
88
using System.Net.Http;
@@ -81,7 +81,7 @@ public virtual List<GitObjectSize> QueryForFileSizes(IEnumerable<string> objectI
8181
}
8282

8383
string objectSizesString = response.RetryableReadToEnd();
84-
List<GitObjectSize> objectSizes = JsonConvert.DeserializeObject<List<GitObjectSize>>(objectSizesString);
84+
List<GitObjectSize> objectSizes = GVFSJsonOptions.Deserialize<List<GitObjectSize>>(objectSizesString);
8585
return new RetryWrapper<List<GitObjectSize>>.CallbackResult(objectSizes);
8686
}
8787
});
@@ -343,8 +343,8 @@ private string ObjectIdsJsonGenerator(long requestId, Func<IEnumerable<string>>
343343

344344
public class GitObjectSize
345345
{
346-
public readonly string Id;
347-
public readonly long Size;
346+
public string Id { get; set; }
347+
public long Size { get; set; }
348348

349349
[JsonConstructor]
350350
public GitObjectSize(string id, long size)

GVFS/GVFS.Common/InternalVerbParameters.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using Newtonsoft.Json;
21

32
namespace GVFS.Common
43
{
@@ -23,12 +22,12 @@ public InternalVerbParameters(
2322

2423
public static InternalVerbParameters FromJson(string json)
2524
{
26-
return JsonConvert.DeserializeObject<InternalVerbParameters>(json);
25+
return GVFSJsonOptions.Deserialize<InternalVerbParameters>(json);
2726
}
2827

2928
public string ToJson()
3029
{
31-
return JsonConvert.SerializeObject(this);
30+
return GVFSJsonOptions.Serialize(this);
3231
}
3332
}
3433
}

0 commit comments

Comments
 (0)