Skip to content

PR #4: Migrate: Newtonsoft.Json -> System.Text.Json#97

Open
AlexanderJohnston wants to merge 37 commits into
bwatts:devfrom
AlexanderJohnston:refactor/Newtonsoft_to_STJ
Open

PR #4: Migrate: Newtonsoft.Json -> System.Text.Json#97
AlexanderJohnston wants to merge 37 commits into
bwatts:devfrom
AlexanderJohnston:refactor/Newtonsoft_to_STJ

Conversation

@AlexanderJohnston

@AlexanderJohnston AlexanderJohnston commented Feb 17, 2026

Copy link
Copy Markdown

This PR completes the migration from Newtonsoft.Json to System.Text.Json (STJ) across the entire Totem framework.

Changes

Phase 1: Upgrade TFMs to net10.0 and modernize obsolete APIs

  • Updated target frameworks to net10.0
  • Replaced obsolete API usages

Phase 2: Core System.Text.Json implementation

  • Replaced JsonFormatContractResolver with TotemJsonTypeInfoResolver for STJ's contract model
  • Replaced JsonFormatSerializationBinder with DurableTypeDiscriminatorConverter for polymorphic type handling
  • Updated IJsonFormat, JsonFormat, and JsonFormatExtensions to use System.Text.Json types

Phase 3–5: Migrate Timeline, EventStore, and MVC layers

  • Rewrote FlowKeyConverter and TimelinePositionConverter as STJ JsonConverter implementations
  • Updated SubscribeCommand in EventStore to use STJ deserialization
  • Simplified WebRuntimeOptionsSetup in MVC to configure STJ directly

Phase 6: Remove Newtonsoft.Json dependencies

  • Removed Newtonsoft.Json and Microsoft.AspNetCore.Mvc.NewtonsoftJson package references
  • Deleted JsonFormatContractResolver and JsonFormatSerializationBinder
    TypeConverter bridge
  • Added TypeConverterJsonConverterFactory to bridge existing TypeConverter/TextConverter types to STJ
  • Fixed discriminator scanning to check all JSON properties, not just the first

Summary

  • Newtonsoft.Json is fully removed as a dependency
  • All JSON serialization now uses System.Text.Json with custom resolvers and converters

bwatts and others added 30 commits February 6, 2026 09:46
- Write to the client stream whether or not the checkpoint write succeeds
- Do not ignore events routed to stopped flows
- Avoid a null reference when an existing flow fails to load
The SkipException handling that required await was removed in a prior commit.
Simplify back to returning the task directly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Transition core standard libraries to net10.
- Upgrade Totem, Totem.Runtime, Totem.Timeline, Totem.App.Tests from netstandard2.0 to net10.0
- Update Microsoft.Extensions.* package versions from 2.2.0 to 10.0.0
- Replace FormatterServices.GetUninitializedObject() with RuntimeHelpers.GetUninitializedObject() in DurableType.cs
- Replace Assembly.LoadWithPartialName() with Assembly.Load(new AssemblyName()) in TypeResolver.cs
- Remove ToHashSet extension methods that conflict with built-in LINQ on net10.0
- Fix C# 14 field keyword conflict in Fields.cs

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Create new STJ infrastructure replacing Newtonsoft.Json core:

