Skip to content

feat(tooling)!: STJ-4 + STJ-5 + #116 closure — Fallout.Tooling engine + Generator on System.Text.Json#172

Merged
ChrisonSimtian merged 2 commits into
mainfrom
feature/117-118-stj4-tooling-engine
May 25, 2026
Merged

feat(tooling)!: STJ-4 + STJ-5 + #116 closure — Fallout.Tooling engine + Generator on System.Text.Json#172
ChrisonSimtian merged 2 commits into
mainfrom
feature/117-118-stj4-tooling-engine

Conversation

@ChrisonSimtian

Copy link
Copy Markdown
Collaborator

Summary

Bundles #117 STJ-4 (Fallout.Tooling engine), #118 STJ-5 (Fallout.Tooling.Generator + model attributes), and the residual #116 tail (Object.ToJObject deletion + Newtonsoft.Json PackageReference removal from Fallout.Utilities.Text.Json) into one PR. After this, Newtonsoft.Json remains in the solution only via Fallout.Common — kept for the *Tasks.cs files until #119 (STJ-6) lands.

Why bundled

The two issues are coupled by the engine's JsonPropertyAttribute lookup at Options.cs:83: the engine maps C# member names → JSON keys via Newtonsoft's [JsonProperty], and the generator emits [JsonProperty] on tool wrappers. Migrating one without the other would either break Discord/Slack/Teams (only 3 of 62 wrappers actually carry [JsonProperty]) or require a compat shim that gets ripped out a week later.

