Skip to content

feat: add LayeredCraft.OptimizedEnums.SystemTextJson package#3

Merged
ncipollina merged 14 commits intomainfrom
feat/systemtextjson-package
Mar 31, 2026
Merged

feat: add LayeredCraft.OptimizedEnums.SystemTextJson package#3
ncipollina merged 14 commits intomainfrom
feat/systemtextjson-package

Conversation

@ncipollina
Copy link
Copy Markdown
Contributor

Summary

Adds the LayeredCraft.OptimizedEnums.SystemTextJson package — a source-generated, zero-reflection JsonConverter for OptimizedEnum types. Decorate a class with [OptimizedEnumJsonConverter] and the generator emits a concrete, AOT-safe converter and stamps [JsonConverter] on the partial class automatically. No manual JsonSerializerOptions registration is ever needed.

The package declares LayeredCraft.OptimizedEnums as a NuGet dependency, so consumers only need a single dotnet add package.

Changes

New package — LayeredCraft.OptimizedEnums.SystemTextJson

  • OptimizedEnumJsonConverterGenerator — Roslyn IIncrementalGenerator with RegisterPostInitializationOutput to inject the attribute/enum source into consuming compilations
  • [OptimizedEnumJsonConverter(OptimizedEnumJsonConverterType.ByName | ByValue)] attribute
  • JsonConverterEmitter — uses Scriban template (Templates/JsonConverter.scriban) to emit a concrete non-generic JsonConverter<T> per decorated class
  • OE2001 diagnostic — class does not inherit OptimizedEnum<TEnum, TValue>
  • OE2002 diagnostic — class is not declared partial
  • Snapshot tests via Verify.SourceGenerators
  • Plain ProjectReference to main generator — Microsoft.CSharp.dll flows transitively via GetDependencyTargetPaths

Version & build

  • VersionPrefix bumped from 1.0.01.1.0
  • Directory.Packages.props: System.Text.Json bumped to 10.0.5 (required by Scriban 7.0.6 NuGet metadata); SCRIBAN_NO_SYSTEM_TEXT_JSON define prevents Scriban from using STJ at compile time

Documentation

  • docs/usage/json-serialization.md — new page covering installation, ByName/ByValue strategies, generated output, string-valued enums, AOT safety, diagnostics, and constraints
  • docs/advanced/diagnostics.md — added OE2001 and OE2002 SystemTextJson diagnostics section
  • docs/getting-started/installation.md — added optional STJ install section
  • docs/getting-started/quick-start.md — added step 6 with JSON serialization example
  • docs/changelog.md — added SystemTextJson entry under Unreleased
  • README.md — updated packages table with NuGet badge; added JSON Serialization section
  • mkdocs.yml — added json-serialization.md to Usage nav
  • LayeredCraft.OptimizedEnums.slnx — added json-serialization.md to solution folder

Validation

  • All existing generator snapshot tests pass
  • New LayeredCraft.OptimizedEnums.SystemTextJson.Tests snapshot tests pass for ByName, ByValue, nested types, OE2001, and OE2002 diagnostics
  • Confirmed single-package install: referencing only LayeredCraft.OptimizedEnums.SystemTextJson brings in the core package automatically

Release Notes

New package: LayeredCraft.OptimizedEnums.SystemTextJson (1.1.0)

Add source-generated, AOT-safe System.Text.Json converters to your OptimizedEnum types with a single attribute:

[OptimizedEnumJsonConverter(OptimizedEnumJsonConverterType.ByName)]
public sealed partial class OrderStatus : OptimizedEnum<OrderStatus, int> { ... }

Install with dotnet add package LayeredCraft.OptimizedEnums.SystemTextJson — the core package is pulled in automatically.

🤖 Generated with Claude Code

ncipollina and others added 4 commits March 30, 2026 22:13
Adds a new source generator package that emits zero-reflection, AOT-safe
System.Text.Json converters for OptimizedEnum types. Decorate any
OptimizedEnum subclass with [OptimizedEnumJsonConverter(ByName|ByValue)]
and the generator stamps [JsonConverter] on a generated partial class and
emits a concrete, non-generic converter calling TryFromName/TryFromValue
directly.

