Skip to content

Runtime plugin pattern: KnownSubTypeOf, RegisterSubtypeAssembly, RegisterDynamicSubtype - #211

Open
manuc66 wants to merge 6 commits into
masterfrom
feature/split/pr5-plugin-pattern
Open

manuc66 wants to merge 6 commits into
masterfrom
feature/split/pr5-plugin-pattern

Conversation

@manuc66

@manuc66 manuc66 commented Aug 16, 2026

Copy link
Copy Markdown
Owner

Problem

Runtime-loaded subtypes (plugins, assemblies loaded at runtime, config-driven types) could not self-declare in the STJ engine: the only cross-assembly mechanism was a process-wide assembly registry, and per-subtype runtime registration did not exist.

Fix

  • Replace the global JsonSubTypesTypeResolution.AddAssembly registry with a declarative [KnownSubTypeOtherAssembly("AssemblyName")] attribute on the base type (per-type, no process-wide leak; no compile-time reference to the plugin).
  • Add self-declaring subtypes via KnownSubTypeOf (with discriminator value or by type name) and KnownSubTypeWithPropertyOf (by property presence).
  • Add RegisterSubtypeAssembly(assembly) to scan a loaded assembly for those declarations.
  • Add RegisterDynamicSubtype(discriminator, type) to register a single subtype directly after the converter is built.
  • New JsonSubTypes.Text.Json.Tests.Plugin test project.

Tests

  • New plugin/self-declaring tests across STJ suites; full STJ suite passes (net8.0 and net10.0).

Honest note(s)

  • The generator cannot see runtime-loaded hierarchies; the converter is the only engine for them (documented in the README plugin section, added in a later PR).
  • RegisterDynamicSubtype mutates the mapping and is intended for setup time, not concurrent use with serialization.

…ypeResolution attribute

JsonSubTypesTypeResolution.AddAssembly was a process-wide mutable registry that
leaked state across serialization profiles. Replace it with
[JsonSubTypesTypeResolution("AssemblyName")] on the base type, cached per type.

The attribute takes an assembly name rather than a Type: a Type would force the
base to reference the plugin assembly, which together with the plugin's reference
to the base creates a project cycle. Naming the assembly keeps the base free of a
compile-time reference to the plugin.

The cross-assembly tests now use SharedAnimal (base) with the plugin assembly
declared on it and PluginDog (in the plugin project) resolved by name, plus a
negative test on a base without the attribute.
The attribute's previous name was inherited from the global AddAssembly registry
it replaced and said nothing about its contract. KnownSubTypeOtherAssembly is a
noun consistent with the KnownSubType family, and Other carries the extension
beyond the base type's own assembly. The file follows the class name.
…SubtypeAssembly

Support the inverse philosophy of the base-declares approach: the subtype declares
itself as a child of the base through [KnownSubTypeOf(typeof(Base), "value")],
and the host registers the plugin assembly at runtime through
RegisterSubtypeAssembly. This covers the real plugin scenario the attribute
cannot: assemblies loaded at runtime whose names are unknown at compile time.

The scan mirrors the runtime cross-assembly approach: register the assembly, scan
its types for self-declared subtypes, and map those that carry a discriminator
value; types without a value resolve by name in the registered assembly.
ReflectionTypeLoadException is handled so an assembly with unloadable optional
types is not fatal. Both philosophies coexist: the base can still name other
assemblies via [KnownSubTypeOtherAssembly].
The generator already exposed RegisterDynamicSubtype, but the runtime converter
did not: README claimed parity that did not exist. Add the same runtime hook to
JsonSubtypes<T>, mapping a discriminator to a subtype after the converter is
built, with last-registration-wins like the generator. This covers the simple
plugin need (register a subtype at runtime) without editing the plugin or
scanning an assembly, complementing RegisterSubtypeAssembly.

NullableDictionary gains a Set method for the overwrite semantics.
…ropertyOf

Mirror the KnownSubTypeOf work for property-presence discrimination. The subtype
declares itself through [KnownSubTypeWithPropertyOf(typeof(Base), "Property")]
and JsonSubtypesWithPropertyConverterBuilder.RegisterSubtypeAssembly scans the
registered assembly for those declarations, mapping the property presence. Same
ReflectionTypeLoadException handling as the value-based scan.
@manuc66
manuc66 changed the base branch from feature/split/pr4-migration-fixes to master October 7, 2026 20:30
@codecov

codecov Bot commented Oct 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.72093% with 14 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.30%. Comparing base (90098c4) to head (3427426).

Files with missing lines Patch % Lines
JsonSubTypes.Text.Json/JsonSubtypes.cs 75.00% 2 Missing and 2 partials ⚠️
...sonSubTypes.Text.Json/KnownSubTypeOtherAssembly.cs 87.50% 1 Missing and 2 partials ⚠️
JsonSubTypes.Text.Json/NullableDictionary.cs 40.00% 2 Missing and 1 partial ⚠️
...SubTypes.Text.Json/JsonSubtypesConverterBuilder.cs 88.23% 2 Missing ⚠️
...t.Json/JsonSubtypesWithPropertyConverterBuilder.cs 83.33% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #211      +/-   ##
==========================================
- Coverage   93.71%   93.30%   -0.41%     
==========================================
  Files          18       22       +4     
  Lines        1622     1688      +66     
  Branches      328      340      +12     
==========================================
+ Hits         1520     1575      +55     
- Misses         42       48       +6     
- Partials       60       65       +5     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

This branch has not been deployed

No deployments
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