This round clarifies v4's feature set.
Contractless, Typeless, DynamicObject, NonGenerics, and Json have been implemented.
In other words, v4 now delivers 100% of v3's functionality, compatible both in features and in input/output binary.
The target frameworks are identical as well (netstandard2.0 floor), so there is no barrier for v3 users to upgrade.
Honestly, these features were well isolated — the design consistently kept options from bleeding into the body of other formatters — so none of these features, present since v1, ever amounted to technical debt.
They also separate cleanly between the NativeAOT and JIT paths, so they pose no obstacle to NativeAOT support either.
That said, as a safety consideration, the blacklist-style Typeless is marked as Obsolete.
As for Contractless/DynamicObject, v3 emitted the entire formatter body with IL.Emit; v4 switches to per-property delegation.
This is a maintenance-cost decision: whole-body Emit makes the IL implementation and its upkeep far too expensive.
The Source Generator path is what normally runs, and these are positioned as fallbacks, so by deliberately not chasing extreme performance, we balance functionality against maintenance cost.
v4 DynamicObject is by no means the best possible performance, yet it still outperforms v3.
This is because the underlying baseline is fast enough to leave headroom in the performance budget.
If v4's baseline had been merely on par with v3, the fallback would have meant an actual regression — fallback or not, that would have been hard to accept.
This is one more place where the pursuit of raw performance pays off.
With the guarantee that "it never gets worse than v3" in hand, I'm proceeding with this implementation approach.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This round clarifies v4's feature set.
Contractless, Typeless, DynamicObject, NonGenerics, and Json have been implemented.
In other words, v4 now delivers 100% of v3's functionality, compatible both in features and in input/output binary.
The target frameworks are identical as well (netstandard2.0 floor), so there is no barrier for v3 users to upgrade.
Honestly, these features were well isolated — the design consistently kept options from bleeding into the body of other formatters — so none of these features, present since v1, ever amounted to technical debt.
They also separate cleanly between the NativeAOT and JIT paths, so they pose no obstacle to NativeAOT support either.
That said, as a safety consideration, the blacklist-style Typeless is marked as Obsolete.
As for Contractless/DynamicObject, v3 emitted the entire formatter body with IL.Emit; v4 switches to per-property delegation.
This is a maintenance-cost decision: whole-body Emit makes the IL implementation and its upkeep far too expensive.
The Source Generator path is what normally runs, and these are positioned as fallbacks, so by deliberately not chasing extreme performance, we balance functionality against maintenance cost.
Performance compared with v3 and other libraries:
The Stack Overflow API Answer graph (Answer → ShallowUser → BadgeCount, Answer → List<Comment> → ShallowUser, byte enums, DateTimes, ~1.65KB payload), [Key(int)] array format.
i7-13700KF, .NET 10.0.5, BenchmarkDotNet MediumRun (2 launches × 15 iterations).
v4 DynamicObject is by no means the best possible performance, yet it still outperforms v3.
This is because the underlying baseline is fast enough to leave headroom in the performance budget.
If v4's baseline had been merely on par with v3, the fallback would have meant an actual regression — fallback or not, that would have been hard to accept.
This is one more place where the pursuit of raw performance pays off.
With the guarantee that "it never gets worse than v3" in hand, I'm proceeding with this implementation approach.