The attribute and OptimizedEnumJsonConverterType enum are injected into
consuming compilations via RegisterPostInitializationOutput (no separate
runtime DLL needed). Converter code is rendered via a Scriban template
consistent with the main generator. Two diagnostics are emitted for
invalid usage: OE2001 (must inherit OptimizedEnum) and OE2002 (must be
partial). All 21 snapshot tests pass across net8.0/net9.0/net10.0.

Also bumps System.Text.Json to 10.0.5 (Scriban 7.0.6 requires it) and
adds SCRIBAN_NO_SYSTEM_TEXT_JSON to both generator projects so Scriban's
STJ integration code is compiled out.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…he STJ package

Adds a ProjectReference (ReferenceOutputAssembly=false) from the STJ
generator to the main generator so that NuGet pack includes
LayeredCraft.OptimizedEnums in the .nuspec dependency group. Consumers
now only need to install LayeredCraft.OptimizedEnums.SystemTextJson and
the core package is pulled in automatically.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…dep from STJ generator

The plain ProjectReference to the main generator caused CS1703 because
both projects declared Microsoft.CSharp, resulting in both lib/ and ref/
versions being added to the compile references. Since the main generator's
GetDependencyTargetPaths target makes Microsoft.CSharp.dll available
transitively, the STJ generator no longer needs its own direct reference,
pack item, or GetDependencyTargetPaths target for it.

The ProjectReference (without ReferenceOutputAssembly=false) now correctly
declares LayeredCraft.OptimizedEnums as a public NuGet dependency, so
consumers only need to install LayeredCraft.OptimizedEnums.SystemTextJson.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add docs/usage/json-serialization.md covering ByName/ByValue strategies,
  what gets generated, string-valued enums, AOT safety, and OE2xxx diagnostics
- Update README with SystemTextJson NuGet badge and JSON serialization section
- Update installation, quick-start, diagnostics, and changelog docs
- Add json-serialization.md to mkdocs.yml nav and .slnx solution folder
- Bump VersionPrefix from 1.0.0 to 1.1.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ncipollina ncipollina requested a review from Copilot March 31, 2026 11:47
@ncipollina
Copy link
Copy Markdown
Contributor Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 79dea795ff

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new LayeredCraft.OptimizedEnums.SystemTextJson source-generator package that emits per-type System.Text.Json converters for OptimizedEnum classes via a new [OptimizedEnumJsonConverter] attribute, plus tests and documentation.

Changes:

  • Introduces LayeredCraft.OptimizedEnums.SystemTextJson.Generator incremental generator + Scriban template to emit [JsonConverter]-stamped partial stubs and concrete converters.
  • Adds snapshot-based generator tests (Verify.SourceGenerators) for ByName, ByValue, and error diagnostics (OE2001/OE2002).
  • Updates docs/README and bumps repo version + System.Text.Json package version.

Reviewed changes

Copilot reviewed 51 out of 51 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/LayeredCraft.OptimizedEnums.SystemTextJson.Tests/xunit.runner.json xUnit runner schema config for new test project
tests/LayeredCraft.OptimizedEnums.SystemTextJson.Tests/Snapshots/GeneratorVerifyTests.Error_NotPartial#OptimizedEnumJsonConverterAttribute.g.verified.cs Snapshot: post-init emitted attribute source
tests/LayeredCraft.OptimizedEnums.SystemTextJson.Tests/Snapshots/GeneratorVerifyTests.Error_NotPartial.verified.txt Snapshot: expected diagnostics for non-partial usage
tests/LayeredCraft.OptimizedEnums.SystemTextJson.Tests/Snapshots/GeneratorVerifyTests.Error_NotOptimizedEnum#OptimizedEnumJsonConverterAttribute.g.verified.cs Snapshot: post-init emitted attribute source
tests/LayeredCraft.OptimizedEnums.SystemTextJson.Tests/Snapshots/GeneratorVerifyTests.Error_NotOptimizedEnum.verified.txt Snapshot: expected diagnostics for non-OptimizedEnum usage
tests/LayeredCraft.OptimizedEnums.SystemTextJson.Tests/Snapshots/GeneratorVerifyTests.ByValue_WithNamespace#OptimizedEnumJsonConverterAttribute.g.verified.cs Snapshot: post-init emitted attribute source
tests/LayeredCraft.OptimizedEnums.SystemTextJson.Tests/Snapshots/GeneratorVerifyTests.ByValue_WithNamespace#MyApp.Domain.OrderStatus.SystemTextJson.g.verified.cs Snapshot: generated ByValue converter output
tests/LayeredCraft.OptimizedEnums.SystemTextJson.Tests/Snapshots/GeneratorVerifyTests.ByValue_WithNamespace#MyApp.Domain.OrderStatus.g.verified.cs Snapshot: core generator output (dependency behavior)
tests/LayeredCraft.OptimizedEnums.SystemTextJson.Tests/Snapshots/GeneratorVerifyTests.ByValue_StringValueType#OptimizedEnumJsonConverterAttribute.g.verified.cs Snapshot: post-init emitted attribute source
tests/LayeredCraft.OptimizedEnums.SystemTextJson.Tests/Snapshots/GeneratorVerifyTests.ByValue_StringValueType#MyApp.Domain.Color.SystemTextJson.g.verified.cs Snapshot: generated ByValue converter for string TValue
tests/LayeredCraft.OptimizedEnums.SystemTextJson.Tests/Snapshots/GeneratorVerifyTests.ByValue_StringValueType#MyApp.Domain.Color.g.verified.cs Snapshot: core generator output for string TValue
tests/LayeredCraft.OptimizedEnums.SystemTextJson.Tests/Snapshots/GeneratorVerifyTests.ByName_WithNamespace#OptimizedEnumJsonConverterAttribute.g.verified.cs Snapshot: post-init emitted attribute source
tests/LayeredCraft.OptimizedEnums.SystemTextJson.Tests/Snapshots/GeneratorVerifyTests.ByName_WithNamespace#MyApp.Domain.OrderStatus.SystemTextJson.g.verified.cs Snapshot: generated ByName converter output
tests/LayeredCraft.OptimizedEnums.SystemTextJson.Tests/Snapshots/GeneratorVerifyTests.ByName_WithNamespace#MyApp.Domain.OrderStatus.g.verified.cs Snapshot: core generator output (dependency behavior)
tests/LayeredCraft.OptimizedEnums.SystemTextJson.Tests/Snapshots/GeneratorVerifyTests.ByName_StringValueType#OptimizedEnumJsonConverterAttribute.g.verified.cs Snapshot: post-init emitted attribute source
tests/LayeredCraft.OptimizedEnums.SystemTextJson.Tests/Snapshots/GeneratorVerifyTests.ByName_StringValueType#MyApp.Domain.Color.SystemTextJson.g.verified.cs Snapshot: generated ByName converter for string TValue
tests/LayeredCraft.OptimizedEnums.SystemTextJson.Tests/Snapshots/GeneratorVerifyTests.ByName_StringValueType#MyApp.Domain.Color.g.verified.cs Snapshot: core generator output for string TValue
tests/LayeredCraft.OptimizedEnums.SystemTextJson.Tests/Snapshots/GeneratorVerifyTests.ByName_GlobalNamespace#Priority.SystemTextJson.g.verified.cs Snapshot: generated ByName converter for global namespace
tests/LayeredCraft.OptimizedEnums.SystemTextJson.Tests/Snapshots/GeneratorVerifyTests.ByName_GlobalNamespace#Priority.g.verified.cs Snapshot: core generator output for global namespace
tests/LayeredCraft.OptimizedEnums.SystemTextJson.Tests/Snapshots/GeneratorVerifyTests.ByName_GlobalNamespace#OptimizedEnumJsonConverterAttribute.g.verified.cs Snapshot: post-init emitted attribute source
tests/LayeredCraft.OptimizedEnums.SystemTextJson.Tests/ModuleInitializer.cs Initializes Verify.SourceGenerators in test module
tests/LayeredCraft.OptimizedEnums.SystemTextJson.Tests/LayeredCraft.OptimizedEnums.SystemTextJson.Tests.csproj New multi-TFM test project wiring refs + packages
tests/LayeredCraft.OptimizedEnums.SystemTextJson.Tests/GeneratorVerifyTests.cs Snapshot tests for new STJ generator behaviors
tests/LayeredCraft.OptimizedEnums.SystemTextJson.Tests/GeneratorTestHelpers.cs Roslyn compilation + generator driver harness for snapshots
src/LayeredCraft.OptimizedEnums.SystemTextJson.Generator/TrackingNames.cs Incremental generator tracking names
src/LayeredCraft.OptimizedEnums.SystemTextJson.Generator/Templates/JsonConverter.scriban Scriban template for converter + partial stub emission
src/LayeredCraft.OptimizedEnums.SystemTextJson.Generator/Providers/JsonConverterSyntaxProvider.cs Syntax provider extracts decorated classes + validates usage
src/LayeredCraft.OptimizedEnums.SystemTextJson.Generator/OptimizedEnumJsonConverterType.cs Internal enum mirror used by generator pipeline
src/LayeredCraft.OptimizedEnums.SystemTextJson.Generator/OptimizedEnumJsonConverterGenerator.cs New incremental generator entry point
src/LayeredCraft.OptimizedEnums.SystemTextJson.Generator/Models/LocationInfo.cs Location model + extension helpers for diagnostics
src/LayeredCraft.OptimizedEnums.SystemTextJson.Generator/Models/JsonConverterInfo.cs Immutable model driving emission
src/LayeredCraft.OptimizedEnums.SystemTextJson.Generator/Models/EquatableArray.cs Equatable array utility for incremental cache friendliness
src/LayeredCraft.OptimizedEnums.SystemTextJson.Generator/LayeredCraft.OptimizedEnums.SystemTextJson.Generator.csproj New packaged analyzer/generator project definition
src/LayeredCraft.OptimizedEnums.SystemTextJson.Generator/Emitters/TemplateHelper.cs Loads/caches embedded Scriban templates
src/LayeredCraft.OptimizedEnums.SystemTextJson.Generator/Emitters/JsonConverterEmitter.cs Builds model + emits converter source; reports internal errors
src/LayeredCraft.OptimizedEnums.SystemTextJson.Generator/Diagnostics/DiagnosticInfo.cs Diagnostic wrapper + reporting helpers
src/LayeredCraft.OptimizedEnums.SystemTextJson.Generator/Diagnostics/DiagnosticDescriptors.cs OE2001/OE2002 descriptors for generator validation
src/LayeredCraft.OptimizedEnums.SystemTextJson.Generator/AttributeSource.cs Post-init injected attribute/enum source text
src/LayeredCraft.OptimizedEnums.SystemTextJson.Generator/AnalyzerReleases.Unshipped.md Adds release-tracking entries for OE2001/OE2002
src/LayeredCraft.OptimizedEnums.SystemTextJson.Generator/AnalyzerReleases.Shipped.md Initializes shipped rules file
src/LayeredCraft.OptimizedEnums.Generator/LayeredCraft.OptimizedEnums.Generator.csproj Adds SCRIBAN_NO_SYSTEM_TEXT_JSON define for Scriban build behavior
README.md Documents new STJ package + usage snippet
mkdocs.yml Adds JSON serialization doc page to nav
LayeredCraft.OptimizedEnums.slnx Adds new generator + test project + doc file to solution
docs/usage/json-serialization.md New usage guide for STJ converter generation
docs/getting-started/quick-start.md Adds quick-start section demonstrating JSON serialization
docs/getting-started/installation.md Adds optional STJ install instructions
docs/changelog.md Notes new STJ package under Unreleased
docs/advanced/diagnostics.md Documents new OE2001/OE2002 diagnostics
Directory.Packages.props Pins System.Text.Json version for repo
Directory.Build.props Bumps VersionPrefix to 1.1.0

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

