From c22cec413863eec7d82abb2215c89c74ec9cc835 Mon Sep 17 00:00:00 2001 From: Chrison Simtian Date: Sun, 24 May 2026 21:38:07 +1200 Subject: [PATCH] feat(net): STJ overloads for HTTP JSON helpers, [Obsolete] Newtonsoft surface (#83 follow-up) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds System.Text.Json-flavored overloads to Fallout.Utilities.Net's HTTP extension methods alongside the existing Newtonsoft.Json-flavored ones, and marks the Newtonsoft-typed public API surface [Obsolete] for v11 removal. New STJ overloads (non-breaking): - WithJsonContent(this HttpRequestBuilder, T, JsonSerializerOptions) - GetBodyAsJson(this HttpResponseInspector, JsonSerializerOptions) - GetBodyAsJsonObject(this HttpResponseInspector) → Task - GetBodyAsJsonObject(this HttpResponseInspector, JsonNodeOptions) → Task Deprecated (CS0618) for v11 removal: - WithJsonContent(..., JsonSerializerSettings) - GetBodyAsJson(..., JsonSerializerSettings) - GetBodyAsJson(this HttpResponseInspector) → Task - GetBodyAsJson(..., JsonSerializerSettings) → Task Three internal callers of the deprecated JObject-returning GetBodyAsJson() (MastodonTasks, SlackTasks, GitHubActions.Client) are suppressed via #pragma warning disable CS0618 with TODO comments — they'll migrate to GetBodyAsJsonObject in v11 when the obsolete surface is removed (JObject property-access patterns map non-trivially to JsonObject, so the migration is its own follow-up rather than crammed in here). Added System.Text.Json PackageReference to Fallout.Utilities.Net.csproj (STJ isn't in netstandard2.0 BCL). Newtonsoft.Json reference stays — both serializers are now first-class until the v11 removal cleanup. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../CI/GitHubActions/GitHubActions.Client.cs | 2 + .../Tools/Mastodon/MastodonTasks.cs | 2 + src/Fallout.Common/Tools/Slack/SlackTasks.cs | 2 + .../Fallout.Utilities.Net.csproj | 1 + .../HttpRequest.Content.cs | 17 ++++++-- .../HttpResponse.Body.cs | 42 ++++++++++++++++--- 6 files changed, 58 insertions(+), 8 deletions(-) diff --git a/src/Fallout.Common/CI/GitHubActions/GitHubActions.Client.cs b/src/Fallout.Common/CI/GitHubActions/GitHubActions.Client.cs index 90e226bb8..6f93aaa11 100644 --- a/src/Fallout.Common/CI/GitHubActions/GitHubActions.Client.cs +++ b/src/Fallout.Common/CI/GitHubActions/GitHubActions.Client.cs @@ -29,7 +29,9 @@ private JObject GetJobDetails(long runId) .GetResponse() .AssertSuccessfulStatusCode(); +#pragma warning disable CS0618 // GetBodyAsJson(JObject) retires in v11; migrate to GetBodyAsJsonObject + System.Text.Json.Nodes.JsonObject access patterns then. return response.GetBodyAsJson().GetAwaiter().GetResult() +#pragma warning restore CS0618 .GetChildren("jobs") .Single(x => x.GetPropertyStringValue("name") == Job); } diff --git a/src/Fallout.Common/Tools/Mastodon/MastodonTasks.cs b/src/Fallout.Common/Tools/Mastodon/MastodonTasks.cs index 7f2e68071..027325daa 100644 --- a/src/Fallout.Common/Tools/Mastodon/MastodonTasks.cs +++ b/src/Fallout.Common/Tools/Mastodon/MastodonTasks.cs @@ -37,7 +37,9 @@ async Task PostMediaFile(string file) .AddStreamContent("file", stream, Path.GetFileName(file))) .GetResponseAsync(); +#pragma warning disable CS0618 // GetBodyAsJson(JObject) retires in v11; migrate to GetBodyAsJsonObject + System.Text.Json.Nodes.JsonObject access patterns then. var json = await response.GetBodyAsJson(); +#pragma warning restore CS0618 return json["id"].NotNull().ToString(); } diff --git a/src/Fallout.Common/Tools/Slack/SlackTasks.cs b/src/Fallout.Common/Tools/Slack/SlackTasks.cs index a5356607d..8c6a1a542 100644 --- a/src/Fallout.Common/Tools/Slack/SlackTasks.cs +++ b/src/Fallout.Common/Tools/Slack/SlackTasks.cs @@ -51,7 +51,9 @@ public static async Task SendOrUpdateSlackMessage(Configure("error"); Assert.True(error == null, error); diff --git a/src/Fallout.Utilities.Net/Fallout.Utilities.Net.csproj b/src/Fallout.Utilities.Net/Fallout.Utilities.Net.csproj index a5b669422..c662c22b6 100644 --- a/src/Fallout.Utilities.Net/Fallout.Utilities.Net.csproj +++ b/src/Fallout.Utilities.Net/Fallout.Utilities.Net.csproj @@ -11,6 +11,7 @@ + diff --git a/src/Fallout.Utilities.Net/HttpRequest.Content.cs b/src/Fallout.Utilities.Net/HttpRequest.Content.cs index 7e86d77a7..96d8b1bfa 100644 --- a/src/Fallout.Utilities.Net/HttpRequest.Content.cs +++ b/src/Fallout.Utilities.Net/HttpRequest.Content.cs @@ -1,4 +1,4 @@ -// Copyright 2026 Maintainers of Fallout. +// Copyright 2026 Maintainers of Fallout. // Originally based on NUKE by Matthias Koch and contributors. // Distributed under the MIT License. // https://github.com/ChrisonSimtian/Fallout/blob/main/LICENSE @@ -10,6 +10,7 @@ using System.Net.Http; using System.Net.Http.Headers; using System.Text; +using System.Text.Json; using Newtonsoft.Json; namespace Fallout.Common.Utilities.Net; @@ -17,7 +18,7 @@ namespace Fallout.Common.Utilities.Net; public static partial class HttpRequestExtensions { /// - /// Sets the JSON-serialized object as content via . + /// Sets the JSON-serialized object as content via . /// public static HttpRequestBuilder WithJsonContent(this HttpRequestBuilder builder, T obj) { @@ -26,14 +27,24 @@ public static HttpRequestBuilder WithJsonContent(this HttpRequestBuilder buil } /// - /// Sets the JSON-serialized object as content via . + /// Sets the JSON-serialized object as content via . /// + [Obsolete("Use the JsonSerializerOptions overload instead. Newtonsoft.Json.JsonSerializerSettings is scheduled for removal in v11 as part of the System.Text.Json migration (#83).")] public static HttpRequestBuilder WithJsonContent(this HttpRequestBuilder builder, T obj, JsonSerializerSettings settings) { var content = JsonConvert.SerializeObject(obj, settings); return builder.WithStringContent(content, "application/json"); } + /// + /// Sets the JSON-serialized object as content via . + /// + public static HttpRequestBuilder WithJsonContent(this HttpRequestBuilder builder, T obj, JsonSerializerOptions options) + { + var content = System.Text.Json.JsonSerializer.Serialize(obj, options); + return builder.WithStringContent(content, "application/json"); + } + /// /// Sets the string as content. /// diff --git a/src/Fallout.Utilities.Net/HttpResponse.Body.cs b/src/Fallout.Utilities.Net/HttpResponse.Body.cs index 22440cf1f..ea8010c40 100644 --- a/src/Fallout.Utilities.Net/HttpResponse.Body.cs +++ b/src/Fallout.Utilities.Net/HttpResponse.Body.cs @@ -1,9 +1,12 @@ -// Copyright 2026 Maintainers of Fallout. +// Copyright 2026 Maintainers of Fallout. // Originally based on NUKE by Matthias Koch and contributors. // Distributed under the MIT License. // https://github.com/ChrisonSimtian/Fallout/blob/main/LICENSE +using System; using System.IO; +using System.Text.Json; +using System.Text.Json.Nodes; using System.Threading.Tasks; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -15,7 +18,7 @@ namespace Fallout.Common.Utilities.Net; public static partial class HttpResponseExtensions { /// - /// Reads the HTTP response body as JSON. + /// Reads the HTTP response body as JSON via . /// public static async Task GetBodyAsJson(this HttpResponseInspector inspector) { @@ -23,27 +26,56 @@ public static async Task GetBodyAsJson(this HttpResponseInspector inspecto } /// - /// Reads the HTTP response body as JSON. + /// Reads the HTTP response body as a Newtonsoft . /// + [Obsolete("Use GetBodyAsJsonObject (returns System.Text.Json.Nodes.JsonObject) instead. Newtonsoft.Json.Linq.JObject is scheduled for removal in v11 as part of the System.Text.Json migration (#83).")] public static async Task GetBodyAsJson(this HttpResponseInspector inspector) { return await inspector.GetBodyAsJson(); } /// - /// Reads the HTTP response body as JSON. + /// Reads the HTTP response body as JSON via . /// + [Obsolete("Use the JsonSerializerOptions overload instead. Newtonsoft.Json.JsonSerializerSettings is scheduled for removal in v11 as part of the System.Text.Json migration (#83).")] public static async Task GetBodyAsJson(this HttpResponseInspector inspector, JsonSerializerSettings settings) { return JsonConvert.DeserializeObject(await inspector.GetBodyAsync(), settings); } /// - /// Reads the HTTP response body as JSON. + /// Reads the HTTP response body as a Newtonsoft using the given . /// + [Obsolete("Use GetBodyAsJsonObject with JsonSerializerOptions instead. Newtonsoft types are scheduled for removal in v11 as part of the System.Text.Json migration (#83).")] public static async Task GetBodyAsJson(this HttpResponseInspector inspector, JsonSerializerSettings settings) { +#pragma warning disable CS0618 // Self-call into the obsolete settings-taking generic overload; both retire together in v11. return await inspector.GetBodyAsJson(settings); +#pragma warning restore CS0618 + } + + /// + /// Reads the HTTP response body as JSON via . + /// + public static async Task GetBodyAsJson(this HttpResponseInspector inspector, JsonSerializerOptions options) + { + return System.Text.Json.JsonSerializer.Deserialize(await inspector.GetBodyAsync(), options); + } + + /// + /// Reads the HTTP response body as a System.Text.Json . + /// + public static async Task GetBodyAsJsonObject(this HttpResponseInspector inspector) + { + return JsonNode.Parse(await inspector.GetBodyAsync())?.AsObject(); + } + + /// + /// Reads the HTTP response body as a System.Text.Json using the given . + /// + public static async Task GetBodyAsJsonObject(this HttpResponseInspector inspector, JsonNodeOptions options) + { + return JsonNode.Parse(await inspector.GetBodyAsync(), nodeOptions: options)?.AsObject(); } public static async Task WriteToFile(this HttpResponseInspector inspector, AbsolutePath path, FileMode mode = FileMode.CreateNew)