diff --git a/uSync.Core/Extensions/JsonTextExtensions.cs b/uSync.Core/Extensions/JsonTextExtensions.cs
index 6e012b53..cd6029aa 100644
--- a/uSync.Core/Extensions/JsonTextExtensions.cs
+++ b/uSync.Core/Extensions/JsonTextExtensions.cs
@@ -520,4 +520,15 @@ public static bool IsJsonEqual(this object currentObject, object newObject)
#endregion
+ #region Type Checks
+
+ ///
+ /// checks if the value is a non-string JSON value (array, object, number, boolean).
+ ///
+ public static bool IsNonStringJsonValue(this object? value)
+ => value is JsonElement { ValueKind: not JsonValueKind.String and not JsonValueKind.Undefined }
+ || value is JsonArray or JsonObject;
+
+ #endregion
+
}
diff --git a/uSync.Core/Mapping/Mappers/SingleBlockMapper.cs b/uSync.Core/Mapping/Mappers/SingleBlockMapper.cs
new file mode 100644
index 00000000..97b0371b
--- /dev/null
+++ b/uSync.Core/Mapping/Mappers/SingleBlockMapper.cs
@@ -0,0 +1,23 @@
+using Microsoft.Extensions.Logging;
+
+using Umbraco.Cms.Core;
+using Umbraco.Cms.Core.Models.Blocks;
+using Umbraco.Cms.Core.Services;
+
+namespace uSync.Core.Mapping.Mappers;
+
+internal class SingleBlockMapper : SyncBlockMapperBase, ISyncMapper
+{
+ public override string Name => "NuBlock Single Block mapper";
+
+ public override string[] Editors => [Constants.PropertyEditors.Aliases.SingleBlock];
+
+ public SingleBlockMapper(
+ IEntityService entityService,
+ IContentTypeService contentTypeService,
+ Lazy mapperCollection,
+ ILogger logger)
+ : base(entityService, contentTypeService, mapperCollection, logger)
+ {
+ }
+}
diff --git a/uSync.Core/Mapping/SyncBlockMapperBase.cs b/uSync.Core/Mapping/SyncBlockMapperBase.cs
index 68c511a2..b9d24030 100644
--- a/uSync.Core/Mapping/SyncBlockMapperBase.cs
+++ b/uSync.Core/Mapping/SyncBlockMapperBase.cs
@@ -1,6 +1,7 @@
using Microsoft.Extensions.Logging;
using System.Collections;
+using System.Text.Json;
using System.Text.Json.Nodes;
using Umbraco.Cms.Core;
@@ -61,7 +62,17 @@ public SyncBlockMapperBase(
_logger.LogDebug("Importing block value for {PropertyEditorAlias} {valueType}", propertyType.PropertyEditorAlias, value?.GetType().Name ?? "blank");
var importString = SyncBlockMapperBase.GetStringValue(value) ?? string.Empty;
- return await _mapperCollection.Value.GetImportValueAsync(importString, propertyType, options);
+ var result = await _mapperCollection.Value.GetImportValueAsync(importString, propertyType, options);
+
+ // When the original value was a non-string JSON type (array, object, number, etc.),
+ // convert string results back to JsonNode to preserve the correct JSON type
+ // and prevent double-encoding when the block value is re-serialized.
+ if (result is string stringResult && value.IsNonStringJsonValue())
+ {
+ return stringResult.ConvertToJsonNode() ?? result;
+ }
+
+ return result;
}
private async Task