- Add TotemJsonTypeInfoResolver: replaces JsonFormatContractResolver and
  JsonFormatSerializationBinder with DefaultJsonTypeInfoResolver + Modifiers
  - Many<T> collection creation via expression-compiled factory
  - Durable type object creation via RuntimeHelpers.GetUninitializedObject()
  - Property filtering: excludes [Transient], [CompilerGenerated], Notion-declared
  - Private field/property serialization for durable types
  - [WriteOnly] attribute support (serialize but don't deserialize)

- Add DurableTypeDiscriminatorConverter: JsonConverterFactory handling polymorphic
  \ property with 'durable:Prefix:TypeName' values for backward compatibility
  with Newtonsoft-serialized EventStore data. Includes TypeResolver fallback for
  legacy assembly-qualified type names.

- Redesign IJsonFormat: expose JsonSerializerOptions instead of Apply() pattern
- Rewrite JsonFormat: simple wrapper around JsonSerializerOptions
- Rewrite JsonFormatExtensions: use JsonSerializer/JsonNode instead of
  JsonConvert/JObject. Add ToJsonNode/ToJsonNodeUtf8 methods replacing JObject.
  Implement CopyProperties for PopulateObject equivalent.
- Rewrite JsonFormatOptions: SerializerOptions replaces SerializerSettings
- Rewrite JsonFormatOptionsSetup: configure WriteIndented, CamelCase naming,
  JsonStringEnumConverter, TotemJsonTypeInfoResolver, DurableTypeDiscriminatorConverter
- Update JsonServiceExtensions: use SerializerOptions

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… STJ

Phase 3 - Timeline Converters:
- Rewrite FlowKeyConverter as JsonConverter<FlowKey> using Utf8JsonReader/Writer
- Rewrite TimelinePositionConverter as JsonConverter<TimelinePosition>
- Update TimelineJsonFormatOptionsSetup to use SerializerOptions

Phase 4 - EventStore Layer:
- Rewrite SubscribeCommand: replace JObject/JArray/JToken (Newtonsoft.Json.Linq)
  with JsonNode/JsonArray (System.Text.Json.Nodes)
- Use JsonNode.GetValue<T>() instead of JToken.Value<T>()
- Use JsonArray instead of JArray, pattern matching with 'is JsonArray'

Phase 5 - MVC Layer:
- Rewrite WebRuntimeOptionsSetup: replace IPostConfigureOptions<MvcNewtonsoftJsonOptions>
  with IPostConfigureOptions<JsonOptions>, copying STJ settings instead of 22
  Newtonsoft properties
- Remove .AddNewtonsoftJson() from ConfigureWebApp.cs (STJ is MVC default)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…iles

- Remove Newtonsoft.Json 13.0.3 package from Totem.Runtime.csproj
- Remove Microsoft.AspNetCore.Mvc.NewtonsoftJson 10.0.0 package from Totem.Timeline.Mvc.csproj
- Delete JsonFormatContractResolver.cs (replaced by TotemJsonTypeInfoResolver)
- Delete JsonFormatSerializationBinder.cs (merged into DurableTypeDiscriminatorConverter)

All direct Newtonsoft.Json dependencies are now removed. The only remaining
Newtonsoft reference is a transitive dependency from EventStore.Client.Grpc
(Newtonsoft.Json 9.0.1) which is outside our control.

Solution builds with 0 errors across all projects.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
AlexanderJohnston and others added 7 commits February 17, 2026 09:38
The DurableTypeDiscriminatorConverter.Read method was only checking the first
property for the \ discriminator. Since JSON property order is not
guaranteed, this could miss the discriminator and deserialize as the base
type instead of the actual polymorphic type.

Now scans through all properties using Utf8JsonReader.TrySkip() to find
\ regardless of position in the JSON object.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…o STJ

STJ does not auto-detect [TypeConverter] attributes like Newtonsoft did.
This caused deserialization crashes for 20+ types (TypeName, Id, FileLink,
etc.) that use TextConverter for string-based serialization.

The factory detects types with a non-default TypeConverter that supports
string conversion, and serializes/deserializes them as JSON strings via
ConvertFromInvariantString/ConvertToInvariantString.

Registered in JsonFormatOptionsSetup.Configure() after JsonStringEnumConverter,
before the durable type converters in PostConfigure(). No conflicts since
TypeConverter types are not durable.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ctory

Add TypeConverterJsonConverterFactory to bridge [TypeConverter] types to STJ
…only

The previous check matched all .NET built-in types with TypeConverters
(int, DateTime, bool, Guid, etc.), causing reader.GetString() to throw
on non-string JSON tokens. This silently broke ASP.NET model binding
for any model with non-string properties (e.g. SmartScanRecord.FileCount).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Narrow TypeConverterJsonConverterFactory and add dictionary key support
// Details
//

static void CopyProperties(object source, object target, Type type)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sanity check this later.

@AlexanderJohnston AlexanderJohnston changed the title PR #4: Migration: Newtonsoft.Json -> System.Text.Json PR #4: Migrate: Newtonsoft.Json -> System.Text.Json Feb 17, 2026
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.

2 participants