Skip to content

STJ converter review round: streamed write, declared-type writer, modernization - #212

Open
manuc66 wants to merge 5 commits into
feature/split/pr5-plugin-patternfrom
feature/split/pr6-stj-review-round
Open

STJ converter review round: streamed write, declared-type writer, modernization#212
manuc66 wants to merge 5 commits into
feature/split/pr5-plugin-patternfrom
feature/split/pr6-stj-review-round

Conversation

@manuc66

Copy link
Copy Markdown
Owner

Problem

The converter review round addressed: README claims about two STJ behaviors, missing RegisterDynamicSubtype validation, a write path still materializing JSON strings, a base writer that ignored the declared property type, and stale code style.

Fix

  • Clarify two STJ behaviors in the README.
  • Validate RegisterDynamicSubtype, cache the mapping key type, drop per-level LINQ.
  • Stream the discriminator write with Utf8JsonReader instead of JsonDocument.
  • Use the declared property type in the base writer and reject mixed discriminator types.
  • Modernize JsonSubtypes.cs: private field, pattern matching, nullable fixes.

Tests

  • New ReviewBugTests; full STJ suite passes (net8.0 and net10.0).

Honest note(s)

  • The streamed write changes the MaxDepth + 1 note and the allocation numbers for the converter; the follow-up review-fixes PR corrects the docs and re-measures the benchmarks.

The generator runs in metadata mode, not fast-path mode: System.Text.Json only
fast-paths types without a custom converter. And unlike native [JsonDerivedType]
polymorphism (which needs AllowOutOfOrderMetadataProperties for a mid-object
discriminator), the converter reads the discriminator from anywhere.
…-level LINQ
Code review follow-ups:
- RegisterDynamicSubtype now rejects null, abstract, interface or non-assignable
types instead of silently breaking the converter's invariants.
- Cache the discriminator key type per converter (the generated converter
compiles it) instead of scanning the mapping keys on every object, and make it
volatile so a concurrent registration is visible. A registration racing a
deserialization only affects the cache, never the mapping.
- GetTypeResolver scans the converter array directly with an excluded resolver
instead of rebuilding a LINQ Where iterator on every multi-level walk step.
…ument
The payload is written into a compact buffer we just produced, so re-reading it
token by token with a Utf8JsonReader avoids materializing a JsonDocument DOM.
The discriminator is injected first or last and the payload property of the same
name is skipped. Values are copied with a small recursive copier; numbers use
WriteRawValue to preserve the exact token (decimals, exponents, big ints).
Measured (BenchmarkDotNet, net10, DefaultJob): Single_Converter_Serialize
1.15us -> 1.00us, Col_Converter_Serialize 4.23us -> 3.41us. All 200 STJ tests
pass.
…scriminator types
Code review follow-ups:
- BuildBaseTypeWriter serialized each property as object (runtime type); STJ uses
the declared property type so a polymorphic converter on that type applies.
The reader already used the declared type, so the writer now matches.
- RegisterDynamicSubtype rejected abstract/interface types; it now also rejects
a discriminator whose type differs from the existing keys, which would make
the cached key type inconsistent.
…fixes
- JsonDiscriminatorPropertyName is private (was protected); no internal code
or test derives-and-uses it.
- Write takes T? and ReadPlainObject returns T (non-nullable), matching their
actual contracts.
- Replace verbose conditions with pattern matching (is {...}, [..^], ?.ConverterType).
- Drop the volatile on _mappingKeyType: it only protected a cache field whose
stale-read risk is benign, and RegisterDynamicSubtype documents its setup-time
contract instead.
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.

1 participant

@manuc66
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
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;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
STJ converter review round: streamed write, declared-type writer, modernization by manuc66 · Pull Request #212 · manuc66/JsonSubTypes · GitHub
Skip to content

STJ converter review round: streamed write, declared-type writer, modernization - #212

Open
manuc66 wants to merge 5 commits into
feature/split/pr5-plugin-patternfrom
feature/split/pr6-stj-review-round
Open

STJ converter review round: streamed write, declared-type writer, modernization#212
manuc66 wants to merge 5 commits into
feature/split/pr5-plugin-patternfrom
feature/split/pr6-stj-review-round

