Use Jumoo.Json for the json helpers - #1014
Merged
Merged
Conversation
uSync.Core/Extensions/JsonTextExtensions.cs was a copy of the Jumoo.Json
package, and had drifted behind it. uSync's own code now calls Jumoo.Json
(18.0.3) directly, so there is one implementation to maintain and uSync picks
up the fixes and allocation work done there:
- TryConvertToJsonNode returned false for any string containing a quote,
backslash or control character - it built its fallback with
JsonNode.Parse($"\"{value}\""), which is invalid json for those inputs.
- TrySerializeJsonNode built a fresh JsonSerializerOptions on every call.
- TryExpandJsonNodeValue deep-cloned at every level of recursion.
- IsJsonEqual and IsValidJsonString allocated on the large object heap for
large values.
Nothing downstream needs to change. JsonTextExtensions keeps its namespace,
class name, signatures and behaviour - including where Jumoo.Json returns null
and it returned string.Empty - but every method is now [Obsolete] and forwards,
and they will be removed in v20. A file can't import both uSync.Core.Extensions
and Jumoo.Json, because they declare the same extension method signatures, so
the 42 migrated files import Jumoo.Json instead. Four take a using static for
the one other class they needed, two inside uSync.Core.Extensions call
Jumoo.Json fully qualified (the shim wins on namespace proximity there), and
GlobalUsings.cs aliases the public uSyncTaskHelper rather than repeating that
in fourteen files.
Also deletes uSync's copy of OrderedPropertiesJsonResolver (internal, byte
identical upstream) and obsoletes JsonXElementConverter.
Serialized output is unchanged. Jumoo.Json lists the converters in a different
order and order decides which one wins, so JsonSerializerOptionsTests pins the
converter uSync's serialization resolves for each type - that also guards
against another Jumoo package calling JsonTextOptions.AddConverter and quietly
reformatting everyone's .config files.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 18, 2026
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
uSync.Core/Extensions/JsonTextExtensions.cswas a copy of theJumoo.Jsonpackage and had drifted behind it. uSync's own code now callsJumoo.Json18.0.3 directly, so there is one implementation to maintain, and uSync inherits the fixes and allocation work done there.Depends on Jumoo/Jumoo.Json#9 (merged, published as 18.0.3).
What uSync gains
TryConvertToJsonNodereturnedfalsefor any string containing a quote, backslash or control characterJsonNode.Parse($"\"{value}\""), which is invalid json for those inputsTrySerializeJsonNodebuilt a freshJsonSerializerOptionson every callTryExpandJsonNodeValuedeep-cloned at every level of recursionIsJsonEqual/IsValidJsonStringallocated on the large object heapToString()on whole subtrees before parsingGetPropertyValueOrDefaulton a type mismatch: 1,509ns → 10nsNumbers are from
Jumoo.Json.Benchmarks/README.md.Nothing downstream needs to change
uSync.Core.Extensions.JsonTextExtensionskeeps its namespace, class name, signatures and behaviour — including the places whereJumoo.Jsonreturnsnulland it returnedstring.Empty. Every method is now[Obsolete]and forwards, and they will be removed in v20.JsonSerializerOptionsTestscovers those contracts, since that is what stands between uSync.Complete and a broken build.To move a file over, replace
using uSync.Core.Extensions;withusing Jumoo.Json;. A file can't have both — they declare the same extension method signatures, so a call matching both is ambiguous (CS0121). Names that differ:TryGetPropertyAsObject→TryGetPropertyAsJsonObject,GetPropertyAsObject→GetPropertyAsJsonObject.How the migration handles that ambiguity
uSync.Core.Extensionsholds only four classes; everything else lives innamespace uSync.Core. So of the 42 migrated files:using staticfor the one other class they needed (DictionaryExtensions/ObjectPropertyExtensions/ConversionExtensions).namespace uSync.Core.Extensions, where the shim wins on namespace proximity no matter what the usings say, so they callJumoo.Json.JsonSerialization.TryGetValueAsfully qualified.GlobalUsings.csaliases the publicuSyncTaskHelperonce, rather than repeating an alias in fourteen files.Zero
CS0618/CS0619warnings solution-wide, so nothing in uSync still routes through the shim.Serialized output
This is the risk that matters — uSync detects changes by comparing serialized json, so a formatting difference makes every item report as changed.
Jumoo.Jsonlists the same converters in a different order, and STJ resolves first-match-wins, so I checked rather than assumed: every type resolves to the same converter as before (notablyJsonObjectConvertercoming beforeJsonUdiConverterdoes not stealUdi).JsonXElementConverteris the same implementation under a new namespace. That check is nowJsonSerializerOptionsTests, which doubles as a guard against another Jumoo package callingJsonTextOptions.AddConverterand quietly reformatting everyone's.configfiles — those options are a process-wide static shared across the Jumoo packages.Behaviour changes I had to make by hand
RichTextEditorMigratingSerializer.TopLevelEditor—SerializeJsonStringreturningnullinstead ofstring.Emptyturned acontinueinto a dropped config key. Rewritten to fall through to theelsebranch as before.uSyncHistoryNotificationHandler— now logs and skips instead of writing an empty history file if serialization fails.?? string.Emptywhere a caller needs non-null:ColourPickerMigratingConfigSerializer,RTEBlockDataContentMigrator,ContentSerializerBase,DataTypeTracker.DeserializeJson, which now returnsdefaultrather than throwing — the guards were already the intended path.Also
OrderedPropertiesJsonResolver(internal, byte identical upstream).uSync.Core.Json.JsonXElementConverter.SyncConfigMergerBase.cs:113'sToJsonArray()turned out to be Umbraco's ownJsonNodeExtensions.ToJsonArray(IEnumerable<JsonNode?>), not uSync's string overload — untouched.Testing
dotnet build uSync.slnx -c Release— clean, only the pre-existing CS8632 inTryGetValueAsTests.dotnet test uSync.Tests— 177 passing (was 148).dotnet pack uSync.Core— emits a single cleanJumoo.Json 18.0.3dependency.Still outstanding: the export diff. Run a full export from
v18/mainand from this branch against the same database and confirmgit diff --no-indexis empty, plus a report against a pre-change export showing zero changed items. The converter check above is strong evidence but it is not the same as a real export. Worth exercising block list/grid, an RTE with blocks, media picker 3 with crops, and a root data type config merge specifically.🤖 Generated with Claude Code