Add OnUnresolvedSubtype callback to detect unregistered subtypes (#112) - #193

Open
manuc66 wants to merge 4 commits into
masterfrom
feature/error-paths-in-exceptions
Open

Add OnUnresolvedSubtype callback to detect unregistered subtypes (#112)#193
manuc66 wants to merge 4 commits into
masterfrom
feature/error-paths-in-exceptions

Conversation

@manuc66

@manuc66manuc66 commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Closes#112

Problem

The reporter of #112 wants to detect, while deserializing a polymorphic tree, every object whose Type discriminator is not registered — without throwing on the first mismatch. Today the only way is a "trap" fallback type: its constructor records each unknown Type into a static list, which is not thread-safe and requires cleanup after deserialization.

Fix

Adds OnUnresolvedSubtype to all four builders (JsonSubtypesConverterBuilder / JsonSubtypesWithPropertyConverterBuilder, in both JsonSubTypes and JsonSubTypes.Text.Json). The callback fires once per JSON element whose subtype cannot be resolved: unknown or missing discriminator value, or no matching property in property-presence mode.

The hook sits at the existing single failure point in both backends (resolvedType ?? GetFallbackSubType(...)), so nothing changes when no callback is registered. UnresolvedSubtypeInfo exposes the parent type, the discriminator name and value, a HasDiscriminator flag (to distinguish a missing discriminator from an unknown value, both otherwise read as null) and the fallback subtype that will be used (null when none is configured).

Tests

  • JsonSubTypes.Text.Json.Tests: 139 pass on net8.0 and net10.0. Six new tests: no callback when the subtype resolves, unknown value, missing discriminator, no fallback configured, one callback per unresolved element in a nested tree, property-presence mode.
  • JsonSubTypes.Tests (net46): compiles, but the NUnit suite cannot execute under Linux/VSTest, so I validated the same six scenarios through a net8.0 harness against the netstandard2.0 build (all pass).

Honest notes

  • The callback runs on the deserializing thread. A converter shared across threads calls it from several threads, so the callback must be thread-safe; I did not add locking or weak-event machinery.
  • JsonPath is available in the Newtonsoft backend but not in System.Text.Json (resolution works on a JsonDocument, which carries no path), so I omitted it from the info type.
  • The callback cannot override the resolved type. That would be a separate custom-type-resolver feature; say so here if you actually need it.

Feedback welcome on the API shape (method name, and what UnresolvedSubtypeInfo exposes).

Fires once per JSON element whose subtype cannot be resolved (unknown or
missing discriminator value, or no matching property in property-presence
mode), on both the Newtonsoft.Json and System.Text.Json backends. The
callback receives the parent type, discriminator name/value, whether the
discriminator was present, and the fallback subtype that will be used.
Extends the existing fallback point in ResolveType/GetType so no behavior
changes when no callback is registered. #112
Covers resolved subtype (no callback), unknown discriminator value, missing
discriminator, no configured fallback, one callback per unresolved element in
a nested tree, and property-presence mode. #112
…olvedSubtypeInfo
The public API keeps the README as its documentation; no XML doc comments on
fluent builder methods or value classes, consistent with the rest of the repo.
Sign up for freeto 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.

Detect all unregistered subtypes for a given json?

1 participant

@manuc66
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Add OnUnresolvedSubtype callback to detect unregistered subtypes (#112) - #193

Open
manuc66 wants to merge 4 commits into
masterfrom
feature/error-paths-in-exceptions
Open

Add OnUnresolvedSubtype callback to detect unregistered subtypes (#112)#193
manuc66 wants to merge 4 commits into
masterfrom
feature/error-paths-in-exceptions

Conversation

@manuc66

@manuc66manuc66 commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Closes#112

Problem

The reporter of #112 wants to detect, while deserializing a polymorphic tree, every object whose Type discriminator is not registered — without throwing on the first mismatch. Today the only way is a "trap" fallback type: its constructor records each unknown Type into a static list, which is not thread-safe and requires cleanup after deserialization.

Fix

Adds OnUnresolvedSubtype to all four builders (JsonSubtypesConverterBuilder / JsonSubtypesWithPropertyConverterBuilder, in both JsonSubTypes and JsonSubTypes.Text.Json). The callback fires once per JSON element whose subtype cannot be resolved: unknown or missing discriminator value, or no matching property in property-presence mode.

The hook sits at the existing single failure point in both backends (resolvedType ?? GetFallbackSubType(...)), so nothing changes when no callback is registered. UnresolvedSubtypeInfo exposes the parent type, the discriminator name and value, a HasDiscriminator flag (to distinguish a missing discriminator from an unknown value, both otherwise read as null) and the fallback subtype that will be used (null when none is configured).

Tests

  • JsonSubTypes.Text.Json.Tests: 139 pass on net8.0 and net10.0. Six new tests: no callback when the subtype resolves, unknown value, missing discriminator, no fallback configured, one callback per unresolved element in a nested tree, property-presence mode.
  • JsonSubTypes.Tests (net46): compiles, but the NUnit suite cannot execute under Linux/VSTest, so I validated the same six scenarios through a net8.0 harness against the netstandard2.0 build (all pass).

Honest notes

  • The callback runs on the deserializing thread. A converter shared across threads calls it from several threads, so the callback must be thread-safe; I did not add locking or weak-event machinery.
  • JsonPath is available in the Newtonsoft backend but not in System.Text.Json (resolution works on a JsonDocument, which carries no path), so I omitted it from the info type.
  • The callback cannot override the resolved type. That would be a separate custom-type-resolver feature; say so here if you actually need it.

Feedback welcome on the API shape (method name, and what UnresolvedSubtypeInfo exposes).

Fires once per JSON element whose subtype cannot be resolved (unknown or
missing discriminator value, or no matching property in property-presence
mode), on both the Newtonsoft.Json and System.Text.Json backends. The
callback receives the parent type, discriminator name/value, whether the
discriminator was present, and the fallback subtype that will be used.
Extends the existing fallback point in ResolveType/GetType so no behavior
changes when no callback is registered. #112
Covers resolved subtype (no callback), unknown discriminator value, missing
discriminator, no configured fallback, one callback per unresolved element in
a nested tree, and property-presence mode. #112
…olvedSubtypeInfo
The public API keeps the README as its documentation; no XML doc comments on
fluent builder methods or value classes, consistent with the rest of the repo.
Sign up for freeto 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.

Detect all unregistered subtypes for a given json?

1 participant

@manuc66
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add OnUnresolvedSubtype callback to detect unregistered subtypes (#112) - #193

Open
manuc66 wants to merge 4 commits into
masterfrom
feature/error-paths-in-exceptions
Open

Add OnUnresolvedSubtype callback to detect unregistered subtypes (#112)#193
manuc66 wants to merge 4 commits into
masterfrom
feature/error-paths-in-exceptions

Conversation

@manuc66

@manuc66manuc66 commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Closes#112

Problem

The reporter of #112 wants to detect, while deserializing a polymorphic tree, every object whose Type discriminator is not registered — without throwing on the first mismatch. Today the only way is a "trap" fallback type: its constructor records each unknown Type into a static list, which is not thread-safe and requires cleanup after deserialization.

Fix

Adds OnUnresolvedSubtype to all four builders (JsonSubtypesConverterBuilder / JsonSubtypesWithPropertyConverterBuilder, in both JsonSubTypes and JsonSubTypes.Text.Json). The callback fires once per JSON element whose subtype cannot be resolved: unknown or missing discriminator value, or no matching property in property-presence mode.

The hook sits at the existing single failure point in both backends (resolvedType ?? GetFallbackSubType(...)), so nothing changes when no callback is registered. UnresolvedSubtypeInfo exposes the parent type, the discriminator name and value, a HasDiscriminator flag (to distinguish a missing discriminator from an unknown value, both otherwise read as null) and the fallback subtype that will be used (null when none is configured).

Tests

  • JsonSubTypes.Text.Json.Tests: 139 pass on net8.0 and net10.0. Six new tests: no callback when the subtype resolves, unknown value, missing discriminator, no fallback configured, one callback per unresolved element in a nested tree, property-presence mode.
  • JsonSubTypes.Tests (net46): compiles, but the NUnit suite cannot execute under Linux/VSTest, so I validated the same six scenarios through a net8.0 harness against the netstandard2.0 build (all pass).

Honest notes

  • The callback runs on the deserializing thread. A converter shared across threads calls it from several threads, so the callback must be thread-safe; I did not add locking or weak-event machinery.
  • JsonPath is available in the Newtonsoft backend but not in System.Text.Json (resolution works on a JsonDocument, which carries no path), so I omitted it from the info type.
  • The callback cannot override the resolved type. That would be a separate custom-type-resolver feature; say so here if you actually need it.

Feedback welcome on the API shape (method name, and what UnresolvedSubtypeInfo exposes).

Fires once per JSON element whose subtype cannot be resolved (unknown or
missing discriminator value, or no matching property in property-presence
mode), on both the Newtonsoft.Json and System.Text.Json backends. The
callback receives the parent type, discriminator name/value, whether the
discriminator was present, and the fallback subtype that will be used.
Extends the existing fallback point in ResolveType/GetType so no behavior
changes when no callback is registered. #112
Covers resolved subtype (no callback), unknown discriminator value, missing
discriminator, no configured fallback, one callback per unresolved element in
a nested tree, and property-presence mode. #112
…olvedSubtypeInfo
The public API keeps the README as its documentation; no XML doc comments on
fluent builder methods or value classes, consistent with the rest of the repo.
Sign up for freeto 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.

Detect all unregistered subtypes for a given json?

1 participant

@manuc66
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add OnUnresolvedSubtype callback to detect unregistered subtypes (#112) - #193

Open
manuc66 wants to merge 4 commits into
masterfrom
feature/error-paths-in-exceptions
Open

Add OnUnresolvedSubtype callback to detect unregistered subtypes (#112)#193
manuc66 wants to merge 4 commits into
masterfrom
feature/error-paths-in-exceptions

Conversation

@manuc66

@manuc66manuc66 commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Closes#112

Problem

The reporter of #112 wants to detect, while deserializing a polymorphic tree, every object whose Type discriminator is not registered — without throwing on the first mismatch. Today the only way is a "trap" fallback type: its constructor records each unknown Type into a static list, which is not thread-safe and requires cleanup after deserialization.

Fix

Adds OnUnresolvedSubtype to all four builders (JsonSubtypesConverterBuilder / JsonSubtypesWithPropertyConverterBuilder, in both JsonSubTypes and JsonSubTypes.Text.Json). The callback fires once per JSON element whose subtype cannot be resolved: unknown or missing discriminator value, or no matching property in property-presence mode.

The hook sits at the existing single failure point in both backends (resolvedType ?? GetFallbackSubType(...)), so nothing changes when no callback is registered. UnresolvedSubtypeInfo exposes the parent type, the discriminator name and value, a HasDiscriminator flag (to distinguish a missing discriminator from an unknown value, both otherwise read as null) and the fallback subtype that will be used (null when none is configured).

Tests

  • JsonSubTypes.Text.Json.Tests: 139 pass on net8.0 and net10.0. Six new tests: no callback when the subtype resolves, unknown value, missing discriminator, no fallback configured, one callback per unresolved element in a nested tree, property-presence mode.
  • JsonSubTypes.Tests (net46): compiles, but the NUnit suite cannot execute under Linux/VSTest, so I validated the same six scenarios through a net8.0 harness against the netstandard2.0 build (all pass).

Honest notes

  • The callback runs on the deserializing thread. A converter shared across threads calls it from several threads, so the callback must be thread-safe; I did not add locking or weak-event machinery.
  • JsonPath is available in the Newtonsoft backend but not in System.Text.Json (resolution works on a JsonDocument, which carries no path), so I omitted it from the info type.
  • The callback cannot override the resolved type. That would be a separate custom-type-resolver feature; say so here if you actually need it.

Feedback welcome on the API shape (method name, and what UnresolvedSubtypeInfo exposes).

Fires once per JSON element whose subtype cannot be resolved (unknown or
missing discriminator value, or no matching property in property-presence
mode), on both the Newtonsoft.Json and System.Text.Json backends. The
callback receives the parent type, discriminator name/value, whether the
discriminator was present, and the fallback subtype that will be used.
Extends the existing fallback point in ResolveType/GetType so no behavior
changes when no callback is registered. #112
Covers resolved subtype (no callback), unknown discriminator value, missing
discriminator, no configured fallback, one callback per unresolved element in
a nested tree, and property-presence mode. #112
…olvedSubtypeInfo
The public API keeps the README as its documentation; no XML doc comments on
fluent builder methods or value classes, consistent with the rest of the repo.
Sign up for freeto 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.

Detect all unregistered subtypes for a given json?

1 participant

@manuc66
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Add OnUnresolvedSubtype callback to detect unregistered subtypes (#112) - #193

Open
manuc66 wants to merge 4 commits into
masterfrom
feature/error-paths-in-exceptions
Open

Add OnUnresolvedSubtype callback to detect unregistered subtypes (#112)#193
manuc66 wants to merge 4 commits into
masterfrom
feature/error-paths-in-exceptions

Conversation

@manuc66

@manuc66manuc66 commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Closes#112

Problem

The reporter of #112 wants to detect, while deserializing a polymorphic tree, every object whose Type discriminator is not registered — without throwing on the first mismatch. Today the only way is a "trap" fallback type: its constructor records each unknown Type into a static list, which is not thread-safe and requires cleanup after deserialization.

Fix

Adds OnUnresolvedSubtype to all four builders (JsonSubtypesConverterBuilder / JsonSubtypesWithPropertyConverterBuilder, in both JsonSubTypes and JsonSubTypes.Text.Json). The callback fires once per JSON element whose subtype cannot be resolved: unknown or missing discriminator value, or no matching property in property-presence mode.

The hook sits at the existing single failure point in both backends (resolvedType ?? GetFallbackSubType(...)), so nothing changes when no callback is registered. UnresolvedSubtypeInfo exposes the parent type, the discriminator name and value, a HasDiscriminator flag (to distinguish a missing discriminator from an unknown value, both otherwise read as null) and the fallback subtype that will be used (null when none is configured).

Tests

  • JsonSubTypes.Text.Json.Tests: 139 pass on net8.0 and net10.0. Six new tests: no callback when the subtype resolves, unknown value, missing discriminator, no fallback configured, one callback per unresolved element in a nested tree, property-presence mode.
  • JsonSubTypes.Tests (net46): compiles, but the NUnit suite cannot execute under Linux/VSTest, so I validated the same six scenarios through a net8.0 harness against the netstandard2.0 build (all pass).

Honest notes

  • The callback runs on the deserializing thread. A converter shared across threads calls it from several threads, so the callback must be thread-safe; I did not add locking or weak-event machinery.
  • JsonPath is available in the Newtonsoft backend but not in System.Text.Json (resolution works on a JsonDocument, which carries no path), so I omitted it from the info type.
  • The callback cannot override the resolved type. That would be a separate custom-type-resolver feature; say so here if you actually need it.

Feedback welcome on the API shape (method name, and what UnresolvedSubtypeInfo exposes).

Fires once per JSON element whose subtype cannot be resolved (unknown or
missing discriminator value, or no matching property in property-presence
mode), on both the Newtonsoft.Json and System.Text.Json backends. The
callback receives the parent type, discriminator name/value, whether the
discriminator was present, and the fallback subtype that will be used.
Extends the existing fallback point in ResolveType/GetType so no behavior
changes when no callback is registered. #112
Covers resolved subtype (no callback), unknown discriminator value, missing
discriminator, no configured fallback, one callback per unresolved element in
a nested tree, and property-presence mode. #112
…olvedSubtypeInfo
The public API keeps the README as its documentation; no XML doc comments on
fluent builder methods or value classes, consistent with the rest of the repo.
Sign up for freeto 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.

Detect all unregistered subtypes for a given json?

1 participant

@manuc66
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add OnUnresolvedSubtype callback to detect unregistered subtypes (#112) - #193

Open
manuc66 wants to merge 4 commits into
masterfrom
feature/error-paths-in-exceptions
Open

Add OnUnresolvedSubtype callback to detect unregistered subtypes (#112)#193
manuc66 wants to merge 4 commits into
masterfrom
feature/error-paths-in-exceptions

Conversation

@manuc66

@manuc66manuc66 commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Closes#112

Problem

The reporter of #112 wants to detect, while deserializing a polymorphic tree, every object whose Type discriminator is not registered — without throwing on the first mismatch. Today the only way is a "trap" fallback type: its constructor records each unknown Type into a static list, which is not thread-safe and requires cleanup after deserialization.

Fix

Adds OnUnresolvedSubtype to all four builders (JsonSubtypesConverterBuilder / JsonSubtypesWithPropertyConverterBuilder, in both JsonSubTypes and JsonSubTypes.Text.Json). The callback fires once per JSON element whose subtype cannot be resolved: unknown or missing discriminator value, or no matching property in property-presence mode.

The hook sits at the existing single failure point in both backends (resolvedType ?? GetFallbackSubType(...)), so nothing changes when no callback is registered. UnresolvedSubtypeInfo exposes the parent type, the discriminator name and value, a HasDiscriminator flag (to distinguish a missing discriminator from an unknown value, both otherwise read as null) and the fallback subtype that will be used (null when none is configured).

Tests

  • JsonSubTypes.Text.Json.Tests: 139 pass on net8.0 and net10.0. Six new tests: no callback when the subtype resolves, unknown value, missing discriminator, no fallback configured, one callback per unresolved element in a nested tree, property-presence mode.
  • JsonSubTypes.Tests (net46): compiles, but the NUnit suite cannot execute under Linux/VSTest, so I validated the same six scenarios through a net8.0 harness against the netstandard2.0 build (all pass).

Honest notes

  • The callback runs on the deserializing thread. A converter shared across threads calls it from several threads, so the callback must be thread-safe; I did not add locking or weak-event machinery.
  • JsonPath is available in the Newtonsoft backend but not in System.Text.Json (resolution works on a JsonDocument, which carries no path), so I omitted it from the info type.
  • The callback cannot override the resolved type. That would be a separate custom-type-resolver feature; say so here if you actually need it.

Feedback welcome on the API shape (method name, and what UnresolvedSubtypeInfo exposes).

Fires once per JSON element whose subtype cannot be resolved (unknown or
missing discriminator value, or no matching property in property-presence
mode), on both the Newtonsoft.Json and System.Text.Json backends. The
callback receives the parent type, discriminator name/value, whether the
discriminator was present, and the fallback subtype that will be used.
Extends the existing fallback point in ResolveType/GetType so no behavior
changes when no callback is registered. #112
Covers resolved subtype (no callback), unknown discriminator value, missing
discriminator, no configured fallback, one callback per unresolved element in
a nested tree, and property-presence mode. #112
…olvedSubtypeInfo
The public API keeps the README as its documentation; no XML doc comments on
fluent builder methods or value classes, consistent with the rest of the repo.
Sign up for freeto 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.

Detect all unregistered subtypes for a given json?

1 participant

@manuc66
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add OnUnresolvedSubtype callback to detect unregistered subtypes (#112) - #193

Open
manuc66 wants to merge 4 commits into
masterfrom
feature/error-paths-in-exceptions
Open

Add OnUnresolvedSubtype callback to detect unregistered subtypes (#112)#193
manuc66 wants to merge 4 commits into
masterfrom
feature/error-paths-in-exceptions

Conversation

@manuc66

@manuc66manuc66 commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Closes#112

Problem

The reporter of #112 wants to detect, while deserializing a polymorphic tree, every object whose Type discriminator is not registered — without throwing on the first mismatch. Today the only way is a "trap" fallback type: its constructor records each unknown Type into a static list, which is not thread-safe and requires cleanup after deserialization.

Fix

Adds OnUnresolvedSubtype to all four builders (JsonSubtypesConverterBuilder / JsonSubtypesWithPropertyConverterBuilder, in both JsonSubTypes and JsonSubTypes.Text.Json). The callback fires once per JSON element whose subtype cannot be resolved: unknown or missing discriminator value, or no matching property in property-presence mode.

The hook sits at the existing single failure point in both backends (resolvedType ?? GetFallbackSubType(...)), so nothing changes when no callback is registered. UnresolvedSubtypeInfo exposes the parent type, the discriminator name and value, a HasDiscriminator flag (to distinguish a missing discriminator from an unknown value, both otherwise read as null) and the fallback subtype that will be used (null when none is configured).

Tests

  • JsonSubTypes.Text.Json.Tests: 139 pass on net8.0 and net10.0. Six new tests: no callback when the subtype resolves, unknown value, missing discriminator, no fallback configured, one callback per unresolved element in a nested tree, property-presence mode.
  • JsonSubTypes.Tests (net46): compiles, but the NUnit suite cannot execute under Linux/VSTest, so I validated the same six scenarios through a net8.0 harness against the netstandard2.0 build (all pass).

Honest notes

  • The callback runs on the deserializing thread. A converter shared across threads calls it from several threads, so the callback must be thread-safe; I did not add locking or weak-event machinery.
  • JsonPath is available in the Newtonsoft backend but not in System.Text.Json (resolution works on a JsonDocument, which carries no path), so I omitted it from the info type.
  • The callback cannot override the resolved type. That would be a separate custom-type-resolver feature; say so here if you actually need it.

Feedback welcome on the API shape (method name, and what UnresolvedSubtypeInfo exposes).

Fires once per JSON element whose subtype cannot be resolved (unknown or
missing discriminator value, or no matching property in property-presence
mode), on both the Newtonsoft.Json and System.Text.Json backends. The
callback receives the parent type, discriminator name/value, whether the
discriminator was present, and the fallback subtype that will be used.
Extends the existing fallback point in ResolveType/GetType so no behavior
changes when no callback is registered. #112
Covers resolved subtype (no callback), unknown discriminator value, missing
discriminator, no configured fallback, one callback per unresolved element in
a nested tree, and property-presence mode. #112
…olvedSubtypeInfo
The public API keeps the README as its documentation; no XML doc comments on
fluent builder methods or value classes, consistent with the rest of the repo.
Sign up for freeto 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.

Detect all unregistered subtypes for a given json?

1 participant

@manuc66
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Add OnUnresolvedSubtype callback to detect unregistered subtypes (#112) - #193

Open
manuc66 wants to merge 4 commits into
masterfrom
feature/error-paths-in-exceptions
Open

Add OnUnresolvedSubtype callback to detect unregistered subtypes (#112)#193
manuc66 wants to merge 4 commits into
masterfrom
feature/error-paths-in-exceptions

Conversation

@manuc66

@manuc66manuc66 commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Closes#112

Problem

The reporter of #112 wants to detect, while deserializing a polymorphic tree, every object whose Type discriminator is not registered — without throwing on the first mismatch. Today the only way is a "trap" fallback type: its constructor records each unknown Type into a static list, which is not thread-safe and requires cleanup after deserialization.

Fix

Adds OnUnresolvedSubtype to all four builders (JsonSubtypesConverterBuilder / JsonSubtypesWithPropertyConverterBuilder, in both JsonSubTypes and JsonSubTypes.Text.Json). The callback fires once per JSON element whose subtype cannot be resolved: unknown or missing discriminator value, or no matching property in property-presence mode.

The hook sits at the existing single failure point in both backends (resolvedType ?? GetFallbackSubType(...)), so nothing changes when no callback is registered. UnresolvedSubtypeInfo exposes the parent type, the discriminator name and value, a HasDiscriminator flag (to distinguish a missing discriminator from an unknown value, both otherwise read as null) and the fallback subtype that will be used (null when none is configured).

Tests

  • JsonSubTypes.Text.Json.Tests: 139 pass on net8.0 and net10.0. Six new tests: no callback when the subtype resolves, unknown value, missing discriminator, no fallback configured, one callback per unresolved element in a nested tree, property-presence mode.
  • JsonSubTypes.Tests (net46): compiles, but the NUnit suite cannot execute under Linux/VSTest, so I validated the same six scenarios through a net8.0 harness against the netstandard2.0 build (all pass).

Honest notes

  • The callback runs on the deserializing thread. A converter shared across threads calls it from several threads, so the callback must be thread-safe; I did not add locking or weak-event machinery.
  • JsonPath is available in the Newtonsoft backend but not in System.Text.Json (resolution works on a JsonDocument, which carries no path), so I omitted it from the info type.
  • The callback cannot override the resolved type. That would be a separate custom-type-resolver feature; say so here if you actually need it.

Feedback welcome on the API shape (method name, and what UnresolvedSubtypeInfo exposes).

Fires once per JSON element whose subtype cannot be resolved (unknown or
missing discriminator value, or no matching property in property-presence
mode), on both the Newtonsoft.Json and System.Text.Json backends. The
callback receives the parent type, discriminator name/value, whether the
discriminator was present, and the fallback subtype that will be used.
Extends the existing fallback point in ResolveType/GetType so no behavior
changes when no callback is registered. #112
Covers resolved subtype (no callback), unknown discriminator value, missing
discriminator, no configured fallback, one callback per unresolved element in
a nested tree, and property-presence mode. #112
…olvedSubtypeInfo
The public API keeps the README as its documentation; no XML doc comments on
fluent builder methods or value classes, consistent with the rest of the repo.
Sign up for freeto 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.

Detect all unregistered subtypes for a given json?

1 participant

@manuc66