Conversation

@manuc66

Copy link
Copy Markdown
Owner

Problem

The converter review round addressed: README claims about two STJ behaviors, missing RegisterDynamicSubtype validation, a write path still materializing JSON strings, a base writer that ignored the declared property type, and stale code style.

Fix

  • Clarify two STJ behaviors in the README.
  • Validate RegisterDynamicSubtype, cache the mapping key type, drop per-level LINQ.
  • Stream the discriminator write with Utf8JsonReader instead of JsonDocument.
  • Use the declared property type in the base writer and reject mixed discriminator types.
  • Modernize JsonSubtypes.cs: private field, pattern matching, nullable fixes.

Tests

  • New ReviewBugTests; full STJ suite passes (net8.0 and net10.0).

Honest note(s)

  • The streamed write changes the MaxDepth + 1 note and the allocation numbers for the converter; the follow-up review-fixes PR corrects the docs and re-measures the benchmarks.

The generator runs in metadata mode, not fast-path mode: System.Text.Json only
fast-paths types without a custom converter. And unlike native [JsonDerivedType]
polymorphism (which needs AllowOutOfOrderMetadataProperties for a mid-object
discriminator), the converter reads the discriminator from anywhere.
…-level LINQ
Code review follow-ups:
- RegisterDynamicSubtype now rejects null, abstract, interface or non-assignable
types instead of silently breaking the converter's invariants.
- Cache the discriminator key type per converter (the generated converter
compiles it) instead of scanning the mapping keys on every object, and make it
volatile so a concurrent registration is visible. A registration racing a
deserialization only affects the cache, never the mapping.
- GetTypeResolver scans the converter array directly with an excluded resolver
instead of rebuilding a LINQ Where iterator on every multi-level walk step.
…ument
The payload is written into a compact buffer we just produced, so re-reading it
token by token with a Utf8JsonReader avoids materializing a JsonDocument DOM.
The discriminator is injected first or last and the payload property of the same
name is skipped. Values are copied with a small recursive copier; numbers use
WriteRawValue to preserve the exact token (decimals, exponents, big ints).
Measured (BenchmarkDotNet, net10, DefaultJob): Single_Converter_Serialize
1.15us -> 1.00us, Col_Converter_Serialize 4.23us -> 3.41us. All 200 STJ tests
pass.
…scriminator types
Code review follow-ups:
- BuildBaseTypeWriter serialized each property as object (runtime type); STJ uses
the declared property type so a polymorphic converter on that type applies.
The reader already used the declared type, so the writer now matches.
- RegisterDynamicSubtype rejected abstract/interface types; it now also rejects
a discriminator whose type differs from the existing keys, which would make
the cached key type inconsistent.
…fixes
- JsonDiscriminatorPropertyName is private (was protected); no internal code
or test derives-and-uses it.
- Write takes T? and ReadPlainObject returns T (non-nullable), matching their
actual contracts.
- Replace verbose conditions with pattern matching (is {...}, [..^], ?.ConverterType).
- Drop the volatile on _mappingKeyType: it only protected a cache field whose
stale-read risk is benign, and RegisterDynamicSubtype documents its setup-time
contract instead.
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.

1 participant

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

STJ converter review round: streamed write, declared-type writer, modernization - #212

Open
manuc66 wants to merge 5 commits into
feature/split/pr5-plugin-patternfrom
feature/split/pr6-stj-review-round
Open

STJ converter review round: streamed write, declared-type writer, modernization#212
manuc66 wants to merge 5 commits into
feature/split/pr5-plugin-patternfrom
feature/split/pr6-stj-review-round

Conversation

@manuc66

Copy link
Copy Markdown
Owner

Problem

The converter review round addressed: README claims about two STJ behaviors, missing RegisterDynamicSubtype validation, a write path still materializing JSON strings, a base writer that ignored the declared property type, and stale code style.

Fix

  • Clarify two STJ behaviors in the README.
  • Validate RegisterDynamicSubtype, cache the mapping key type, drop per-level LINQ.
  • Stream the discriminator write with Utf8JsonReader instead of JsonDocument.
  • Use the declared property type in the base writer and reject mixed discriminator types.
  • Modernize JsonSubtypes.cs: private field, pattern matching, nullable fixes.

