From b77595ffa3a4a3f7c5afcb0c60346fa0c720e647 Mon Sep 17 00:00:00 2001 From: Shane Neuville Date: Mon, 27 Apr 2026 05:30:45 -0500 Subject: [PATCH] Extract shared JsonSerializerOptions to eliminate duplicate allocations Replace 11 inline `new JsonSerializerOptions { WriteIndented = true }` allocations across 7 files with a shared `JsonDefaults.Indented` static readonly field. Each inline allocation bypassed System.Text.Json's internal caching; the shared instance is reused for every serialization call. New file: PolyPilot/Models/JsonDefaults.cs Modified: CopilotService.Events.cs, CopilotService.Organization.cs, CopilotService.Persistence.cs, CopilotService.cs, RepoManager.cs, ScheduledTaskService.cs, ConnectionSettings.cs Test csproj: added Compile Include for JsonDefaults.cs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- PolyPilot.Tests/PolyPilot.Tests.csproj | 1 + PolyPilot/Models/ConnectionSettings.cs | 4 ++-- PolyPilot/Models/JsonDefaults.cs | 8 ++++++++ PolyPilot/Services/CopilotService.Events.cs | 2 +- PolyPilot/Services/CopilotService.Organization.cs | 4 ++-- PolyPilot/Services/CopilotService.Persistence.cs | 6 +++--- PolyPilot/Services/CopilotService.cs | 2 +- PolyPilot/Services/RepoManager.cs | 2 +- PolyPilot/Services/ScheduledTaskService.cs | 2 +- 9 files changed, 20 insertions(+), 11 deletions(-) create mode 100644 PolyPilot/Models/JsonDefaults.cs diff --git a/PolyPilot.Tests/PolyPilot.Tests.csproj b/PolyPilot.Tests/PolyPilot.Tests.csproj index 4a31008a63..537895b6e2 100644 --- a/PolyPilot.Tests/PolyPilot.Tests.csproj +++ b/PolyPilot.Tests/PolyPilot.Tests.csproj @@ -34,6 +34,7 @@ + diff --git a/PolyPilot/Models/ConnectionSettings.cs b/PolyPilot/Models/ConnectionSettings.cs index 83a3748f1a..a51de27bf1 100644 --- a/PolyPilot/Models/ConnectionSettings.cs +++ b/PolyPilot/Models/ConnectionSettings.cs @@ -331,9 +331,9 @@ public void Save() Directory.CreateDirectory(dir); #if IOS || ANDROID SaveMobileSecretsIfDirty(); - var json = JsonSerializer.Serialize(this, new JsonSerializerOptions { WriteIndented = true }); + var json = JsonSerializer.Serialize(this, JsonDefaults.Indented); #else - var json = JsonSerializer.Serialize(this, new JsonSerializerOptions { WriteIndented = true }); + var json = JsonSerializer.Serialize(this, JsonDefaults.Indented); #endif File.WriteAllText(SettingsPath, json); } diff --git a/PolyPilot/Models/JsonDefaults.cs b/PolyPilot/Models/JsonDefaults.cs new file mode 100644 index 0000000000..6230ce8a9c --- /dev/null +++ b/PolyPilot/Models/JsonDefaults.cs @@ -0,0 +1,8 @@ +using System.Text.Json; + +namespace PolyPilot.Models; + +internal static class JsonDefaults +{ + internal static readonly JsonSerializerOptions Indented = new() { WriteIndented = true }; +} diff --git a/PolyPilot/Services/CopilotService.Events.cs b/PolyPilot/Services/CopilotService.Events.cs index 1a193c7374..47f6fb6bd3 100644 --- a/PolyPilot/Services/CopilotService.Events.cs +++ b/PolyPilot/Services/CopilotService.Events.cs @@ -1493,7 +1493,7 @@ private static string FormatToolResult(object? result) if (!string.IsNullOrEmpty(val)) return val; } } - var json = JsonSerializer.Serialize(result, new JsonSerializerOptions { WriteIndented = true }); + var json = JsonSerializer.Serialize(result, JsonDefaults.Indented); if (json != "{}" && json != "null") return json; } catch { } diff --git a/PolyPilot/Services/CopilotService.Organization.cs b/PolyPilot/Services/CopilotService.Organization.cs index 24e5b662ad..a1dfbd5b3b 100644 --- a/PolyPilot/Services/CopilotService.Organization.cs +++ b/PolyPilot/Services/CopilotService.Organization.cs @@ -455,7 +455,7 @@ private void SaveOrganizationCore() DeletedRepoGroupRepoIds = new HashSet(Organization.DeletedRepoGroupRepoIds) }; } - var json = JsonSerializer.Serialize(snapshot, new JsonSerializerOptions { WriteIndented = true }); + var json = JsonSerializer.Serialize(snapshot, JsonDefaults.Indented); WriteOrgFile(json); } catch (Exception ex) @@ -3056,7 +3056,7 @@ internal void SavePendingOrchestration(PendingOrchestration pending) try { Directory.CreateDirectory(PolyPilotBaseDir); - var json = JsonSerializer.Serialize(pending, new JsonSerializerOptions { WriteIndented = true }); + var json = JsonSerializer.Serialize(pending, JsonDefaults.Indented); var tmp = PendingOrchestrationFile + ".tmp"; File.WriteAllText(tmp, json); File.Move(tmp, PendingOrchestrationFile, overwrite: true); diff --git a/PolyPilot/Services/CopilotService.Persistence.cs b/PolyPilot/Services/CopilotService.Persistence.cs index 9202df7a0e..cf5e932ed6 100644 --- a/PolyPilot/Services/CopilotService.Persistence.cs +++ b/PolyPilot/Services/CopilotService.Persistence.cs @@ -156,7 +156,7 @@ private void WriteActiveSessionsFile(List entries) Debug($"Failed to merge existing sessions: {ex.Message}"); } - var json = JsonSerializer.Serialize(entries, new JsonSerializerOptions { WriteIndented = true }); + var json = JsonSerializer.Serialize(entries, JsonDefaults.Indented); // Atomic write: write to temp file then rename to prevent corruption on crash var tempFile = ActiveSessionsFile + ".tmp"; File.WriteAllText(tempFile, json); @@ -1458,7 +1458,7 @@ public void SetSessionAlias(string sessionId, string alias) { // Ensure directory exists (required on iOS where it may not exist by default) Directory.CreateDirectory(PolyPilotBaseDir); - var json = JsonSerializer.Serialize(aliases, new JsonSerializerOptions { WriteIndented = true }); + var json = JsonSerializer.Serialize(aliases, JsonDefaults.Indented); File.WriteAllText(SessionAliasesFile, json); } catch { } @@ -1554,7 +1554,7 @@ public bool DeletePersistedSession(string sessionId) .ToList(); if (kept.Count != entries.Count) { - var updatedJson = JsonSerializer.Serialize(kept, new JsonSerializerOptions { WriteIndented = true }); + var updatedJson = JsonSerializer.Serialize(kept, JsonDefaults.Indented); var tempFile = ActiveSessionsFile + ".tmp"; File.WriteAllText(tempFile, updatedJson); File.Move(tempFile, ActiveSessionsFile, overwrite: true); diff --git a/PolyPilot/Services/CopilotService.cs b/PolyPilot/Services/CopilotService.cs index 1fe380c299..d7070ee6c1 100644 --- a/PolyPilot/Services/CopilotService.cs +++ b/PolyPilot/Services/CopilotService.cs @@ -1963,7 +1963,7 @@ internal static string[] GetMcpCliArgs() // Write merged config back to mcp-config.json so the CLI auto-reads it. // This is more reliable than --additional-mcp-config for persistent servers. var merged = new Dictionary { ["mcpServers"] = allServers }; - var json = JsonSerializer.Serialize(merged, new JsonSerializerOptions { WriteIndented = true }); + var json = JsonSerializer.Serialize(merged, JsonDefaults.Indented); File.WriteAllText(configPath, json); } catch (Exception ex) diff --git a/PolyPilot/Services/RepoManager.cs b/PolyPilot/Services/RepoManager.cs index 6d687a9032..06b2584796 100644 --- a/PolyPilot/Services/RepoManager.cs +++ b/PolyPilot/Services/RepoManager.cs @@ -356,7 +356,7 @@ private void Save() { var stateFile = StateFile; // resolve once Directory.CreateDirectory(Path.GetDirectoryName(stateFile)!); - var json = JsonSerializer.Serialize(_state, new JsonSerializerOptions { WriteIndented = true }); + var json = JsonSerializer.Serialize(_state, JsonDefaults.Indented); // Atomic write: write to .tmp then rename, so a crash during write // doesn't leave repos.json truncated/corrupt. var tmp = stateFile + ".tmp"; diff --git a/PolyPilot/Services/ScheduledTaskService.cs b/PolyPilot/Services/ScheduledTaskService.cs index 7bf3ac9a2b..6b87ba4b76 100644 --- a/PolyPilot/Services/ScheduledTaskService.cs +++ b/PolyPilot/Services/ScheduledTaskService.cs @@ -490,7 +490,7 @@ internal void SaveTasks() { var dir = Path.GetDirectoryName(TasksFilePath)!; Directory.CreateDirectory(dir); - var json = JsonSerializer.Serialize(snapshot, new JsonSerializerOptions { WriteIndented = true }); + var json = JsonSerializer.Serialize(snapshot, JsonDefaults.Indented); var tempPath = TasksFilePath + "." + Guid.NewGuid().ToString("N") + ".tmp"; File.WriteAllText(tempPath, json); File.Move(tempPath, TasksFilePath, overwrite: true);