Engine changes (#117)

File Change
Options.cs JObject InternalOptionsJsonObject. New SerializerOptions (JsonSerializerOptions) replaces JsonSerializer + JsonSerializerSettings. TypeConverter rewritten as JsonConverterFactory and registered in SerializerOptions.Converters — STJ doesn't inherit class-level [JsonConverter] attributes onto subclasses (this was the subtle bug that took the longest to track down).
ObjectFromFieldConverter.cs Rewritten as JsonConverterFactory (LookupTable<,> needs polymorphic handling).
EnumerationJsonConverter.cs (new) Bridges [TypeConverter]-attributed Enumeration subclasses — STJ doesn't honour System.ComponentModel.TypeConverter. Includes ReadAsPropertyName/WriteAsPropertyName for dictionary-key usage (Xunit2ResultFormat).
ToolOptions.Arguments.cs / .Secrets.cs JToken/JArray/JObject → JsonNode/JsonArray/JsonObject; .Value<T>()/.ToObject<T>().GetValue<T>()/.Deserialize<T>(). New NodeToString helper mirrors Newtonsoft's permissive ToObject<string> (STJ throws on non-string JsonValues). DeserializeWithCoercion narrows string→bool coercion to just the formatter-method path.
Options.Modify.cs Deep-clone via JsonSerializer.Serialize/Deserialize.
NpmVersionResolver, NuGetVersionResolver, NuGetPackageResolver, ProcessExtensions JObject.Parse / JsonConvert.DeserializeObject → JsonNode.Parse / JsonSerializer.Deserialize.
DelegateHelper.cs ParseCollection coerces both string and JsonElement — STJ round-trips IReadOnlyDictionary<string, object> values as JsonElement, not string.

Generator changes (#118)

File Change
DataClassGenerator.cs Emits [JsonPropertyName] instead of [JsonProperty].
ToolGenerator.cs Imports System.Text.Json.Serialization instead of Newtonsoft.Json.
ToolSerializer.cs STJ + TypeInfoModifier that mirrors the legacy CustomContractResolver. Two-pass shape (strip read-only properties, then per-property ShouldSerialize for empty collections + default-true ExtensionMethods) because STJ calls the getter BEFORE invoking ShouldSerialize, so a filter alone can't prevent NREs on uninitialized computed properties like SettingsClass.Name. Uses UnsafeRelaxedJsonEscaping to match Newtonsoft's default.
Model/*.cs [JsonRequired] / [JsonPropertyName] / [JsonIgnore] from System.Text.Json.Serialization.

All 62 *.Generated.cs tool wrappers regenerated. Source .json files unchanged (the TypeInfoModifier preserves Newtonsoft's null/default omission and escape behaviour byte-for-byte).

Package refs dropped

Test plan

  • dotnet build fallout.slnx — 0 errors (warnings unrelated to this PR).
  • dotnet test fallout.slnx — 425 pass / 0 fail across 10 test DLLs.
  • OptionsTest.TestSerialization Verify snapshot unchanged (STJ round-trip parity preserved).
  • ToolSerializer.Save byte-for-byte output parity on all 62 tool JSON specifications (no .json diffs after regen).
  • CI ubuntu-latest green.

Closes

Unblocks

🤖 Generated with Claude Code

ChrisonSimtian and others added 2 commits May 25, 2026 13:41
…118, closes #116)

The Fallout.Tooling engine and Fallout.Tooling.Generator are now STJ-native.
Closes the residual #116 tail (Object.ToJObject + Newtonsoft.Json PackageRef in
Fallout.Utilities.Text.Json). Fallout.Common still references Newtonsoft for the
*Tasks.cs files until #119 (STJ-6) lands.

Engine (#117):
- Options.InternalOptions: JObject → JsonObject.
- Options.SerializerOptions (JsonSerializerOptions) replaces Options.JsonSerializer
  + Options.JsonSerializerSettings.
- TypeConverter is now a JsonConverterFactory registered in SerializerOptions
  (STJ doesn't inherit class-level [JsonConverter] onto subclasses, so the
  attribute-only approach silently broke deserialization of all *Settings types).
- ObjectFromFieldConverter rewritten as JsonConverterFactory for LookupTable<,>.
- New EnumerationJsonConverterFactory bridges [TypeConverter]-attributed types
  (STJ ignores System.ComponentModel.TypeConverter). Implements ReadAsPropertyName/
  WriteAsPropertyName for dictionary-key usage (Xunit2ResultFormat).
- ToolOptions.Arguments/Secrets: JToken/JArray/JObject → JsonNode/JsonArray/JsonObject;
  .Value<T>()/.ToObject<T>() → .GetValue<T>()/.Deserialize<T>().
- DeserializeWithCoercion: narrow Newtonsoft-parity coercion for string→bool, only
  on the formatter-method path. STJ's native parser handles DateTime/numeric correctly.
- NodeToString: permissive equivalent of Newtonsoft's JToken.ToObject<string>().
- Options.Modify deep-clone via JsonSerializer.Serialize/Deserialize.
- NpmVersionResolver, NuGetVersionResolver, NuGetPackageResolver, ProcessExtensions
  all migrated off Newtonsoft.
- DelegateHelper.ParseCollection coerces JsonElement → string (STJ deserializes
  IReadOnlyDictionary<string, object> values as JsonElement, not string).

Generator (#118):
- DataClassGenerator emits [JsonPropertyName] instead of [JsonProperty];
  ToolGenerator imports System.Text.Json.Serialization instead of Newtonsoft.Json.
- ToolSerializer: System.Text.Json with a TypeInfoModifier that mirrors the legacy
  CustomContractResolver — skips read-only properties (except Tool.Schema), empty
  enumerables, and default-true SettingsClass.ExtensionMethods. Two-pass shape
  needed because STJ calls the getter BEFORE ShouldSerialize, so the filter alone
  can't prevent NREs on uninitialized computed properties; we strip read-only
  JsonPropertyInfo entries at modifier time.
- UnsafeRelaxedJsonEscaping to match Newtonsoft's default (no <,>,' escaping).
- Models (Tool/Property/DataClass/Enumeration/Task): [JsonRequired] /
  [JsonPropertyName] / [JsonIgnore].
- All 62 *.Generated.cs tool wrappers regenerated. Source .json files unchanged.

Package refs dropped:
- Newtonsoft.Json removed from Fallout.Tooling.csproj, Fallout.Tooling.Generator.csproj,
  and Fallout.Utilities.Text.Json.csproj.

Tests:
- OptionsTest, ToolOptionsArgumentsTest, SettingsTest migrated to STJ.
- TestSerialization Verify snapshot unchanged (parity preserved).
- All 425 tests pass across 10 DLLs.

Closes #116, #117, #118.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…teTools

#117/#118's GenerateTools run re-emitted the GitHubActions workflow YAML and
dropped the manual ref:github.head_ref pin that keeps HEAD attached on PR
builds. Without it, GitHubTasksTest.GitHubRepositoryFromLocalDirectoryTest
fails because repository.Branch resolves to null on detached-merge-SHA
checkout. The proper fix (teach the Fallout YAML generator about this
override) is tracked in #110.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ChrisonSimtian
ChrisonSimtian merged commit d12aa03 into main May 25, 2026
1 check passed
@ChrisonSimtian
ChrisonSimtian deleted the feature/117-118-stj4-tooling-engine branch May 25, 2026 02:02
ChrisonSimtian added a commit that referenced this pull request May 25, 2026
…the ref pin (#175)

The ubuntu-latest workflow YAML carried a manual ref:github.head_ref patch
that keeps HEAD attached on PR-triggered builds. Without it,
GitHubTasksTest.GitHubRepositoryFromLocalDirectoryTest fails because
GitRepository.FromLocalDirectory can't resolve a branch from a detached merge SHA
(seen on PR #172 when GenerateTools clobbered the patch and CI went red).

Fix: surface the actions/checkout 'ref' input as first-class config on
GitHubActionsCheckoutStep + a CheckoutRef setter on [GitHubActions]. The
ubuntu-latest attribute in Build.CI.GitHubActions.cs now sets
CheckoutRef = "${{ github.head_ref }}", so subsequent GenerateTools runs
re-emit the patch automatically instead of dropping it.

macos-latest and windows-latest are unchanged — they run on push-to-main where
HEAD is already attached on the requested commit.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant