From 0606bcb3dadacd87e4f2cb3ae962a3c036e99ab0 Mon Sep 17 00:00:00 2001 From: Jacob Atchley <10537213+JacobAtchley@users.noreply.github.com> Date: Wed, 7 Oct 2026 13:21:33 -0500 Subject: [PATCH 1/6] feat!: target net9.0 and net10.0 with configurable frameworks The library and test projects now target the frameworks listed in FirebendTargetFrameworks in Directory.Build.props (net9.0 and net10.0). Package versions move to central package management in Directory.Packages.props, with per-framework versions for Microsoft.AspNetCore.JsonPatch and Microsoft.Extensions.DependencyInjection. Test packages are updated (MSTest 4, Microsoft.NET.Test.Sdk 18, FluentAssertions 8.11). A repo nuget.config pins restore to nuget.org. CI installs the 9 and 10 SDKs. Existing dotnet format violations are fixed. BREAKING CHANGE: packages now target only net9.0 and net10.0. The netstandard2.1 target is dropped, so .NET Core 3.x, .NET 5-8 and other netstandard2.1 consumers can no longer install new versions. The net10.0 build depends on the 10.0.x Microsoft.AspNetCore.JsonPatch and Microsoft.Extensions.DependencyInjection packages. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01JMzvDp6hZ4dnMSGobxdo98 --- .github/workflows/dotnet-release.yml | 8 +++-- .github/workflows/dotnet.yml | 8 +++-- Directory.Build.props | 10 +++++++ Directory.Packages.props | 30 +++++++++++++++++++ nuget.config | 13 ++++++++ .../DefaultJsonPatchGenerator.cs | 5 +++- .../Interfaces/IJsonDiffDetector.cs | 2 +- .../Interfaces/IJsonDiffSettingsProvider.cs | 2 +- .../Interfaces/IJsonPatchGenerator.cs | 4 +-- .../Interfaces/IJsonPatchWriter.cs | 8 ++--- src/JsonPatchGenerator/JsonDiffDetector.cs | 9 +++--- .../JsonPatchGenerator.csproj | 6 ++-- .../JsonPatchGenerator.Tests.csproj | 12 ++++---- .../JsonPatchGeneratorTests.cs | 2 +- 14 files changed, 89 insertions(+), 30 deletions(-) create mode 100644 Directory.Build.props create mode 100644 Directory.Packages.props create mode 100644 nuget.config diff --git a/.github/workflows/dotnet-release.yml b/.github/workflows/dotnet-release.yml index 79888f2..84215c3 100644 --- a/.github/workflows/dotnet-release.yml +++ b/.github/workflows/dotnet-release.yml @@ -12,7 +12,7 @@ jobs: steps: - name: Setup Git - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: token : ${{ secrets.FIREBEND_GIT_PUSH_PAT}} fetch-depth: 0 @@ -36,9 +36,11 @@ jobs: - name: Setup .NET if: steps.versionize.outcome == 'success' - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v4 with: - dotnet-version: 8.0.101 + dotnet-version: | + 9.0.x + 10.0.x - name: Restore dependencies if: steps.versionize.outcome == 'success' diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index bc8f429..b8b5c4c 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -12,12 +12,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v4 with: - dotnet-version: 8.0.101 + dotnet-version: | + 9.0.x + 10.0.x - name: Restore dependencies run: dotnet restore json-patch-generator.sln diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..43afb4b --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,10 @@ + + + + + net9.0;net10.0 + + net10.0 + + + diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 0000000..da7f79d --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,30 @@ + + + + true + + + + + + + + + + + + + + + + + + + + + + diff --git a/nuget.config b/nuget.config new file mode 100644 index 0000000..1cff172 --- /dev/null +++ b/nuget.config @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/JsonPatchGenerator/DefaultJsonPatchGenerator.cs b/src/JsonPatchGenerator/DefaultJsonPatchGenerator.cs index bdf4e33..03e0f99 100644 --- a/src/JsonPatchGenerator/DefaultJsonPatchGenerator.cs +++ b/src/JsonPatchGenerator/DefaultJsonPatchGenerator.cs @@ -44,7 +44,10 @@ public JsonPatchDocument Generate(T original, T modified) var diffs = _diffDetector.DetectChanges(original, modified); - if (!diffs.Any()) { return new(); } + if (!diffs.Any()) + { + return new(); + } using var scope = _serviceProvider.CreateScope(); var writer = scope.ServiceProvider.GetRequiredService(); diff --git a/src/JsonPatchGenerator/Interfaces/IJsonDiffDetector.cs b/src/JsonPatchGenerator/Interfaces/IJsonDiffDetector.cs index 8124fed..e6e64bf 100644 --- a/src/JsonPatchGenerator/Interfaces/IJsonDiffDetector.cs +++ b/src/JsonPatchGenerator/Interfaces/IJsonDiffDetector.cs @@ -17,5 +17,5 @@ public interface IJsonDiffDetector /// /// A list of /// - List DetectChanges(object original, object modified); + public List DetectChanges(object original, object modified); } diff --git a/src/JsonPatchGenerator/Interfaces/IJsonDiffSettingsProvider.cs b/src/JsonPatchGenerator/Interfaces/IJsonDiffSettingsProvider.cs index df1d766..434027c 100644 --- a/src/JsonPatchGenerator/Interfaces/IJsonDiffSettingsProvider.cs +++ b/src/JsonPatchGenerator/Interfaces/IJsonDiffSettingsProvider.cs @@ -4,5 +4,5 @@ namespace Firebend.JsonPatch.Interfaces; public interface IJsonDiffSettingsProvider { - JsonSerializerSettings Get(); + public JsonSerializerSettings Get(); } diff --git a/src/JsonPatchGenerator/Interfaces/IJsonPatchGenerator.cs b/src/JsonPatchGenerator/Interfaces/IJsonPatchGenerator.cs index 810fd55..529aa51 100644 --- a/src/JsonPatchGenerator/Interfaces/IJsonPatchGenerator.cs +++ b/src/JsonPatchGenerator/Interfaces/IJsonPatchGenerator.cs @@ -20,7 +20,7 @@ public interface IJsonPatchGenerator /// /// A representing the changes in the two objects. /// - JsonPatchDocument Generate(T original, T modified) where T : class; + public JsonPatchDocument Generate(T original, T modified) where T : class; /// /// Converts a and converts it to the non-generic @@ -34,5 +34,5 @@ public interface IJsonPatchGenerator /// /// A /// - JsonPatchDocument ConvertFromGeneric(JsonPatchDocument patch) where T : class; + public JsonPatchDocument ConvertFromGeneric(JsonPatchDocument patch) where T : class; } diff --git a/src/JsonPatchGenerator/Interfaces/IJsonPatchWriter.cs b/src/JsonPatchGenerator/Interfaces/IJsonPatchWriter.cs index 0e861c1..60082c9 100644 --- a/src/JsonPatchGenerator/Interfaces/IJsonPatchWriter.cs +++ b/src/JsonPatchGenerator/Interfaces/IJsonPatchWriter.cs @@ -11,7 +11,7 @@ public interface IJsonPatchWriter /// /// The value added. /// - void WriteAdd(string path, object value); + public void WriteAdd(string path, object value); /// /// Writes a json patch replace operation to the output. @@ -22,7 +22,7 @@ public interface IJsonPatchWriter /// /// The value replaced. /// - void WriteReplace(string path, object value); + public void WriteReplace(string path, object value); /// /// Writes a json remove operation to the output @@ -30,7 +30,7 @@ public interface IJsonPatchWriter /// /// The json patch path /// - void WriteRemove(string path); + public void WriteRemove(string path); /// /// Finishes writing the json patch document and returns it as a string @@ -38,5 +38,5 @@ public interface IJsonPatchWriter /// /// A json patch in string form. /// - string Finish(); + public string Finish(); } diff --git a/src/JsonPatchGenerator/JsonDiffDetector.cs b/src/JsonPatchGenerator/JsonDiffDetector.cs index 51b21ff..81fc1e1 100644 --- a/src/JsonPatchGenerator/JsonDiffDetector.cs +++ b/src/JsonPatchGenerator/JsonDiffDetector.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using Firebend.JsonPatch.Extensions; @@ -10,16 +11,14 @@ namespace Firebend.JsonPatch; public class JsonDiffDetector : IJsonDiffDetector { - private readonly IJsonDiffSettingsProvider _settings; - - private JsonSerializer _serializer; + private readonly Lazy _serializer; public JsonDiffDetector(IJsonDiffSettingsProvider settings) { - _settings = settings; + _serializer = new Lazy(() => JsonSerializer.Create(settings.Get())); } - private JsonSerializer Serializer => _serializer ??= JsonSerializer.Create(_settings.Get()); + private JsonSerializer Serializer => _serializer.Value; public List DetectChanges(object original, object modified) => DetectChangesRecursion(GetJObject(original), GetJObject(modified), [], string.Empty); diff --git a/src/JsonPatchGenerator/JsonPatchGenerator.csproj b/src/JsonPatchGenerator/JsonPatchGenerator.csproj index e2fc531..14cdf0e 100644 --- a/src/JsonPatchGenerator/JsonPatchGenerator.csproj +++ b/src/JsonPatchGenerator/JsonPatchGenerator.csproj @@ -1,7 +1,7 @@ - netstandard2.1 + $(FirebendTargetFrameworks) 9.0.13 Firebend.JsonPatchGenerator Firebend.JsonPatch @@ -21,8 +21,8 @@ - - + + diff --git a/tests/JsonPatchGenerator.Tests/JsonPatchGenerator.Tests.csproj b/tests/JsonPatchGenerator.Tests/JsonPatchGenerator.Tests.csproj index e8352ac..2e120d8 100644 --- a/tests/JsonPatchGenerator.Tests/JsonPatchGenerator.Tests.csproj +++ b/tests/JsonPatchGenerator.Tests/JsonPatchGenerator.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + $(FirebendTargetFrameworks) false @@ -11,11 +11,11 @@ - - - - - + + + + + diff --git a/tests/JsonPatchGenerator.Tests/JsonPatchGeneratorTests.cs b/tests/JsonPatchGenerator.Tests/JsonPatchGeneratorTests.cs index c21ec7f..9bb1c8c 100644 --- a/tests/JsonPatchGenerator.Tests/JsonPatchGeneratorTests.cs +++ b/tests/JsonPatchGenerator.Tests/JsonPatchGeneratorTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Threading.Tasks; using Firebend.JsonPatch.Extensions; using Firebend.JsonPatch.Interfaces; using Firebend.JsonPatch.JsonSerializationSettings; @@ -12,7 +13,6 @@ using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Serialization; -using System.Threading.Tasks; // ReSharper disable UnusedAutoPropertyAccessor.Local From 867283af1082d15fa9075d6df52ebb844aadd569 Mon Sep 17 00:00:00 2001 From: Jacob Atchley <10537213+JacobAtchley@users.noreply.github.com> Date: Wed, 7 Oct 2026 14:06:39 -0500 Subject: [PATCH 2/6] chore: correct the changelog header copied from lit-redis Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01JMzvDp6hZ4dnMSGobxdo98 --- .versionize | 2 +- CHANGELOG.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.versionize b/.versionize index 77fa5f2..8458f60 100644 --- a/.versionize +++ b/.versionize @@ -1,6 +1,6 @@ { "changelog": { - "header": "Lit Redis Changelog", + "header": "Json Patch Generator Changelog", "includeAllCommits": true, "sections": [ { diff --git a/CHANGELOG.md b/CHANGELOG.md index 8645030..b372490 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -Lit Redis Changelog +Json Patch Generator Changelog ## [9.0.13](https://www.github.com/firebend/json-patch-generator/releases/tag/v9.0.13) (2025-09-17) From 69215f59bbc8e7b4646bf19b40a4f73c03a3c8a3 Mon Sep 17 00:00:00 2001 From: Jacob Atchley <10537213+JacobAtchley@users.noreply.github.com> Date: Wed, 7 Oct 2026 14:06:39 -0500 Subject: [PATCH 3/6] test: pin FluentAssertions to the Apache-licensed 7.x line FluentAssertions 8 requires a commercial license. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01JMzvDp6hZ4dnMSGobxdo98 --- Directory.Packages.props | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index da7f79d..469a2a3 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -21,7 +21,8 @@ - + + From d3a5c8b092988ad4b9810db95a9c3826cf04f6b5 Mon Sep 17 00:00:00 2001 From: Jacob Atchley <10537213+JacobAtchley@users.noreply.github.com> Date: Wed, 7 Oct 2026 14:06:39 -0500 Subject: [PATCH 4/6] chore: embed package icon and use MIT license expression Replaces the deprecated PackageLicenseUrl and PackageIconUrl (NU5125, NU5048) with PackageLicenseExpression and an embedded nuget-image.png, packed from Directory.Build.props for every project that sets PackageIcon. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01JMzvDp6hZ4dnMSGobxdo98 --- Directory.Build.props | 5 +++++ src/JsonPatchGenerator/JsonPatchGenerator.csproj | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 43afb4b..5db5b8c 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,4 +7,9 @@ net10.0 + + + + + diff --git a/src/JsonPatchGenerator/JsonPatchGenerator.csproj b/src/JsonPatchGenerator/JsonPatchGenerator.csproj index 14cdf0e..70f6f9e 100644 --- a/src/JsonPatchGenerator/JsonPatchGenerator.csproj +++ b/src/JsonPatchGenerator/JsonPatchGenerator.csproj @@ -15,9 +15,9 @@ firebend;json;patch;document;jsonpatch; Firebend https://github.com/firebend/json-patch-generator - https://github.com/firebend/json-patch-generator/blob/main/LICENSE + MIT https://github.com/firebend/json-patch-generator - https://github.com/firebend/json-patch-generator/blob/main/nuget-image.png?raw=true + nuget-image.png From 36b54b8adc116bece0faf29aecd5f76ee7264dba Mon Sep 17 00:00:00 2001 From: Jacob Atchley <10537213+JacobAtchley@users.noreply.github.com> Date: Wed, 7 Oct 2026 14:26:55 -0500 Subject: [PATCH 5/6] build: pin the .NET SDK with global.json global.json pins SDK 10.0.401 with rollForward latestPatch, so local builds and CI run the same analyzer rules and a new SDK feature band can no longer break dotnet format without a code change. CI reads global.json through setup-dotnet and still installs the 9.0 SDK for the net9.0 test runtime. The release workflow now sets up .NET before installing Versionize, so the dotnet tool install runs on the pinned SDK. Dependabot opens monthly dotnet-sdk updates for global.json. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01JMzvDp6hZ4dnMSGobxdo98 --- .github/dependabot.yml | 7 +++++++ .github/workflows/dotnet-release.yml | 15 +++++++-------- .github/workflows/dotnet.yml | 6 +++--- global.json | 6 ++++++ 4 files changed, 23 insertions(+), 11 deletions(-) create mode 100644 global.json diff --git a/.github/dependabot.yml b/.github/dependabot.yml index eb80fbb..ab79c21 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -12,3 +12,10 @@ updates: commit-message: include: scope prefix: "fix: " + - package-ecosystem: "dotnet-sdk" + directory: "/" + schedule: + interval: "monthly" + commit-message: + include: scope + prefix: "chore: " diff --git a/.github/workflows/dotnet-release.yml b/.github/workflows/dotnet-release.yml index 84215c3..3260af3 100644 --- a/.github/workflows/dotnet-release.yml +++ b/.github/workflows/dotnet-release.yml @@ -17,6 +17,13 @@ jobs: token : ${{ secrets.FIREBEND_GIT_PUSH_PAT}} fetch-depth: 0 + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + # The .NET 9 SDK supplies the net9.0 runtime for tests; global.json pins the SDK that builds. + dotnet-version: 9.0.x + global-json-file: global.json + - name: Install Versionize run: dotnet tool install --global Versionize @@ -34,14 +41,6 @@ jobs: if: steps.versionize.outcome != 'success' run: echo "Skipping publishing. No release required." - - name: Setup .NET - if: steps.versionize.outcome == 'success' - uses: actions/setup-dotnet@v4 - with: - dotnet-version: | - 9.0.x - 10.0.x - - name: Restore dependencies if: steps.versionize.outcome == 'success' run: dotnet restore json-patch-generator.sln diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index b8b5c4c..f7a26a3 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -17,9 +17,9 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: | - 9.0.x - 10.0.x + # The .NET 9 SDK supplies the net9.0 runtime for tests; global.json pins the SDK that builds. + dotnet-version: 9.0.x + global-json-file: global.json - name: Restore dependencies run: dotnet restore json-patch-generator.sln diff --git a/global.json b/global.json new file mode 100644 index 0000000..f3e8020 --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "10.0.401", + "rollForward": "latestPatch" + } +} From c2a46aa95d22c973bc4551caf71b494afdef15ed Mon Sep 17 00:00:00 2001 From: Jacob Atchley <10537213+JacobAtchley@users.noreply.github.com> Date: Wed, 7 Oct 2026 16:15:14 -0500 Subject: [PATCH 6/6] docs: document the SDK pin and framework configuration Adds a Building from source section covering global.json and the FirebendTargetFrameworks and FirebendAppTargetFramework properties. Dependabot now ignores major updates to the framework-aligned Microsoft packages, which are versioned per target framework in Directory.Packages.props. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01JMzvDp6hZ4dnMSGobxdo98 --- .github/dependabot.yml | 13 +++++++++++++ README.md | 8 +++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ab79c21..84e52b8 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -12,6 +12,19 @@ updates: commit-message: include: scope prefix: "fix: " + # Framework-aligned packages are versioned per target framework in Directory.Packages.props. + # A new major version means a new target framework block, which is added by hand. + ignore: + - dependency-name: "Microsoft.Extensions.*" + update-types: ["version-update:semver-major"] + - dependency-name: "Microsoft.EntityFrameworkCore*" + update-types: ["version-update:semver-major"] + - dependency-name: "Microsoft.AspNetCore.Authentication.JwtBearer" + update-types: ["version-update:semver-major"] + - dependency-name: "Microsoft.AspNetCore.JsonPatch" + update-types: ["version-update:semver-major"] + - dependency-name: "Microsoft.AspNetCore.Mvc.NewtonsoftJson" + update-types: ["version-update:semver-major"] - package-ecosystem: "dotnet-sdk" directory: "/" schedule: diff --git a/README.md b/README.md index d029300..adfc2c6 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,8 @@ # Firebend JSON Patch Generator -A class library for comparing objects and generating JSON patch documents. \ No newline at end of file +A class library for comparing objects and generating JSON patch documents. + +## Building from source + +- Building requires the .NET SDK version in [`global.json`](global.json) (10.0.401 or a later 10.0.4xx patch). Running the tests also needs the .NET 9 runtime. +- Target frameworks are set in [`Directory.Build.props`](Directory.Build.props). `FirebendTargetFrameworks` lists the frameworks every library and test project builds for (currently `net9.0;net10.0`). `FirebendAppTargetFramework` is the single framework for samples and other runnable apps. +- Package versions are managed centrally in [`Directory.Packages.props`](Directory.Packages.props). Microsoft framework packages have one version block per target framework, so adding a framework means adding it to `FirebendTargetFrameworks` and adding a matching block there.