Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

### Breaking changes

- **Last-mile Newtonsoft removal — closes the #83 migration tree** (#119 STJ-6, #115 STJ-2 tail). Every Fallout source file is STJ-native; `Newtonsoft.Json.dll` survives in the closure only as a transitive of `NuGet.Packaging` and `Serilog.Formatting.Compact.Reader`. The `Newtonsoft.Json` `PackageVersion` is gone from `Directory.Packages.props` entirely.
- **`GitHubActions.GitHubEvent` is now `JsonObject`** (was Newtonsoft `JObject`). Consumers that introspect the event payload need to swap `using Newtonsoft.Json.Linq` → `using System.Text.Json.Nodes` and update accessors (`["key"].Value<T>()` → `["key"].GetValue<T>()`, `Property(name)` → indexer, etc.).
- **`Fallout.Utilities.Net` `HttpRequestExtensions.WithJsonContent` / `HttpResponseExtensions.GetBodyAsJson` defaults are STJ.** The `[Obsolete]`-marked Newtonsoft overloads (`JsonSerializerSettings` parameter, `JObject` return) are gone. `JsonSerializerOptions` overloads remain for explicit configuration; the parameterless default uses STJ with default options. `GetBodyAsJsonObject` returns `System.Text.Json.Nodes.JsonObject`.
- **`SlackMessageActionButton.Type` / `TeamsMessage.Type` / `TeamsMessage.Context`**: hand-written `[JsonProperty(...)]` attributes swapped to `[JsonPropertyName(...)]`. Consumer impact only if you subclass these types and added `[JsonProperty]`-style attributes — switch to `[JsonPropertyName]`.
- **`TwitterTasks`**: internal error-parsing path swapped from `JObject.Parse` to `JsonNode.Parse`. No public-API change.
- **`ArgumentsFromParametersFileAttribute`** (#115 STJ-2 tail): `parameters.json` reading swapped from `JObject` to `JsonObject`. Behaviour identical for valid input; STJ may be slightly stricter about malformed JSON.

- **`SchemaUtility` rewritten on `System.Text.Json` — NJsonSchema gone** (#114, STJ-1 of #83). The build-parameter schema generator (which emits `.fallout/build.schema.json` for editor autocomplete) no longer goes through NJsonSchema. It now hand-rolls the draft-04 schema using `JsonNode`/`JsonObject` directly — same output shape, no Rico-entanglement.
- **Dropped packages**: `NJsonSchema`, `NJsonSchema.NewtonsoftJson`, `NJsonSchema.Annotations`, `Namotion.Reflection` — four packages and ~12 transitive dependencies gone from `Fallout.Build`. `Newtonsoft.Json` stays in the closure only via `Fallout.Common`'s `*Tasks.cs` (Slack/Twitter/Teams) until #119 lands.
- **API**: `SchemaUtility.GetJsonString(IFalloutBuild)` and `GetJsonDocument(IFalloutBuild)` unchanged for consumers.
Expand Down
1 change: 0 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
<PackageVersion Include="Microsoft.Extensions.DependencyModel" Version="10.0.0" />
<PackageVersion Include="Nerdbank.GitVersioning" Version="3.7.115" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
<PackageVersion Include="NuGet.Packaging" Version="6.14.3" />
<PackageVersion Include="Octokit" Version="14.0.0" />
<PackageVersion Include="Serilog" Version="4.3.0" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Newtonsoft.Json.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;
using Fallout.Common.CI;
using Fallout.Common.IO;
using Fallout.Common.Utilities;
Expand Down Expand Up @@ -49,11 +50,11 @@ public void OnBuildCreated(IReadOnlyCollection<ExecutableTarget> executableTarge
// : value;

var parameterMembers = ValueInjectionUtility.GetParameterMembers(Build.GetType(), includeUnlisted: true);
var jobjectsAndProfiles = new[] { (File: Constants.GetDefaultParametersFile(FalloutBuild.RootDirectory), Profile: Constants.DefaultProfileName) }
var parameterObjectsAndProfiles = new[] { (File: Constants.GetDefaultParametersFile(FalloutBuild.RootDirectory), Profile: Constants.DefaultProfileName) }
.Where(x => File.Exists(x.File))
.Concat(FalloutBuild.LoadedLocalProfiles.Select(x => (File: Constants.GetParametersProfileFile(FalloutBuild.RootDirectory, x), Profile: x)))
.ForEachLazy(x => Assert.FileExists(x.File))
.Select(x => (JObject: JObject.Parse(File.ReadAllText(x.File)), x.Profile))
.Select(x => (JsonObject: JsonNode.Parse(File.ReadAllText(x.File)).NotNull().AsObject(), x.Profile))
.Reverse();

var passwords = new Dictionary<string, string>();
Expand All @@ -66,22 +67,22 @@ string DecryptValue(string profile, string name, string value)

ParameterService.Instance.ArgumentsFromFilesService = (parameter, destinationType) =>
{
var (property, profile) = jobjectsAndProfiles.Select(x => (Property: x.JObject.Property(parameter), x.Profile))
.Where(x => x.Property != null)
var (value, profile) = parameterObjectsAndProfiles.Select(x => (Value: x.JsonObject[parameter], x.Profile))
.Where(x => x.Value != null)
.FirstOrDefault();
if (property == null)
if (value == null)
return null;

var member = parameterMembers.SingleOrDefault(x => ParameterService.GetParameterMemberName(x).EqualsOrdinalIgnoreCase(parameter));
var scalarType = member?.GetMemberType().GetScalarType();
if (typeof(IAbsolutePathHolder).IsAssignableFrom(scalarType))
return property.Value.ToObject<string>().Apply(x => !PathConstruction.HasPathRoot(x) ? FalloutBuild.RootDirectory / x : (AbsolutePath)x);
return value.GetValue<string>().Apply(x => !PathConstruction.HasPathRoot(x) ? FalloutBuild.RootDirectory / x : (AbsolutePath)x);

if ((member?.HasCustomAttribute<SecretAttribute>() ?? false) &&
!BuildServerConfigurationGeneration.IsActive)
return DecryptValue(profile, parameter, property.Value.ToObject<string>());
return DecryptValue(profile, parameter, value.GetValue<string>());

return property.Value.ToObject(destinationType);
return value.Deserialize(destinationType);
};
}
}
14 changes: 6 additions & 8 deletions src/Fallout.Common/CI/GitHubActions/GitHubActions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Text.Json.Nodes;
using Fallout.Common.IO;
using Fallout.Common.Tooling;
using Fallout.Common.Utilities;
Expand All @@ -29,7 +28,7 @@ public partial class GitHubActions : Host, IBuildServer

public new static GitHubActions Instance => Host.Instance as GitHubActions;

private readonly Lazy<JObject> _eventContext;
private readonly Lazy<JsonObject> _eventContext;
private readonly Lazy<HttpClient> _httpClient;
private readonly Lazy<long> _jobId;

Expand All @@ -38,7 +37,7 @@ internal GitHubActions()
_eventContext = Lazy.Create(() =>
{
var content = File.ReadAllText(EventPath);
return JsonConvert.DeserializeObject<JObject>(content);
return JsonNode.Parse(content)?.AsObject() ?? new JsonObject();
});
_httpClient = Lazy.Create(() =>
{
Expand Down Expand Up @@ -112,11 +111,10 @@ internal GitHubActions()
public string Token => EnvironmentInfo.GetVariable("GITHUB_TOKEN");
public long JobId => _jobId.Value;

public JObject GitHubEvent => _eventContext.Value;
public JsonObject GitHubEvent => _eventContext.Value;
public bool IsPullRequest => EventName == "pull_request";
// Note: GitHubEvent stays JObject (Newtonsoft) public surface for v11; full JsonObject migration is a separate API-break PR.
public int? PullRequestNumber => GitHubEvent.Property("number")?.Value.Value<int>();
public string PullRequestAction => GitHubEvent.Property("action")?.Value.Value<string>();
public int? PullRequestNumber => GitHubEvent["number"]?.GetValue<int>();
public string PullRequestAction => GitHubEvent["action"]?.GetValue<string>();

public AbsolutePath StepSummaryFile => EnvironmentInfo.GetVariable("GITHUB_STEP_SUMMARY");

Expand Down
7 changes: 4 additions & 3 deletions src/Fallout.Common/Tools/Slack/SlackTasks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net.Http;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Fallout.Common.Tooling;
using Fallout.Common.Utilities;
using Fallout.Common.Utilities.Net;
Expand All @@ -28,7 +29,7 @@ public static void SendSlackMessage(Configure<SlackMessage> configurator, string
public static async Task SendSlackMessageAsync(Configure<SlackMessage> configurator, string webhook)
{
var message = configurator(new SlackMessage());
var payload = JsonConvert.SerializeObject(message);
var payload = JsonSerializer.Serialize(message);

var response = await s_client.CreateRequest(HttpMethod.Post, webhook)
.WithFormUrlEncodedContent(new Dictionary<string, string> { ["payload"] = payload })
Expand Down Expand Up @@ -63,6 +64,6 @@ public static async Task<string> SendOrUpdateSlackMessage(Configure<SlackMessage
[Serializable]
public class SlackMessageActionButton : SlackMessageAction
{
[JsonProperty("type")]
[JsonPropertyName("type")]
public string Type => "button";
}
9 changes: 4 additions & 5 deletions src/Fallout.Common/Tools/Teams/Teams.Extensions.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
// 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 Newtonsoft.Json;
using System;
using System.Text.Json.Serialization;

namespace Fallout.Common.Tools.Teams;

public partial class TeamsMessage
{
[JsonProperty("@type")]
[JsonPropertyName("@type")]
internal string Type => "MessageCard";
[JsonProperty("@context")]
[JsonPropertyName("@context")]
internal string Context => "http://schema.org/extensions";
}
6 changes: 3 additions & 3 deletions src/Fallout.Common/Tools/Twitter/TwitterTasks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using Fallout.Common.Utilities;
using Fallout.Common.Utilities.Collections;

Expand Down Expand Up @@ -95,8 +95,8 @@ private static string GetErrorFromBody(string response)
{
try
{
var jResponse = JObject.Parse(response);
var message = (string) jResponse["errors"].NotNull()[0].NotNull()["message"];
var jResponse = JsonNode.Parse(response).NotNull().AsObject();
var message = jResponse["errors"].NotNull().AsArray()[0].NotNull()["message"]?.GetValue<string>();
if (!string.IsNullOrEmpty(message))
return message;
}
Expand Down
1 change: 0 additions & 1 deletion src/Fallout.Utilities.Net/Fallout.Utilities.Net.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="System.Net.Http" />
<PackageReference Include="System.Text.Json" />
</ItemGroup>
Expand Down
20 changes: 4 additions & 16 deletions src/Fallout.Utilities.Net/HttpRequest.Content.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,42 +6,30 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using Newtonsoft.Json;

namespace Fallout.Common.Utilities.Net;

public static partial class HttpRequestExtensions
{
/// <summary>
/// Sets the JSON-serialized object as content via <see cref="Newtonsoft.Json.JsonConvert.SerializeObject(object?)"/>.
/// Sets the JSON-serialized object as content via <see cref="JsonSerializer.Serialize{TValue}(TValue, JsonSerializerOptions?)"/>.
/// </summary>
public static HttpRequestBuilder WithJsonContent<T>(this HttpRequestBuilder builder, T obj)
{
var content = JsonConvert.SerializeObject(obj);
var content = JsonSerializer.Serialize(obj);
return builder.WithStringContent(content, "application/json");
}

/// <summary>
/// Sets the JSON-serialized object as content via <see cref="Newtonsoft.Json.JsonConvert.SerializeObject(object?)"/>.
/// </summary>
[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<T>(this HttpRequestBuilder builder, T obj, JsonSerializerSettings settings)
{
var content = JsonConvert.SerializeObject(obj, settings);
return builder.WithStringContent(content, "application/json");
}

/// <summary>
/// Sets the JSON-serialized object as content via <see cref="System.Text.Json.JsonSerializer.Serialize{TValue}(TValue, JsonSerializerOptions?)"/>.
/// Sets the JSON-serialized object as content via <see cref="JsonSerializer.Serialize{TValue}(TValue, JsonSerializerOptions?)"/>.
/// </summary>
public static HttpRequestBuilder WithJsonContent<T>(this HttpRequestBuilder builder, T obj, JsonSerializerOptions options)
{
var content = System.Text.Json.JsonSerializer.Serialize(obj, options);
var content = JsonSerializer.Serialize(obj, options);
return builder.WithStringContent(content, "application/json");
}

Expand Down
45 changes: 6 additions & 39 deletions src/Fallout.Utilities.Net/HttpResponse.Body.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,75 +3,42 @@
// 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;
using Fallout.Common.IO;

namespace Fallout.Common.Utilities.Net;

// TODO: reduce with overloads
public static partial class HttpResponseExtensions
{
/// <summary>
/// Reads the HTTP response body as JSON via <see cref="Newtonsoft.Json.JsonConvert.DeserializeObject{T}(string)"/>.
/// Reads the HTTP response body as JSON via <see cref="JsonSerializer.Deserialize{TValue}(string, JsonSerializerOptions?)"/>.
/// </summary>
public static async Task<T> GetBodyAsJson<T>(this HttpResponseInspector inspector)
{
return JsonConvert.DeserializeObject<T>(await inspector.GetBodyAsync());
return JsonSerializer.Deserialize<T>(await inspector.GetBodyAsync());
}

/// <summary>
/// Reads the HTTP response body as a Newtonsoft <see cref="JObject"/>.
/// </summary>
[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<JObject> GetBodyAsJson(this HttpResponseInspector inspector)
{
return await inspector.GetBodyAsJson<JObject>();
}

/// <summary>
/// Reads the HTTP response body as JSON via <see cref="Newtonsoft.Json.JsonConvert.DeserializeObject{T}(string, JsonSerializerSettings)"/>.
/// </summary>
[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<T> GetBodyAsJson<T>(this HttpResponseInspector inspector, JsonSerializerSettings settings)
{
return JsonConvert.DeserializeObject<T>(await inspector.GetBodyAsync(), settings);
}

/// <summary>
/// Reads the HTTP response body as a Newtonsoft <see cref="JObject"/> using the given <paramref name="settings"/>.
/// </summary>
[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<JObject> 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<JObject>(settings);
#pragma warning restore CS0618
}

/// <summary>
/// Reads the HTTP response body as JSON via <see cref="System.Text.Json.JsonSerializer.Deserialize{TValue}(string, JsonSerializerOptions?)"/>.
/// Reads the HTTP response body as JSON via <see cref="JsonSerializer.Deserialize{TValue}(string, JsonSerializerOptions?)"/>.
/// </summary>
public static async Task<T> GetBodyAsJson<T>(this HttpResponseInspector inspector, JsonSerializerOptions options)
{
return System.Text.Json.JsonSerializer.Deserialize<T>(await inspector.GetBodyAsync(), options);
return JsonSerializer.Deserialize<T>(await inspector.GetBodyAsync(), options);
}

/// <summary>
/// Reads the HTTP response body as a System.Text.Json <see cref="JsonObject"/>.
/// Reads the HTTP response body as a <see cref="JsonObject"/>.
/// </summary>
public static async Task<JsonObject> GetBodyAsJsonObject(this HttpResponseInspector inspector)
{
return JsonNode.Parse(await inspector.GetBodyAsync())?.AsObject();
}

/// <summary>
/// Reads the HTTP response body as a System.Text.Json <see cref="JsonObject"/> using the given <paramref name="options"/>.
/// Reads the HTTP response body as a <see cref="JsonObject"/> using the given <paramref name="options"/>.
/// </summary>
public static async Task<JsonObject> GetBodyAsJsonObject(this HttpResponseInspector inspector, JsonNodeOptions options)
{
Expand Down
Loading