Tests

  • New ReviewBugTests; full STJ suite passes (net8.0 and net10.0).

Honest note(s)

  • The streamed write changes the MaxDepth + 1 note and the allocation numbers for the converter; the follow-up review-fixes PR corrects the docs and re-measures the benchmarks.

The generator runs in metadata mode, not fast-path mode: System.Text.Json only
fast-paths types without a custom converter. And unlike native [JsonDerivedType]
polymorphism (which needs AllowOutOfOrderMetadataProperties for a mid-object
discriminator), the converter reads the discriminator from anywhere.
…-level LINQ
Code review follow-ups:
- RegisterDynamicSubtype now rejects null, abstract, interface or non-assignable
types instead of silently breaking the converter's invariants.
- Cache the discriminator key type per converter (the generated converter
compiles it) instead of scanning the mapping keys on every object, and make it
volatile so a concurrent registration is visible. A registration racing a
deserialization only affects the cache, never the mapping.
- GetTypeResolver scans the converter array directly with an excluded resolver
instead of rebuilding a LINQ Where iterator on every multi-level walk step.
…ument
The payload is written into a compact buffer we just produced, so re-reading it
token by token with a Utf8JsonReader avoids materializing a JsonDocument DOM.
The discriminator is injected first or last and the payload property of the same
name is skipped. Values are copied with a small recursive copier; numbers use
WriteRawValue to preserve the exact token (decimals, exponents, big ints).
Measured (BenchmarkDotNet, net10, DefaultJob): Single_Converter_Serialize
1.15us -> 1.00us, Col_Converter_Serialize 4.23us -> 3.41us. All 200 STJ tests
pass.
…scriminator types
Code review follow-ups:
- BuildBaseTypeWriter serialized each property as object (runtime type); STJ uses
the declared property type so a polymorphic converter on that type applies.
The reader already used the declared type, so the writer now matches.
- RegisterDynamicSubtype rejected abstract/interface types; it now also rejects
a discriminator whose type differs from the existing keys, which would make
the cached key type inconsistent.
…fixes
- JsonDiscriminatorPropertyName is private (was protected); no internal code
or test derives-and-uses it.
- Write takes T? and ReadPlainObject returns T (non-nullable), matching their
actual contracts.
- Replace verbose conditions with pattern matching (is {...}, [..^], ?.ConverterType).
- Drop the volatile on _mappingKeyType: it only protected a cache field whose
stale-read risk is benign, and RegisterDynamicSubtype documents its setup-time
contract instead.
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.

1 participant

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

STJ converter review round: streamed write, declared-type writer, modernization - #212

Open
manuc66 wants to merge 5 commits into
feature/split/pr5-plugin-patternfrom
feature/split/pr6-stj-review-round
Open

STJ converter review round: streamed write, declared-type writer, modernization#212
manuc66 wants to merge 5 commits into
feature/split/pr5-plugin-patternfrom
feature/split/pr6-stj-review-round

Conversation

@manuc66

Copy link
Copy Markdown
Owner

Problem

The converter review round addressed: README claims about two STJ behaviors, missing RegisterDynamicSubtype validation, a write path still materializing JSON strings, a base writer that ignored the declared property type, and stale code style.

Fix

  • Clarify two STJ behaviors in the README.
  • Validate RegisterDynamicSubtype, cache the mapping key type, drop per-level LINQ.
  • Stream the discriminator write with Utf8JsonReader instead of JsonDocument.
  • Use the declared property type in the base writer and reject mixed discriminator types.
  • Modernize JsonSubtypes.cs: private field, pattern matching, nullable fixes.

Tests

  • New ReviewBugTests; full STJ suite passes (net8.0 and net10.0).

Honest note(s)

  • The streamed write changes the MaxDepth + 1 note and the allocation numbers for the converter; the follow-up review-fixes PR corrects the docs and re-measures the benchmarks.

