Skip to content

Migrate NJsonSchema core to System.Text.Json (v12) - [WIP] - #1914

Open
RicoSuter wants to merge 11 commits into
v12from
feature/migrate-core-to-stj
Open

Migrate NJsonSchema core to System.Text.Json (v12) - [WIP]#1914
RicoSuter wants to merge 11 commits into
v12from
feature/migrate-core-to-stj

Conversation

@RicoSuter

@RicoSuter RicoSuter commented Mar 8, 2026

Copy link
Copy Markdown
Owner

Summary

Migrates the core NJsonSchema package from Newtonsoft.Json to System.Text.Json (STJ). Newtonsoft.Json support is preserved in the NJsonSchema.NewtonsoftJson package.

  • Zero Newtonsoft.Json references in NJsonSchema core production code
  • All 520 tests passing (519 original + 1 new), 0 failing
  • Demo matches master exactly (248 passes, 10 fails, 8 exceptions)
  • Full solution builds clean across all target frameworks (netstandard2.0, net462, net8.0)

Key changes

Core serialization

  • All [JsonProperty][JsonPropertyName] + [JsonIgnore(Condition)] + [JsonInclude]
  • $ref handling: ReferencePath changed from explicit interface impl to [JsonInclude] internal property (STJ can't serialize explicit interface implementations)
  • New SchemaSerializationConverter (STJ JsonConverterFactory) replaces Newtonsoft IContractResolver
  • JToken/JObject/JArrayJsonNode/JsonObject/JsonArray throughout

Validation

  • JsonSchemaValidator uses JsonNode instead of JToken
  • Line information tracking reimplemented using Utf8JsonReader (STJ's JsonNode has no line info)
  • ValidationError.Token changed from JsonNode? to object? to support JsonPropertyToken wrapper
  • Number normalization in uniqueItems validation for mathematical equality

Schema generation

  • SampleJsonDataGenerator outputs JsonNode instead of JToken
  • SampleJsonSchemaGenerator handles lenient JSON (unquoted property names, single quotes)
  • ReflectionServiceBase retains Newtonsoft type names (JObject/JArray/JToken) alongside STJ equivalents

Reference resolution

  • JsonReferenceResolver uses [JsonPropertyName] for path segment matching
  • PostProcessExtensionData runs before reference resolution (refs may point into extension data)
  • OpenApiDiscriminator.Mapping changed to { get; set; } for STJ deserialization

Breaking API changes

Change Old New
ValidationError.Token JsonNode? object?
OpenApiDiscriminator.Mapping { get; } { get; set; }
JsonSchemaSerialization.FromJsonAsync arg 5 IContractResolver SchemaSerializationConverter?
Deleted PropertyRenameAndIgnoreSerializerContractResolver SchemaSerializationConverter
Deleted IgnoreEmptyCollectionsContractResolver Removed

Test plan

  • NJsonSchema.Tests: 520 passed, 0 failed, 7 skipped (net8.0)
  • Solution builds clean on all TFMs (netstandard2.0, net462, net8.0)
  • NJsonSchema.Demo matches master output (248/10/8)
  • Run full CI across all test projects and frameworks
  • Verify NSwag integration

See MIGRATION-SUMMARY.md for detailed findings, workarounds, and behavioral differences.

@RicoSuter RicoSuter changed the title Migrate NJsonSchema to System.Text.Json [WIP] Migrate NJsonSchema core to System.Text.Json Mar 29, 2026
Migrate new FormatIpV4Tests from master (JValue -> JsonValue.Create)
after merging Fix IPv4 format validator regex false positives (#1909).
…hots

- Fix integer values stored as long instead of int in ConvertJsonElement
  (TryGetInt32 before TryGetInt64)
- Remove DateTime.TryParse from extension data strings (matches
  Newtonsoft DateParseHandling.None behavior)
- Unwrap JsonElement values in Enumeration, Default, ExclusiveMinimumRaw,
  ExclusiveMaximumRaw after deserialization
- Add [JsonInclude] on EnumerationDescriptionsDashedRaw
- Fix IsWriting flag (true during serialization, false during deserialization)
- Use JavaScriptEncoder.UnsafeRelaxedJsonEscaping to match Newtonsoft output
- Add JsonNumberHandling.AllowReadingFromString for schema numeric properties
- Cache stripped JsonSerializerOptions in SchemaSerializationConverter
- Replace non-breaking spaces (U+00A0) in FixLenientJson
- Restore snapshots to match master output
…ml test

Use decimal instead of double in HandleNumberType to avoid precision loss
when STJ serializes doubles on .NET Framework (G17 format produces
1.0000119999999999 instead of 1.000012, failing validation).

Skip Yaml gzip test that depends on NSwag.Core referencing the deleted
PropertyRenameAndIgnoreSerializerContractResolver class.
@RicoSuter RicoSuter changed the title Migrate NJsonSchema core to System.Text.Json Migrate NJsonSchema core to System.Text.Json (v12) Mar 30, 2026
… for NSwag STJ migration

SchemaSerializationConverter:
- Rewrite Write() to property-by-property serialization using typeInfo,
  enabling nested types to get their own converter filters applied
- Add recursive reverse renames in Read() for nested schema properties
  (e.g., nullable → x-nullable at all depth levels)
- Add IsPropertyIgnored() for JsonPathUtilities $ref path computation
- Add AddConverter() to register additional converters alongside factory
- Skip types with [JsonConverter] attribute in CanConvert()
- Cache GetAllReverseRenames() result with invalidation
- Handle extension data after regular properties with runtime type serialization
- Use runtime type for object-typed property values (prevents {} output)

JsonSchemaSerialization:
- Add Populate modifier for getter-only collection properties
- Set CurrentSerializerOptions before UpdateSchemaReferencePaths
- Save/restore CurrentSerializerOptions for nested FromJsonAsync calls
- Add Example unwrapping in PostProcessExtensionData
- Improve FixLenientJson: string boolean normalization, safer single-quote handling
- Change RequiredPropertiesRaw to object? to handle bool/array collision

Other:
- Fix visitor JsonIgnore condition check (WhenWritingDefault was incorrectly excluded)
- Add JsonElement safety net in CSharpValueGenerator.GetNumericValue
- Add IsPropertyIgnored check in JsonPathUtilities for converter-aware $ref paths
@RicoSuter RicoSuter changed the title Migrate NJsonSchema core to System.Text.Json (v12) Migrate NJsonSchema core to System.Text.Json (v12) - [WIP] Apr 5, 2026
…dd migration docs

- Remove Newtonsoft.Json PackageReference from NJsonSchema.csproj
- Remove leftover Newtonsoft using from CodeGeneratorSettingsBase
- Replace ConcurrentDictionary with ThreadStatic single-slot cache in PropertyFilterConverter
- Extract PopulateTypeInfoResolver as static readonly field
- Add ICollection fast-path in empty collection check
- Promote ConvertJsonElement to internal, reuse in CSharpValueGenerator
- Add WithAttemptingUnquotedStringTypeDeserialization in YAML deserialization
- Add indexer property and value type guards in visitors and JsonPathUtilities
- Bump Namotion.Reflection to 3.5.0
- Replace MIGRATION-SUMMARY.md with comprehensive migration.md and review.md
@RicoSuter
RicoSuter changed the base branch from master to v12 April 21, 2026 21:50
@RicoSuter RicoSuter mentioned this pull request Apr 21, 2026
2 tasks
RicoSuter added a commit that referenced this pull request Apr 21, 2026
- New docs/changelog_v12.md tracks landed changes and v11 → v12 migration guidance
  per breaking change (with placeholder sections for planned PRs #1914, #1917,
  SchemaType expansion, reference-resolution fixes)
- docs/v12.md "Running changelog" section replaced with a pointer to the dedicated file
- Plan vs changelog separation: v12.md describes process (scope, branches, release plan,
  cleanup), changelog_v12.md is the user-facing record
RicoSuter added a commit that referenced this pull request Apr 21, 2026
* Set up v12 integration branch: plan doc and CI triggers

- Add docs/v12.md describing the v12 scope, branch model, development
  workflow with NSwag v15, release plan, and pre-release cleanup checklist
- Add v12 to OnPullRequestBranches / OnPushBranches in Build.CI.GitHubActions.cs
  and the regenerated workflow YAMLs so CI runs on the v12 branch and on PRs
  targeting it (revert before v12 merges to master)

* Split v12 changelog and migration guide into docs/changelog_v12.md

- New docs/changelog_v12.md tracks landed changes and v11 → v12 migration guidance
  per breaking change (with placeholder sections for planned PRs #1914, #1917,
  SchemaType expansion, reference-resolution fixes)
- docs/v12.md "Running changelog" section replaced with a pointer to the dedicated file
- Plan vs changelog separation: v12.md describes process (scope, branches, release plan,
  cleanup), changelog_v12.md is the user-facing record

* Rename v12.md to plan_v12.md for parity with changelog_v12.md

Plan doc and changelog are now visually paired by filename:
- docs/plan_v12.md
- docs/changelog_v12.md

Update internal cross-references in both files.

* Add CLAUDE.md and docs/references.md

CLAUDE.md:
- Project Overview, Cross-check with NSwag (supported spec targets table,
  5-point cross-check checklist, reviewer prompt, link to references.md)
- Build Commands, Architecture, Testing, Code Style
- Git Rules: no AI attribution in commits / PRs / comments

docs/references.md:
- Comprehensive reference on ActualSchema vs ActualTypeSchema vs
  InheritedSchema, the resolution algorithm, and cross-spec behavior
- DocumentPath / external ref resolution; $id, $anchor, $dynamicRef
  non-support limitations
- NSwag's OpenApiParameter.ActualSchema override mechanics
- Sibling-keyword gaps (the "Case C" problem), generalized beyond const
- Known limitations grouped by theme; decision matrix for consumers

* Enhance CLAUDE.md with project documentation index and v12 branch conventions

* CLAUDE.md: drop ephemeral branch name from v12 conventions heading

* Refine reference resolution fixes in changelog and plan documentation to clarify sibling-keyword handling
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