ncipollina and others added 2 commits March 31, 2026 08:34
- Switch CreateSyntaxProvider to ForAttributeWithMetadataName to prevent
  duplicate emissions when a partial class has the attribute on one declaration
  and an unrelated attribute on another
- Expose AttributeMetadataName as internal const for use in generator registration
- Remove manual attribute lookup in Transform; use context.Attributes[0] directly
- Remove unused System.Collections.Immutable using
- Fix OE2001/OE2002 diagnostic messages in docs to match exact MessageFormat strings
- Clarify AOT section: converter logic is reflection-free, but STJ uses
  Activator.CreateInstance for converter instantiation unless JsonSerializerContext
  is used; add note explaining how to eliminate that in NativeAOT scenarios

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…type test

- Add ValueTypeIsReferenceType to JsonConverterInfo model; populate from
  ITypeSymbol.IsReferenceType in provider; thread through emitter model
- Conditionalize null token guard in JsonConverter.scriban on
  value_type_is_reference_type to fix ArgumentNullException crash when
  TValue is a reference type (e.g. string) and JSON token is null
- Add OE9001 to AnalyzerReleases.Unshipped.md to fix pre-existing RS2000 error
- Add ByName_NestedType verify test and snapshots to cover containing-type
  preamble/suffix code path
- Update ByValue_WithNamespace and ByValue_StringValueType snapshots to
  reflect null-guard changes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ncipollina ncipollina added the 💡 idea Idea or proposal for future work label Mar 31, 2026
