From 7fb5f049fcac3ceda13ab3ce3f014a4e5fa8dad9 Mon Sep 17 00:00:00 2001 From: Chrison Simtian Date: Sun, 24 May 2026 22:35:05 +1200 Subject: [PATCH] feat(text.json): JsonObject helpers + migrate 3 pragma'd callers off Newtonsoft (#83) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds JsonObject-flavored extension methods (parallel to the existing JObject helpers) and migrates the 3 internal callers that were #pragma-suppressed in #165, removing the CS0618 suppressions. New JsonNodeExtensions partial class in Fallout.Utilities.Text.Json: - JsonObject.GetPropertyValue.cs — GetPropertyValueOrNull, GetPropertyValue, GetPropertyValue (JsonObject overload), GetPropertyStringValue - JsonObject.GetChildren.cs — GetChildren, GetChildren (JsonObject overload) The signatures mirror the existing Newtonsoft-flavored JObjectExtensions methods so consumer migration is mechanical. Migrated callers (3): - Tools/Mastodon/MastodonTasks.cs — uses GetBodyAsJsonObject + GetPropertyStringValue - Tools/Slack/SlackTasks.cs — uses GetBodyAsJsonObject + GetPropertyValueOrNull / GetPropertyStringValue - CI/GitHubActions/GitHubActions.Client.cs — return type of GetJobDetails changed from JObject to JsonObject; uses GetBodyAsJsonObject + GetChildren + GetPropertyStringValue. The downstream GetPropertyValue caller (GetJobId) works unchanged because the new JsonObject extensions have the same shape. All 3 #pragma warning disable CS0618 blocks are removed. The TODO from #165's v11 cutover plan is now closed for these callers. Out of scope for this PR (still needs Newtonsoft): - The JObject-typed `GitHubActions.GitHubEvent` public property — separate v11 API-break PR - Fallout.Tooling runtime serialization, Fallout.Build NJsonSchema swap, Fallout.Tooling.Generator, ~60 Tools/*/*.Generated.cs files Co-Authored-By: Claude Opus 4.7 (1M context) --- .../CI/GitHubActions/GitHubActions.Client.cs | 8 ++-- .../Tools/Mastodon/MastodonTasks.cs | 7 ++- src/Fallout.Common/Tools/Slack/SlackTasks.cs | 4 +- .../JsonObject.GetChildren.cs | 31 +++++++++++++ .../JsonObject.GetPropertyValue.cs | 43 +++++++++++++++++++ 5 files changed, 81 insertions(+), 12 deletions(-) create mode 100644 src/Fallout.Utilities.Text.Json/JsonObject.GetChildren.cs create mode 100644 src/Fallout.Utilities.Text.Json/JsonObject.GetPropertyValue.cs diff --git a/src/Fallout.Common/CI/GitHubActions/GitHubActions.Client.cs b/src/Fallout.Common/CI/GitHubActions/GitHubActions.Client.cs index 6f93aaa11..574a85636 100644 --- a/src/Fallout.Common/CI/GitHubActions/GitHubActions.Client.cs +++ b/src/Fallout.Common/CI/GitHubActions/GitHubActions.Client.cs @@ -5,8 +5,8 @@ using System.Linq; using System.Net.Http; +using System.Text.Json.Nodes; using System.Threading.Tasks; -using Newtonsoft.Json.Linq; using Fallout.Common.Utilities; using Fallout.Common.Utilities.Net; @@ -22,16 +22,14 @@ await _httpClient.Value .GetResponseAsync(); } - private JObject GetJobDetails(long runId) + private JsonObject GetJobDetails(long runId) { var response = _httpClient.Value .CreateRequest(HttpMethod.Get, $"repos/{Repository}/actions/runs/{runId}/jobs") .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 + return response.GetBodyAsJsonObject().GetAwaiter().GetResult() .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 027325daa..bd133b15e 100644 --- a/src/Fallout.Common/Tools/Mastodon/MastodonTasks.cs +++ b/src/Fallout.Common/Tools/Mastodon/MastodonTasks.cs @@ -9,6 +9,7 @@ using System.Net.Http; using System.Threading.Tasks; using Fallout.Common.Tooling; +using Fallout.Common.Utilities; using Fallout.Common.Utilities.Net; namespace Fallout.Common.Tools.Mastodon; @@ -37,10 +38,8 @@ 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(); + var json = await response.GetBodyAsJsonObject(); + return json.GetPropertyStringValue("id"); } var mediaTasks = status.MediaFiles.Select(PostMediaFile).ToArray(); diff --git a/src/Fallout.Common/Tools/Slack/SlackTasks.cs b/src/Fallout.Common/Tools/Slack/SlackTasks.cs index 8c6a1a542..ec0efb9e4 100644 --- a/src/Fallout.Common/Tools/Slack/SlackTasks.cs +++ b/src/Fallout.Common/Tools/Slack/SlackTasks.cs @@ -51,9 +51,7 @@ public static async Task SendOrUpdateSlackMessage(Configure("error"); Assert.True(error == null, error); diff --git a/src/Fallout.Utilities.Text.Json/JsonObject.GetChildren.cs b/src/Fallout.Utilities.Text.Json/JsonObject.GetChildren.cs new file mode 100644 index 000000000..768fba528 --- /dev/null +++ b/src/Fallout.Utilities.Text.Json/JsonObject.GetChildren.cs @@ -0,0 +1,31 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Text.Json.Nodes; + +namespace Fallout.Common.Utilities; + +public static partial class JsonNodeExtensions +{ + /// + /// Reads property as a and returns its elements typed as . + /// + public static IEnumerable GetChildren(this JsonObject jsonObject, string name) + where T : JsonNode + { + var array = jsonObject[name] as JsonArray; + return array?.OfType() ?? Enumerable.Empty(); + } + + /// + /// Reads property as a of children. + /// + public static IEnumerable GetChildren(this JsonObject jsonObject, string name) + { + return jsonObject.GetChildren(name); + } +} diff --git a/src/Fallout.Utilities.Text.Json/JsonObject.GetPropertyValue.cs b/src/Fallout.Utilities.Text.Json/JsonObject.GetPropertyValue.cs new file mode 100644 index 000000000..38a5a81c2 --- /dev/null +++ b/src/Fallout.Utilities.Text.Json/JsonObject.GetPropertyValue.cs @@ -0,0 +1,43 @@ +// 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.Text.Json.Nodes; + +namespace Fallout.Common.Utilities; + +public static partial class JsonNodeExtensions +{ + /// + /// Reads property as , returning default if absent. + /// + public static T GetPropertyValueOrNull(this JsonObject jsonObject, string name) + { + return jsonObject[name] is JsonNode node ? node.GetValue() : default; + } + + /// + /// Reads property as , throwing if absent. + /// + public static T GetPropertyValue(this JsonObject jsonObject, string name) + { + return jsonObject[name].NotNull($"Property '{name}' not found").GetValue(); + } + + /// + /// Reads property as a nested , throwing if absent. + /// + public static JsonObject GetPropertyValue(this JsonObject jsonObject, string name) + { + return jsonObject[name].NotNull($"Property '{name}' not found").AsObject(); + } + + /// + /// Reads property as a string, throwing if absent. + /// + public static string GetPropertyStringValue(this JsonObject jsonObject, string name) + { + return jsonObject.GetPropertyValue(name); + } +}