The generator runs in metadata mode, not fast-path mode: System.Text.Json only
fast-paths types without a custom converter. And unlike native [JsonDerivedType]
polymorphism (which needs AllowOutOfOrderMetadataProperties for a mid-object
discriminator), the converter reads the discriminator from anywhere.
…-level LINQ
Code review follow-ups:
- RegisterDynamicSubtype now rejects null, abstract, interface or non-assignable
types instead of silently breaking the converter's invariants.
- Cache the discriminator key type per converter (the generated converter
compiles it) instead of scanning the mapping keys on every object, and make it
volatile so a concurrent registration is visible. A registration racing a
deserialization only affects the cache, never the mapping.
- GetTypeResolver scans the converter array directly with an excluded resolver
instead of rebuilding a LINQ Where iterator on every multi-level walk step.
…ument
The payload is written into a compact buffer we just produced, so re-reading it
token by token with a Utf8JsonReader avoids materializing a JsonDocument DOM.
The discriminator is injected first or last and the payload property of the same
name is skipped. Values are copied with a small recursive copier; numbers use
WriteRawValue to preserve the exact token (decimals, exponents, big ints).
Measured (BenchmarkDotNet, net10, DefaultJob): Single_Converter_Serialize
1.15us -> 1.00us, Col_Converter_Serialize 4.23us -> 3.41us. All 200 STJ tests
pass.
…scriminator types
Code review follow-ups:
- BuildBaseTypeWriter serialized each property as object (runtime type); STJ uses
the declared property type so a polymorphic converter on that type applies.
The reader already used the declared type, so the writer now matches.
- RegisterDynamicSubtype rejected abstract/interface types; it now also rejects
a discriminator whose type differs from the existing keys, which would make
the cached key type inconsistent.
…fixes
- JsonDiscriminatorPropertyName is private (was protected); no internal code
or test derives-and-uses it.
- Write takes T? and ReadPlainObject returns T (non-nullable), matching their
actual contracts.
- Replace verbose conditions with pattern matching (is {...}, [..^], ?.ConverterType).
- Drop the volatile on _mappingKeyType: it only protected a cache field whose
stale-read risk is benign, and RegisterDynamicSubtype documents its setup-time
contract instead.
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.

1 participant

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

STJ converter review round: streamed write, declared-type writer, modernization - #212

Open
manuc66 wants to merge 5 commits into
feature/split/pr5-plugin-patternfrom
feature/split/pr6-stj-review-round
Open

STJ converter review round: streamed write, declared-type writer, modernization#212
manuc66 wants to merge 5 commits into
feature/split/pr5-plugin-patternfrom
feature/split/pr6-stj-review-round

Conversation

@manuc66

Copy link
Copy Markdown
Owner

Problem

The converter review round addressed: README claims about two STJ behaviors, missing RegisterDynamicSubtype validation, a write path still materializing JSON strings, a base writer that ignored the declared property type, and stale code style.

Fix

  • Clarify two STJ behaviors in the README.
  • Validate RegisterDynamicSubtype, cache the mapping key type, drop per-level LINQ.
  • Stream the discriminator write with Utf8JsonReader instead of JsonDocument.
  • Use the declared property type in the base writer and reject mixed discriminator types.
  • Modernize JsonSubtypes.cs: private field, pattern matching, nullable fixes.

Tests

  • New ReviewBugTests; full STJ suite passes (net8.0 and net10.0).

Honest note(s)

  • The streamed write changes the MaxDepth + 1 note and the allocation numbers for the converter; the follow-up review-fixes PR corrects the docs and re-measures the benchmarks.

The generator runs in metadata mode, not fast-path mode: System.Text.Json only
fast-paths types without a custom converter. And unlike native [JsonDerivedType]
polymorphism (which needs AllowOutOfOrderMetadataProperties for a mid-object
discriminator), the converter reads the discriminator from anywhere.
…-level LINQ
Code review follow-ups:
- RegisterDynamicSubtype now rejects null, abstract, interface or non-assignable
types instead of silently breaking the converter's invariants.
- Cache the discriminator key type per converter (the generated converter
compiles it) instead of scanning the mapping keys on every object, and make it
volatile so a concurrent registration is visible. A registration racing a
deserialization only affects the cache, never the mapping.
- GetTypeResolver scans the converter array directly with an excluded resolver
instead of rebuilding a LINQ Where iterator on every multi-level walk step.
…ument
The payload is written into a compact buffer we just produced, so re-reading it
token by token with a Utf8JsonReader avoids materializing a JsonDocument DOM.
The discriminator is injected first or last and the payload property of the same
name is skipped. Values are copied with a small recursive copier; numbers use
WriteRawValue to preserve the exact token (decimals, exponents, big ints).
Measured (BenchmarkDotNet, net10, DefaultJob): Single_Converter_Serialize
1.15us -> 1.00us, Col_Converter_Serialize 4.23us -> 3.41us. All 200 STJ tests
pass.
…scriminator types
Code review follow-ups:
- BuildBaseTypeWriter serialized each property as object (runtime type); STJ uses
the declared property type so a polymorphic converter on that type applies.
The reader already used the declared type, so the writer now matches.
- RegisterDynamicSubtype rejected abstract/interface types; it now also rejects
a discriminator whose type differs from the existing keys, which would make
the cached key type inconsistent.
…fixes
- JsonDiscriminatorPropertyName is private (was protected); no internal code
or test derives-and-uses it.
- Write takes T? and ReadPlainObject returns T (non-nullable), matching their
actual contracts.
- Replace verbose conditions with pattern matching (is {...}, [..^], ?.ConverterType).
- Drop the volatile on _mappingKeyType: it only protected a cache field whose
stale-read risk is benign, and RegisterDynamicSubtype documents its setup-time
contract instead.
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.

1 participant

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

STJ converter review round: streamed write, declared-type writer, modernization - #212

Open
manuc66 wants to merge 5 commits into
feature/split/pr5-plugin-patternfrom
feature/split/pr6-stj-review-round
Open

STJ converter review round: streamed write, declared-type writer, modernization#212
manuc66 wants to merge 5 commits into
feature/split/pr5-plugin-patternfrom
feature/split/pr6-stj-review-round

Conversation

@manuc66

Copy link
Copy Markdown
Owner

Problem

The converter review round addressed: README claims about two STJ behaviors, missing RegisterDynamicSubtype validation, a write path still materializing JSON strings, a base writer that ignored the declared property type, and stale code style.

Fix

  • Clarify two STJ behaviors in the README.
  • Validate RegisterDynamicSubtype, cache the mapping key type, drop per-level LINQ.
  • Stream the discriminator write with Utf8JsonReader instead of JsonDocument.
  • Use the declared property type in the base writer and reject mixed discriminator types.
  • Modernize JsonSubtypes.cs: private field, pattern matching, nullable fixes.

Tests

  • New ReviewBugTests; full STJ suite passes (net8.0 and net10.0).

Honest note(s)

  • The streamed write changes the MaxDepth + 1 note and the allocation numbers for the converter; the follow-up review-fixes PR corrects the docs and re-measures the benchmarks.

The generator runs in metadata mode, not fast-path mode: System.Text.Json only
fast-paths types without a custom converter. And unlike native [JsonDerivedType]
polymorphism (which needs AllowOutOfOrderMetadataProperties for a mid-object
discriminator), the converter reads the discriminator from anywhere.
…-level LINQ
Code review follow-ups:
- RegisterDynamicSubtype now rejects null, abstract, interface or non-assignable
types instead of silently breaking the converter's invariants.
- Cache the discriminator key type per converter (the generated converter
compiles it) instead of scanning the mapping keys on every object, and make it
volatile so a concurrent registration is visible. A registration racing a
deserialization only affects the cache, never the mapping.
- GetTypeResolver scans the converter array directly with an excluded resolver
instead of rebuilding a LINQ Where iterator on every multi-level walk step.
…ument
The payload is written into a compact buffer we just produced, so re-reading it
token by token with a Utf8JsonReader avoids materializing a JsonDocument DOM.
The discriminator is injected first or last and the payload property of the same
name is skipped. Values are copied with a small recursive copier; numbers use
WriteRawValue to preserve the exact token (decimals, exponents, big ints).
Measured (BenchmarkDotNet, net10, DefaultJob): Single_Converter_Serialize
1.15us -> 1.00us, Col_Converter_Serialize 4.23us -> 3.41us. All 200 STJ tests
pass.
…scriminator types
Code review follow-ups:
- BuildBaseTypeWriter serialized each property as object (runtime type); STJ uses
the declared property type so a polymorphic converter on that type applies.
The reader already used the declared type, so the writer now matches.
- RegisterDynamicSubtype rejected abstract/interface types; it now also rejects
a discriminator whose type differs from the existing keys, which would make
the cached key type inconsistent.
…fixes
- JsonDiscriminatorPropertyName is private (was protected); no internal code
or test derives-and-uses it.
- Write takes T? and ReadPlainObject returns T (non-nullable), matching their
actual contracts.
- Replace verbose conditions with pattern matching (is {...}, [..^], ?.ConverterType).
- Drop the volatile on _mappingKeyType: it only protected a cache field whose
stale-read risk is benign, and RegisterDynamicSubtype documents its setup-time
contract instead.
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.

1 participant

@manuc66
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' STJ converter review round: streamed write, declared-type writer, modernization by manuc66 · Pull Request #212 · manuc66/JsonSubTypes · GitHub
Skip to content

STJ converter review round: streamed write, declared-type writer, modernization - #212

Open
manuc66 wants to merge 5 commits into
feature/split/pr5-plugin-patternfrom
feature/split/pr6-stj-review-round
Open

STJ converter review round: streamed write, declared-type writer, modernization#212
manuc66 wants to merge 5 commits into
feature/split/pr5-plugin-patternfrom
feature/split/pr6-stj-review-round

Conversation

@manuc66

Copy link
Copy Markdown
Owner

Problem

The converter review round addressed: README claims about two STJ behaviors, missing RegisterDynamicSubtype validation, a write path still materializing JSON strings, a base writer that ignored the declared property type, and stale code style.

Fix

  • Clarify two STJ behaviors in the README.
  • Validate RegisterDynamicSubtype, cache the mapping key type, drop per-level LINQ.
  • Stream the discriminator write with Utf8JsonReader instead of JsonDocument.
  • Use the declared property type in the base writer and reject mixed discriminator types.
  • Modernize JsonSubtypes.cs: private field, pattern matching, nullable fixes.

Tests

  • New ReviewBugTests; full STJ suite passes (net8.0 and net10.0).

Honest note(s)

  • The streamed write changes the MaxDepth + 1 note and the allocation numbers for the converter; the follow-up review-fixes PR corrects the docs and re-measures the benchmarks.

The generator runs in metadata mode, not fast-path mode: System.Text.Json only
fast-paths types without a custom converter. And unlike native [JsonDerivedType]
polymorphism (which needs AllowOutOfOrderMetadataProperties for a mid-object
discriminator), the converter reads the discriminator from anywhere.
…-level LINQ
Code review follow-ups:
- RegisterDynamicSubtype now rejects null, abstract, interface or non-assignable
types instead of silently breaking the converter's invariants.
- Cache the discriminator key type per converter (the generated converter
compiles it) instead of scanning the mapping keys on every object, and make it
volatile so a concurrent registration is visible. A registration racing a
deserialization only affects the cache, never the mapping.
- GetTypeResolver scans the converter array directly with an excluded resolver
instead of rebuilding a LINQ Where iterator on every multi-level walk step.
…ument
The payload is written into a compact buffer we just produced, so re-reading it
token by token with a Utf8JsonReader avoids materializing a JsonDocument DOM.
The discriminator is injected first or last and the payload property of the same
name is skipped. Values are copied with a small recursive copier; numbers use
WriteRawValue to preserve the exact token (decimals, exponents, big ints).
Measured (BenchmarkDotNet, net10, DefaultJob): Single_Converter_Serialize
1.15us -> 1.00us, Col_Converter_Serialize 4.23us -> 3.41us. All 200 STJ tests
pass.
…scriminator types
Code review follow-ups:
- BuildBaseTypeWriter serialized each property as object (runtime type); STJ uses
the declared property type so a polymorphic converter on that type applies.
The reader already used the declared type, so the writer now matches.
- RegisterDynamicSubtype rejected abstract/interface types; it now also rejects
a discriminator whose type differs from the existing keys, which would make
the cached key type inconsistent.
…fixes
- JsonDiscriminatorPropertyName is private (was protected); no internal code
or test derives-and-uses it.
- Write takes T? and ReadPlainObject returns T (non-nullable), matching their
actual contracts.
- Replace verbose conditions with pattern matching (is {...}, [..^], ?.ConverterType).
- Drop the volatile on _mappingKeyType: it only protected a cache field whose
stale-read risk is benign, and RegisterDynamicSubtype documents its setup-time
contract instead.
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.

1 participant

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

STJ converter review round: streamed write, declared-type writer, modernization - #212

Open
manuc66 wants to merge 5 commits into
feature/split/pr5-plugin-patternfrom
feature/split/pr6-stj-review-round
Open

STJ converter review round: streamed write, declared-type writer, modernization#212
manuc66 wants to merge 5 commits into
feature/split/pr5-plugin-patternfrom
feature/split/pr6-stj-review-round

Conversation

@manuc66

Copy link
Copy Markdown
Owner

Problem

The converter review round addressed: README claims about two STJ behaviors, missing RegisterDynamicSubtype validation, a write path still materializing JSON strings, a base writer that ignored the declared property type, and stale code style.

Fix

  • Clarify two STJ behaviors in the README.
  • Validate RegisterDynamicSubtype, cache the mapping key type, drop per-level LINQ.
  • Stream the discriminator write with Utf8JsonReader instead of JsonDocument.
  • Use the declared property type in the base writer and reject mixed discriminator types.
  • Modernize JsonSubtypes.cs: private field, pattern matching, nullable fixes.

Tests

  • New ReviewBugTests; full STJ suite passes (net8.0 and net10.0).

Honest note(s)

  • The streamed write changes the MaxDepth + 1 note and the allocation numbers for the converter; the follow-up review-fixes PR corrects the docs and re-measures the benchmarks.

The generator runs in metadata mode, not fast-path mode: System.Text.Json only
fast-paths types without a custom converter. And unlike native [JsonDerivedType]
polymorphism (which needs AllowOutOfOrderMetadataProperties for a mid-object
discriminator), the converter reads the discriminator from anywhere.
…-level LINQ
Code review follow-ups:
- RegisterDynamicSubtype now rejects null, abstract, interface or non-assignable
types instead of silently breaking the converter's invariants.
- Cache the discriminator key type per converter (the generated converter
compiles it) instead of scanning the mapping keys on every object, and make it
volatile so a concurrent registration is visible. A registration racing a
deserialization only affects the cache, never the mapping.
- GetTypeResolver scans the converter array directly with an excluded resolver
instead of rebuilding a LINQ Where iterator on every multi-level walk step.
…ument
The payload is written into a compact buffer we just produced, so re-reading it
token by token with a Utf8JsonReader avoids materializing a JsonDocument DOM.
The discriminator is injected first or last and the payload property of the same
name is skipped. Values are copied with a small recursive copier; numbers use
WriteRawValue to preserve the exact token (decimals, exponents, big ints).
Measured (BenchmarkDotNet, net10, DefaultJob): Single_Converter_Serialize
1.15us -> 1.00us, Col_Converter_Serialize 4.23us -> 3.41us. All 200 STJ tests
pass.
…scriminator types
Code review follow-ups:
- BuildBaseTypeWriter serialized each property as object (runtime type); STJ uses
the declared property type so a polymorphic converter on that type applies.
The reader already used the declared type, so the writer now matches.
- RegisterDynamicSubtype rejected abstract/interface types; it now also rejects
a discriminator whose type differs from the existing keys, which would make
the cached key type inconsistent.
…fixes
- JsonDiscriminatorPropertyName is private (was protected); no internal code
or test derives-and-uses it.
- Write takes T? and ReadPlainObject returns T (non-nullable), matching their
actual contracts.
- Replace verbose conditions with pattern matching (is {...}, [..^], ?.ConverterType).
- Drop the volatile on _mappingKeyType: it only protected a cache field whose
stale-read risk is benign, and RegisterDynamicSubtype documents its setup-time
contract instead.
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.

1 participant

@manuc66