@ncipollina ncipollina requested a review from Copilot March 31, 2026 12:37
@ncipollina
Copy link
Copy Markdown
Contributor Author

@codex review

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 55 out of 55 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 76866864cb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

ncipollina and others added 2 commits March 31, 2026 08:59
- Promote OE9002 to static DiagnosticDescriptors field; remove inline
  descriptor from JsonConverterEmitter catch block
- Add OE2003 diagnostic for unknown OptimizedEnumJsonConverterType values;
  validate rawValue against defined enum members in provider and return early
  with error diagnostic instead of silently casting
- Add OE9002 and OE2003 to AnalyzerReleases.Unshipped.md
- Change TemplateHelper resource name matching from InvariantCulture to
  Ordinal; manifest resource names are identifiers, not locale-sensitive text
- Add HandleNull => true override to generated converter so STJ always
  invokes Read for null JSON tokens rather than silently producing null,
  ensuring non-null enum assumptions are enforced at deserialization
- Document OE2003 in docs/advanced/diagnostics.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ncipollina
Copy link
Copy Markdown
Contributor Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cfa80f9608

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Setting HandleNull to true caused Read to throw on JsonTokenType.Null
and Write to dereference value without a null guard, breaking nullable
scenarios like OrderStatus? properties. STJ's default HandleNull
behavior (false for reference types) correctly handles null
tokens/values without involving the converter.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 55 out of 55 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@j-d-ha j-d-ha left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is awesome!!!!

I left 2 non-blocking suggestions.

mkdocs.yml Outdated
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that mkdocs is more or less unmaintained, maybe look at this: https://zensical.org/

ncipollina and others added 2 commits March 31, 2026 09:19
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add zensical.toml with full config converted from mkdocs.yml
- Add pyproject.toml with zensical dependency for uv package management
- Update docs workflow to use uv + zensical build instead of pip + mkdocs
- Remove mkdocs.yml and requirements.txt
- Update .slnx to reference new config files

Note: run `uv lock` locally and commit uv.lock to enable --locked installs in CI

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ncipollina and others added 2 commits March 31, 2026 09:54
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ncipollina ncipollina merged commit 958a20a into main Mar 31, 2026
3 checks passed
@ncipollina ncipollina deleted the feat/systemtextjson-package branch March 31, 2026 14:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💡 idea Idea or proposal for future work

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants