diff --git a/Directory.Build.props b/Directory.Build.props
index 1bd4721a..0c5bee3b 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -46,6 +46,7 @@
+
\ No newline at end of file
diff --git a/uSync.Core/DataTypes/ConfigurationSerializerBase.cs b/uSync.Core/DataTypes/ConfigurationSerializerBase.cs
index 167ee6e1..60791beb 100644
--- a/uSync.Core/DataTypes/ConfigurationSerializerBase.cs
+++ b/uSync.Core/DataTypes/ConfigurationSerializerBase.cs
@@ -1,4 +1,8 @@
-namespace uSync.Core.DataTypes;
+using System.Collections.Immutable;
+
+using uSync.Core.Extensions;
+
+namespace uSync.Core.DataTypes;
public abstract class ConfigurationSerializerBase
{
@@ -7,4 +11,23 @@ public virtual IDictionary GetConfigurationExport(IDictionary GetConfigurationImport(IDictionary configuration)
=> configuration;
+
+ ///
+ /// renames properties that might exist in a json string (if it is one).
+ ///
+ ///
+ /// will check if the string is JSON if its not, we return the source
+ ///
+ protected static IDictionary MigratePropertyNames(IDictionary source, Dictionary names, bool sort = true)
+ {
+ foreach (var keyValue in names)
+ {
+ if (source.TryGetValue(keyValue.Key, out var propertyValue) == false) continue;
+ source[keyValue.Value] = propertyValue;
+ source.Remove(keyValue.Key);
+ }
+
+ return sort ? source.ToImmutableSortedDictionary() : source;
+ }
+
}
diff --git a/uSync.Core/DataTypes/ConfigurationSerializerCollectionBuilder.cs b/uSync.Core/DataTypes/ConfigurationSerializerCollectionBuilder.cs
index 9bdd2ffe..60772d24 100644
--- a/uSync.Core/DataTypes/ConfigurationSerializerCollectionBuilder.cs
+++ b/uSync.Core/DataTypes/ConfigurationSerializerCollectionBuilder.cs
@@ -20,4 +20,7 @@ public ConfigurationSerializerCollection(Func this.FirstOrDefault(x => x.Editors.InvariantContains(editorAlias));
+
+ public IEnumerable GetSerializers(string editorAlias)
+ => this.Where(x => x.Editors.InvariantContains(editorAlias));
}
diff --git a/uSync.Core/DataTypes/DataTypeSerializers/ColourPickerMigratingConfigSerializer.cs b/uSync.Core/DataTypes/DataTypeSerializers/ColourPickerMigratingConfigSerializer.cs
new file mode 100644
index 00000000..73f864ef
--- /dev/null
+++ b/uSync.Core/DataTypes/DataTypeSerializers/ColourPickerMigratingConfigSerializer.cs
@@ -0,0 +1,53 @@
+using System.Text.Json;
+using System.Text.Json.Nodes;
+
+using Umbraco.Cms.Core;
+using Umbraco.Cms.Core.Strings;
+
+using uSync.Core.Extensions;
+
+using static Umbraco.Cms.Core.PropertyEditors.ColorPickerConfiguration;
+
+namespace uSync.Core.DataTypes.DataTypeSerializers;
+
+internal class ColourPickerMigratingConfigSerializer : ConfigurationSerializerBase, IConfigurationSerializer
+{
+ public string Name => nameof(ColourPickerMigratingConfigSerializer);
+ public string[] Editors => [Constants.PropertyEditors.Aliases.ColorPicker];
+ public override IDictionary GetConfigurationImport(IDictionary configuration)
+ {
+ if (configuration.TryGetValue("items", out var items) is false || items is null)
+ return configuration;
+
+ if (items is JsonElement element == false) return configuration;
+ if (element.ValueKind != JsonValueKind.Array) return configuration;
+
+ var convertedItems = new List();
+
+ var array = element.EnumerateArray().Select(x => x);
+
+ foreach(var item in element.EnumerateArray())
+ {
+ if (item.ValueKind != JsonValueKind.Object) continue;
+ var obj = JsonSerializer.Deserialize(item.ToString());
+ if (obj == null) continue;
+
+ var valueJson = obj.GetPropertyAsString("value");
+ if (string.IsNullOrEmpty(valueJson)) continue;
+
+ if (valueJson.TryDeserialize(out var itemValues) is false || itemValues is null)
+ return configuration;
+
+ convertedItems.Add(new ColorPickerItem
+ {
+ Label = itemValues.GetPropertyAsString("label"),
+ Value = itemValues.GetPropertyAsString("value")
+ });
+ }
+
+ configuration.Remove("items");
+ configuration.Add("items", convertedItems);
+
+ return configuration;
+ }
+}
\ No newline at end of file
diff --git a/uSync.Core/DataTypes/DataTypeSerializers/DataListMigratingConfigSerializer.cs b/uSync.Core/DataTypes/DataTypeSerializers/DataListMigratingConfigSerializer.cs
new file mode 100644
index 00000000..e1a50e82
--- /dev/null
+++ b/uSync.Core/DataTypes/DataTypeSerializers/DataListMigratingConfigSerializer.cs
@@ -0,0 +1,47 @@
+using System.Text.Json;
+
+using Umbraco.Cms.Core;
+
+using uSync.Core.Extensions;
+
+namespace uSync.Core.DataTypes.DataTypeSerializers;
+
+internal class DataListMigratingConfigSerializer : ConfigurationSerializerBase, IConfigurationSerializer
+{
+ public string Name => nameof(DataListMigratingConfigSerializer);
+ public string[] Editors => [
+ Constants.PropertyEditors.Aliases.CheckBoxList,
+ Constants.PropertyEditors.Aliases.DropDownListFlexible,
+ Constants.PropertyEditors.Aliases.RadioButtonList
+ ];
+
+ public override IDictionary GetConfigurationImport(IDictionary configuration)
+ {
+ if (configuration.TryGetValue("items", out var items) is false || items is null)
+ return configuration;
+
+ if (items is JsonElement element == false) return configuration;
+ if (element.ValueKind != JsonValueKind.Array) return configuration;
+
+ if (element.ToString().TryDeserialize>(out var values) is false || values is null)
+ return configuration;
+
+ var convertedItems = new List();
+
+ foreach(var item in values.OrderBy(x => x.Id))
+ {
+ if (item?.Value is null) continue;
+ convertedItems.Add(item.Value);
+ }
+
+ configuration["items"] = convertedItems;
+
+ return configuration;
+ }
+ private class IdValuePair
+ {
+ public int? Id { get; set; }
+ public string? Value { get; set; }
+ }
+
+}
diff --git a/uSync.Core/DataTypes/DataTypeSerializers/FileUploadMigratingConfigSerializer.cs b/uSync.Core/DataTypes/DataTypeSerializers/FileUploadMigratingConfigSerializer.cs
new file mode 100644
index 00000000..b116c15e
--- /dev/null
+++ b/uSync.Core/DataTypes/DataTypeSerializers/FileUploadMigratingConfigSerializer.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.Json;
+using System.Threading.Tasks;
+
+using Umbraco.Cms.Core;
+
+using uSync.Core.Extensions;
+
+namespace uSync.Core.DataTypes.DataTypeSerializers;
+internal class FileUploadMigratingConfigSerializer : ConfigurationSerializerBase, IConfigurationSerializer
+{
+ public string Name => nameof(FileUploadMigratingConfigSerializer);
+
+ public string[] Editors => [Constants.PropertyEditors.Aliases.UploadField];
+
+ public override IDictionary GetConfigurationImport(IDictionary configuration)
+ {
+ if (configuration.TryGetValue("fileExtensions", out var items) is false || items is null)
+ return configuration;
+
+ if (items is JsonElement element == false) return configuration;
+ if (element.ValueKind != JsonValueKind.Array) return configuration;
+
+ if (element.ToString().TryDeserialize>(out var values) is false || values is null)
+ return configuration;
+
+ var convertedItems = new List();
+
+ foreach(var item in values.OrderBy(x => x.Id))
+ {
+ if (item.Value is null) continue;
+ convertedItems.Add(item.Value);
+ }
+
+ configuration["fileExtensions"] = convertedItems;
+ return configuration;
+ }
+
+ private class IdValuePair
+ {
+ public int? Id { get; set; }
+ public string? Value { get; set; }
+ }
+
+}
diff --git a/uSync.Core/DataTypes/DataTypeSerializers/LabelMigratingConfigSerializer.cs b/uSync.Core/DataTypes/DataTypeSerializers/LabelMigratingConfigSerializer.cs
new file mode 100644
index 00000000..fe2a3681
--- /dev/null
+++ b/uSync.Core/DataTypes/DataTypeSerializers/LabelMigratingConfigSerializer.cs
@@ -0,0 +1,15 @@
+using Umbraco.Cms.Core;
+
+namespace uSync.Core.DataTypes.DataTypeSerializers;
+
+internal class LabelMigratingConfigSerializer : ConfigurationSerializerBase, IConfigurationSerializer
+{
+ public string Name => nameof(LabelMigratingConfigSerializer);
+ public string[] Editors => [Constants.PropertyEditors.Aliases.Label];
+
+ public override IDictionary GetConfigurationImport(IDictionary configuration)
+ => MigratePropertyNames(configuration, new()
+ {
+ { "ValueType", "umbracoDataValueType" }
+ });
+}
\ No newline at end of file
diff --git a/uSync.Core/DataTypes/DataTypeSerializers/MarkdownMigratingConfigSerializer.cs b/uSync.Core/DataTypes/DataTypeSerializers/MarkdownMigratingConfigSerializer.cs
new file mode 100644
index 00000000..a87e34bf
--- /dev/null
+++ b/uSync.Core/DataTypes/DataTypeSerializers/MarkdownMigratingConfigSerializer.cs
@@ -0,0 +1,15 @@
+using Umbraco.Cms.Core;
+
+namespace uSync.Core.DataTypes.DataTypeSerializers;
+internal class MarkdownMigratingConfigSerializer : ConfigurationSerializerBase, IConfigurationSerializer
+{
+ public string Name => nameof(MarkdownMigratingConfigSerializer);
+
+ public string[] Editors => [ Constants.PropertyEditors.Aliases.MarkdownEditor ];
+
+ public override IDictionary GetConfigurationImport(IDictionary configuration)
+ => MigratePropertyNames(configuration, new()
+ {
+ { "displayLivePreview", "preview" }
+ });
+}
diff --git a/uSync.Core/DataTypes/DataTypeSerializers/MultipleTextMigratingConfigSerializer.cs b/uSync.Core/DataTypes/DataTypeSerializers/MultipleTextMigratingConfigSerializer.cs
new file mode 100644
index 00000000..6203ba95
--- /dev/null
+++ b/uSync.Core/DataTypes/DataTypeSerializers/MultipleTextMigratingConfigSerializer.cs
@@ -0,0 +1,17 @@
+using Umbraco.Cms.Core;
+
+namespace uSync.Core.DataTypes.DataTypeSerializers;
+
+internal class MultipleTextMigratingConfigSerializer : ConfigurationSerializerBase, IConfigurationSerializer
+{
+ public string Name => nameof(MultipleTextMigratingConfigSerializer);
+
+ public string[] Editors => [Constants.PropertyEditors.Aliases.MultipleTextstring];
+
+ public override IDictionary GetConfigurationImport(IDictionary configuration)
+ => MigratePropertyNames(configuration, new()
+ {
+ { "maximum", "max" },
+ { "minimum", "min" }
+ });
+}
diff --git a/uSync.Core/DataTypes/DataTypeSerializers/SliderMigratingConfigSerializer.cs b/uSync.Core/DataTypes/DataTypeSerializers/SliderMigratingConfigSerializer.cs
new file mode 100644
index 00000000..7b6a3626
--- /dev/null
+++ b/uSync.Core/DataTypes/DataTypeSerializers/SliderMigratingConfigSerializer.cs
@@ -0,0 +1,21 @@
+using NPoco.Linq;
+
+using Umbraco.Cms.Core;
+
+namespace uSync.Core.DataTypes.DataTypeSerializers;
+
+internal class SliderMigratingConfigSerializer : ConfigurationSerializerBase, IConfigurationSerializer
+{
+ public string Name => nameof(SliderMigratingConfigSerializer);
+ public string[] Editors => [Constants.PropertyEditors.Aliases.Slider];
+
+ public override IDictionary GetConfigurationImport(IDictionary configuration)
+ => MigratePropertyNames(configuration, new()
+ {
+ { "initialValue", "initVal1" },
+ { "initialValue2", "initVal2" },
+ { "maximumValue", "maxVal" },
+ { "minimumValue", "minVal" },
+ { "stepIncrements", "step" }
+ });
+}
diff --git a/uSync.Core/DataTypes/DataTypeSerializers/TagMigratingConfigSerializer.cs b/uSync.Core/DataTypes/DataTypeSerializers/TagMigratingConfigSerializer.cs
new file mode 100644
index 00000000..233444dc
--- /dev/null
+++ b/uSync.Core/DataTypes/DataTypeSerializers/TagMigratingConfigSerializer.cs
@@ -0,0 +1,41 @@
+using System.Text.Json;
+
+using Umbraco.Cms.Core;
+
+using uSync.Core.Extensions;
+
+namespace uSync.Core.DataTypes.DataTypeSerializers;
+
+internal class TagMigratingConfigSerializer : ConfigurationSerializerBase, IConfigurationSerializer
+{
+ public string Name => nameof(TagMigratingConfigSerializer);
+
+ public string[] Editors => [Constants.PropertyEditors.Aliases.Tags];
+
+ public override IDictionary GetConfigurationImport(IDictionary configuration)
+ {
+ if (configuration.TryGetValue("StorageType", out var storageType) is false
+ || storageType == null)
+ {
+ return configuration;
+ }
+
+ if (configuration.ContainsKey("delimiter"))
+ configuration.Remove("delimiter");
+
+ if (storageType is JsonElement element == false) return configuration;
+ if (element.ValueKind != JsonValueKind.Number) return configuration;
+ var storageNumber = element.GetValueAs();
+
+ var typeString = storageNumber == 0 ? "csv" : "Json";
+ // if storage type is a number.
+ configuration.Remove("StorageType");
+
+ configuration.Add("storageType", new string[] { typeString });
+
+ return configuration;
+
+
+ }
+
+}
diff --git a/uSync.Core/Extensions/DictionaryExtensions.cs b/uSync.Core/Extensions/DictionaryExtensions.cs
index 161ef326..e5d02efc 100644
--- a/uSync.Core/Extensions/DictionaryExtensions.cs
+++ b/uSync.Core/Extensions/DictionaryExtensions.cs
@@ -97,7 +97,7 @@ public static IDictionary MergeIgnoreDuplicates(this
public static IDictionary ConvertToCamelCase(this IDictionary originalDictionary)
=> originalDictionary
- .ToDictionary(kvp => kvp.Key.ToCamelCase(), kvp => kvp.Value);
+ .ToDictionary(kvp => kvp.Key.ToCamelCase(), kvp => kvp.Value, StringComparer.OrdinalIgnoreCase);
public static string ToCamelCase(this string s)
{
diff --git a/uSync.Core/Extensions/JsonTextExtensions.cs b/uSync.Core/Extensions/JsonTextExtensions.cs
index e00f734d..737805b8 100644
--- a/uSync.Core/Extensions/JsonTextExtensions.cs
+++ b/uSync.Core/Extensions/JsonTextExtensions.cs
@@ -2,6 +2,11 @@
using System.Text.Json;
using System.Text.Json.Nodes;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+using Org.BouncyCastle.Bcpg.Sig;
+
+using Umbraco.Cms.Core.Security;
using Umbraco.Extensions;
using uSync.Core.Json;
@@ -13,7 +18,7 @@ namespace uSync.Core.Extensions;
///
public static class JsonTextExtensions
{
- private static readonly JsonSerializerOptions _defaultOptions = new()
+ internal static readonly JsonSerializerOptions _defaultOptions = new()
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
@@ -21,7 +26,7 @@ public static class JsonTextExtensions
TypeInfoResolver = new OrderedPropertiesJsonResolver(),
};
- private static readonly JsonSerializerOptions _flatOptions = new()
+ internal static readonly JsonSerializerOptions _flatOptions = new()
{
WriteIndented = false,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
@@ -392,6 +397,9 @@ public static bool TryGetPropertyAsObject(this JsonObject jsonObject, string pro
return result != default;
}
+ ///
+ /// Gets the json property as a string, or returns string.empty
+ ///
public static string GetPropertyAsString(this JsonObject obj, string propertyName)
{
if (obj.TryGetPropertyValue(propertyName, out var value))
@@ -400,6 +408,28 @@ public static string GetPropertyAsString(this JsonObject obj, string propertyNam
return string.Empty;
}
+ public static bool GetPropertyAsBool(this JsonObject obj, string propertyName, bool defaultValue)
+ {
+ if (obj.TryGetPropertyValue(propertyName, out var value))
+ {
+ if (bool.TryParse(value?.ToString() ?? string.Empty, out var result) is false)
+ return defaultValue;
+
+ return result;
+ }
+
+ return defaultValue;
+ }
+
+ public static TResult GetPropertyValueOrDefault(this JsonObject obj, string propertyName, TResult defaultValue)
+ {
+ if (obj.TryGetPropertyValue(propertyName, out var value) is false || value is null)
+ return defaultValue;
+
+ var attempt = value.TryConvertTo();
+ return attempt.ResultOr(defaultValue);
+ }
+
public static bool TryGetPropertyAsArray(this JsonObject jsonObject, string propertyName, [MaybeNullWhen(false)] out JsonArray result)
{
result = default;
diff --git a/uSync.Core/Json/OrderedPropertiesJsonResolver.cs b/uSync.Core/Json/OrderedPropertiesJsonResolver.cs
index 397dc5c1..b28b9f33 100644
--- a/uSync.Core/Json/OrderedPropertiesJsonResolver.cs
+++ b/uSync.Core/Json/OrderedPropertiesJsonResolver.cs
@@ -18,17 +18,25 @@ internal class OrderedPropertiesJsonResolver : DefaultJsonTypeInfoResolver
{
public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
{
- var order = 0;
-
JsonTypeInfo typeInfo = base.GetTypeInfo(type, options);
- if (typeInfo.Kind != JsonTypeInfoKind.Object) return typeInfo;
- foreach(var property in typeInfo.Properties.OrderBy(x => x.Name))
+ switch(typeInfo.Kind)
{
- property.Order = order++;
+ case JsonTypeInfoKind.Object:
+ return SortObject(typeInfo);
+ default:
+ return typeInfo;
}
-
- return typeInfo;
}
+ private JsonTypeInfo SortObject(JsonTypeInfo typeInfo)
+ {
+ var order = 0;
+ foreach (var property in typeInfo.Properties.OrderBy(x => x.Name))
+ {
+ property.Order = order++;
+ }
+
+ return typeInfo;
+ }
}
diff --git a/uSync.Core/Serialization/Serializers/DataTypeSerializer.cs b/uSync.Core/Serialization/Serializers/DataTypeSerializer.cs
index 060fb46c..d0fef12c 100644
--- a/uSync.Core/Serialization/Serializers/DataTypeSerializer.cs
+++ b/uSync.Core/Serialization/Serializers/DataTypeSerializer.cs
@@ -164,23 +164,28 @@ protected override SyncAttempt DeserializeCore(XElement node, SyncSer
private List DeserializeConfiguration(IDataType item, XElement node)
{
- var serializer = _configurationSerializers.GetSerializer(item.EditorAlias);
+ // var serializer = _configurationSerializers.GetSerializer(item.EditorAlias);
var config = node.Element("Config").ValueOrDefault(string.Empty);
if (string.IsNullOrEmpty(config)) return [];
var changes = new List();
- if (config.TryDeserialize(out IDictionary? dictionaryData) is false || dictionaryData is null)
+ if (config.TryDeserialize(out IDictionary? importData) is false || importData is null)
{
changes.AddWarning("Data", item.Name ?? item.Id.ToString(), "Failed to deserialize config for item");
return changes;
}
// v8,9,etc configs the properties
- dictionaryData = dictionaryData.ConvertToCamelCase();
+ importData = importData.ConvertToCamelCase();
- var importData = serializer == null ? dictionaryData : serializer.GetConfigurationImport(dictionaryData);
+ // multiple serializers can run per property.
+ var serializers = _configurationSerializers.GetSerializers(item.EditorAlias);
+ foreach(var serializer in serializers) {
+ logger.LogDebug("Running Configuration Serializer : {name} for {type}", serializer.Name, item.EditorAlias);
+ importData = serializer.GetConfigurationImport(importData);
+ }
if (IsJsonEqual(importData, item.ConfigurationData) is false)
{
@@ -239,7 +244,7 @@ protected override IEnumerable GetContainers(IDataType item)
private XElement SerializeConfiguration(IDataType item)
{
var serializer = _configurationSerializers.GetSerializer(item.EditorAlias);
-
+
var configurationObject = TryGetConfigurationObject(item);
// merge the configurationData and configurationObject into one dictionary
diff --git a/uSync.Core/Serialization/Serializers/RichTextEditorMigratingSerializer.cs b/uSync.Core/Serialization/Serializers/RichTextEditorMigratingSerializer.cs
new file mode 100644
index 00000000..e38c55ce
--- /dev/null
+++ b/uSync.Core/Serialization/Serializers/RichTextEditorMigratingSerializer.cs
@@ -0,0 +1,82 @@
+using System.Collections.Immutable;
+using System.Text.Json;
+
+using Umbraco.Cms.Core;
+using Umbraco.Extensions;
+
+using uSync.Core.DataTypes;
+using uSync.Core.Extensions;
+
+namespace uSync.Core.Serialization.Serializers;
+internal class RichTextEditorMigratingSerializer : ConfigurationSerializerBase, IConfigurationSerializer
+{
+ public string Name => nameof(RichTextEditorMigratingSerializer);
+
+ public string[] Editors => [Constants.PropertyEditors.Aliases.RichText];
+
+ public override IDictionary GetConfigurationImport(IDictionary configuration)
+ {
+ configuration = TopLevelEditor(configuration);
+ configuration = FixMediaParent(configuration);
+
+ return configuration.ToImmutableSortedDictionary();
+ }
+
+ private IDictionary FixMediaParent(IDictionary configuration)
+ {
+ if (configuration.TryGetValue("mediaParentId", out var mediaParent) is false || mediaParent is null)
+ return configuration;
+
+ if (mediaParent is JsonElement element is false || element.ValueKind != JsonValueKind.String)
+ return configuration;
+
+ var mediaParentKey = element.ToString();
+ if (string.IsNullOrWhiteSpace(mediaParentKey)) return configuration;
+
+
+ if (UdiParser.TryParse(mediaParentKey, out var mediaUdi) is false)
+ return configuration;
+
+ if (mediaUdi is GuidUdi guidUdi is false)
+ return configuration;
+
+ var mediaItem = new MediaParentItem()
+ {
+ Key = $"_media_parent_{mediaParentKey}".EncodeAsGuid().ToString(),
+ MediaKey = guidUdi.Guid.ToString()
+ };
+
+ configuration["mediaParentId"] = mediaItem.AsEnumerableOfOne();
+ return configuration;
+ }
+
+ private class MediaParentItem
+ {
+ public required string Key { get; set; }
+ public required string MediaKey { get; set; }
+ public string MediaTypeAlias { get; set; } = "";
+ public string? FocalPoint { get; set; } = null;
+ public string[] Crops { get; set; } = [];
+ }
+
+ private IDictionary TopLevelEditor(IDictionary configuration)
+ {
+ if (configuration.TryGetValue("editor", out var editorObject) is false || editorObject is null)
+ return configuration;
+
+ if (editorObject is JsonElement element == false) return configuration;
+ if (element.ValueKind != JsonValueKind.Object) return configuration;
+
+ if (element.ToString().TryDeserialize>(out var obj) is false || obj is null)
+ return configuration;
+
+ configuration.Remove("editor");
+
+ foreach (var value in obj)
+ {
+ configuration[value.Key] = value.Value;
+ }
+
+ return configuration;
+ }
+}
diff --git a/uSync.Core/uSync.Core.csproj b/uSync.Core/uSync.Core.csproj
index 95d1760c..c5d04327 100644
--- a/uSync.Core/uSync.Core.csproj
+++ b/uSync.Core/uSync.Core.csproj
@@ -15,13 +15,9 @@
readme.md
enable
-
-
-
-
-
+
diff --git a/uSync.Core/uSyncCoreBuilderExtensions.cs b/uSync.Core/uSyncCoreBuilderExtensions.cs
index ad4a6327..4da510c1 100644
--- a/uSync.Core/uSyncCoreBuilderExtensions.cs
+++ b/uSync.Core/uSyncCoreBuilderExtensions.cs
@@ -35,7 +35,7 @@ public static IUmbracoBuilder AdduSyncCore(this IUmbracoBuilder builder)
// cache for entity items, we use it to speed up lookups.
builder.Services.AddSingleton();
-
+
// register *all* ConfigurationSerializers except those marked [HideFromTypeFinder]
// has to happen before the DataTypeSerializer is loaded, because that is where
// they are used
diff --git a/uSync.Tests/Migrations/ColourPickerTests.cs b/uSync.Tests/Migrations/ColourPickerTests.cs
new file mode 100644
index 00000000..0801a83f
--- /dev/null
+++ b/uSync.Tests/Migrations/ColourPickerTests.cs
@@ -0,0 +1,39 @@
+using NUnit.Framework;
+
+using uSync.Core.DataTypes.DataTypeSerializers;
+
+namespace uSync.Tests.Migrations;
+
+[TestFixture]
+internal class ColourPickerTests : MigrationTestBase
+{
+ private ColourPickerMigratingConfigSerializer _serializer = new ColourPickerMigratingConfigSerializer();
+
+ private static string Source = "{\r\n \"Items\": [\r\n {\r\n \"id\": 1,\r\n \"value\": \"{\\\"value\\\":\\\"ff0000\\\",\\\"label\\\":\\\"Red\\\"}\"\r\n },\r\n {\r\n \"id\": 2,\r\n \"value\": \"{\\\"value\\\":\\\"00ff00\\\",\\\"label\\\":\\\"Green\\\"}\"\r\n },\r\n {\r\n \"id\": 3,\r\n \"value\": \"{\\\"value\\\":\\\"0000ff\\\",\\\"label\\\":\\\"Blue\\\"}\"\r\n }\r\n ],\r\n \"UseLabel\": true\r\n}";
+ private static string Target = @"{
+ ""items"": [
+ {
+ ""label"": ""Red"",
+ ""value"": ""ff0000""
+ },
+ {
+ ""label"": ""Green"",
+ ""value"": ""00ff00""
+ },
+ {
+ ""label"": ""Blue"",
+ ""value"": ""0000ff""
+ }
+ ],
+ ""useLabel"": true
+}";
+
+ [Test]
+ public void ColourPickerValuesTest()
+ => TestSerializerPropertyMigration(_serializer, Source, Target);
+
+ [Test]
+ public void ColourPickerMigratedValueTest()
+ => TestSerializerPropertyMigration(_serializer, Target, Target);
+
+}
diff --git a/uSync.Tests/Migrations/FileUploadMigrationTests.cs b/uSync.Tests/Migrations/FileUploadMigrationTests.cs
new file mode 100644
index 00000000..cde6341e
--- /dev/null
+++ b/uSync.Tests/Migrations/FileUploadMigrationTests.cs
@@ -0,0 +1,40 @@
+using NUnit.Framework;
+
+using uSync.Core.DataTypes.DataTypeSerializers;
+
+namespace uSync.Tests.Migrations;
+
+[TestFixture]
+internal class FileUploadMigrationTests : MigrationTestBase
+{
+ private FileUploadMigratingConfigSerializer _serializer = new();
+
+ private static string Source = @"{
+ ""FileExtensions"": [
+ {
+ ""id"": 0,
+ ""value"": ""pdf""
+ },
+ {
+ ""id"": 1,
+ ""value"": ""png""
+ }
+ ]
+}";
+
+ private static string Target = @"{
+ ""fileExtensions"": [
+ ""pdf"",
+ ""png""
+ ]
+}";
+
+
+ [Test]
+ public void FileMigrationValueTest()
+ => TestSerializerPropertyMigration(_serializer, Source, Target);
+
+ [Test]
+ public void FileUploadMigratedValueTest()
+ => TestSerializerPropertyMigration(_serializer, Target, Target);
+}
diff --git a/uSync.Tests/Migrations/LabelMigrationTests.cs b/uSync.Tests/Migrations/LabelMigrationTests.cs
new file mode 100644
index 00000000..d0fd6d22
--- /dev/null
+++ b/uSync.Tests/Migrations/LabelMigrationTests.cs
@@ -0,0 +1,23 @@
+using NUnit.Framework;
+
+using uSync.Core.DataTypes.DataTypeSerializers;
+
+namespace uSync.Tests.Migrations;
+
+[TestFixture]
+internal class LabelMigrationTests : MigrationTestBase
+{
+ private LabelMigratingConfigSerializer _serializer= new LabelMigratingConfigSerializer();
+
+ private static string Source = "{ \"ValueType\": \"DECIMAL\" }";
+ private static string Target = "{\r\n \"umbracoDataValueType\": \"DECIMAL\"\r\n}";
+
+ [Test]
+ public void LabelMigrationValueTest()
+ => TestSerializerPropertyMigration(_serializer, Source, Target);
+
+ [Test]
+ public void LabelMigrationMigratedValueTest()
+ => TestSerializerPropertyMigration(_serializer, Target, Target);
+
+}
diff --git a/uSync.Tests/Migrations/ListMigrationTests.cs b/uSync.Tests/Migrations/ListMigrationTests.cs
new file mode 100644
index 00000000..316ceefd
--- /dev/null
+++ b/uSync.Tests/Migrations/ListMigrationTests.cs
@@ -0,0 +1,22 @@
+using NUnit.Framework;
+
+using uSync.Core.DataTypes.DataTypeSerializers;
+
+namespace uSync.Tests.Migrations;
+
+[TestFixture]
+internal class ListMigrationTests : MigrationTestBase
+{
+ private DataListMigratingConfigSerializer _serializer = new DataListMigratingConfigSerializer();
+
+ private static string Source = "{\r\n\t\"Items\": [\r\n\t\t{\r\n\t\t\t\"id\": 1,\r\n\t\t\t\"value\": \"One\"\r\n\t\t},\r\n\t\t{\r\n\t\t\t\"id\": 2,\r\n\t\t\t\"value\": \"Two\"\r\n\t\t},\r\n\t\t{\r\n\t\t\t\"id\": 3,\r\n\t\t\t\"value\": \"Three\"\r\n\t\t}\r\n\t]\r\n}\r\n";
+ private static string Target = "{\r\n \"items\": [\r\n \"One\",\r\n \"Two\",\r\n \"Three\"\r\n ]\r\n}";
+
+ [Test]
+ public void DropdownListMigrationTest()
+ => TestSerializerPropertyMigration(_serializer, Source, Target);
+
+ [Test]
+ public void DropdownListMigratedValueTest()
+ => TestSerializerPropertyMigration(_serializer, Target, Target);
+}
diff --git a/uSync.Tests/Migrations/MarkdownEditorMigrationTest.cs b/uSync.Tests/Migrations/MarkdownEditorMigrationTest.cs
new file mode 100644
index 00000000..2d66c35c
--- /dev/null
+++ b/uSync.Tests/Migrations/MarkdownEditorMigrationTest.cs
@@ -0,0 +1,21 @@
+using NUnit.Framework;
+
+using uSync.Core.DataTypes.DataTypeSerializers;
+
+namespace uSync.Tests.Migrations;
+
+[TestFixture]
+internal class MarkdownEditorMigrationTest : MigrationTestBase
+{
+ private MarkdownMigratingConfigSerializer _serializer = new MarkdownMigratingConfigSerializer();
+
+ private static string Source = "{\r\n \"DefaultValue\": \"#Hello\",\r\n \"DisplayLivePreview\": false,\r\n \"OverlaySize\": \"medium\"\r\n}";
+ private static string Target = "{\r\n \"defaultValue\": \"#Hello\",\r\n \"overlaySize\": \"medium\",\r\n \"preview\": false\r\n}";
+
+ [Test]
+ public void MarkdownSerializeTest()
+ => TestSerializerPropertyMigration(_serializer, Source, Target);
+ [Test]
+ public void MarkdownMigratedValueTest()
+ => TestSerializerPropertyMigration(_serializer, Target, Target);
+}
diff --git a/uSync.Tests/Migrations/MigrationTestBase.cs b/uSync.Tests/Migrations/MigrationTestBase.cs
new file mode 100644
index 00000000..6dc898bb
--- /dev/null
+++ b/uSync.Tests/Migrations/MigrationTestBase.cs
@@ -0,0 +1,35 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.Json;
+using System.Text.Json.Nodes;
+
+using NUnit.Framework;
+
+using Umbraco.Extensions;
+
+using uSync.Core.DataTypes;
+using uSync.Core.Extensions;
+
+namespace uSync.Tests.Migrations;
+internal class MigrationTestBase
+{
+
+ protected void TestSerializerPropertyMigration(IConfigurationSerializer serializer, string source, string target)
+ {
+ if (source.TryDeserialize(out IDictionary dictionaryData) is false || dictionaryData is null)
+ return;
+
+ dictionaryData = dictionaryData.ConvertToCamelCase();
+
+ var result = serializer.GetConfigurationImport(dictionaryData);
+
+ var targetDictionary =
+ JsonSerializer.Serialize(
+ JsonSerializer.Deserialize(target).ToDictionary(),
+ JsonTextExtensions._defaultOptions
+ );
+ var resultJson = JsonSerializer.Serialize(result, JsonTextExtensions._defaultOptions);
+
+ Assert.AreEqual(targetDictionary, resultJson);
+ }
+}
diff --git a/uSync.Tests/Migrations/MultipleTextMigratorTests.cs b/uSync.Tests/Migrations/MultipleTextMigratorTests.cs
new file mode 100644
index 00000000..3add1373
--- /dev/null
+++ b/uSync.Tests/Migrations/MultipleTextMigratorTests.cs
@@ -0,0 +1,22 @@
+using NUnit.Framework;
+
+using uSync.Core.DataTypes.DataTypeSerializers;
+
+namespace uSync.Tests.Migrations;
+
+[TestFixture]
+internal class MultipleTextMigratorTests : MigrationTestBase
+{
+ private MultipleTextMigratingConfigSerializer _serializer = new MultipleTextMigratingConfigSerializer();
+
+ private static string Source = "{\r\n \"Maximum\": 4,\r\n \"Minimum\": 1\r\n }";
+ private static string Target = "{\r\n \"max\": 4,\r\n \"min\": 1\r\n}";
+
+ [Test]
+ public void MultipleTextMigratorValuesTest()
+ => TestSerializerPropertyMigration(_serializer, Source, Target);
+
+ [Test]
+ public void MultipleTextMigratedValuesTest()
+ => TestSerializerPropertyMigration(_serializer, Target, Target);
+}
diff --git a/uSync.Tests/Migrations/RichTextMigrationTests.cs b/uSync.Tests/Migrations/RichTextMigrationTests.cs
new file mode 100644
index 00000000..5b0640cf
--- /dev/null
+++ b/uSync.Tests/Migrations/RichTextMigrationTests.cs
@@ -0,0 +1,128 @@
+using NUnit.Framework;
+
+using uSync.Core.Serialization.Serializers;
+
+namespace uSync.Tests.Migrations;
+
+[TestFixture]
+internal class RichTextMigrationTests: MigrationTestBase
+{
+ private RichTextEditorMigratingSerializer _serializer = new();
+
+ private static string Source = @"{
+ ""Blocks"": [
+ {
+ ""backgroundColor"": null,
+ ""contentElementTypeKey"": ""a0eec50c-54ce-47d3-97f1-c01843887567"",
+ ""displayInline"": false,
+ ""editorSize"": ""medium"",
+ ""forceHideContentEditorInOverlay"": false,
+ ""iconColor"": null,
+ ""label"": null,
+ ""settingsElementTypeKey"": null,
+ ""stylesheet"": null,
+ ""thumbnail"": null,
+ ""view"": null
+ }
+ ],
+ ""Editor"": {
+ ""toolbar"": [
+ ""ace"",
+ ""styles"",
+ ""bold"",
+ ""italic"",
+ ""alignleft"",
+ ""aligncenter"",
+ ""alignright"",
+ ""bullist"",
+ ""numlist"",
+ ""outdent"",
+ ""indent"",
+ ""link"",
+ ""umbmediapicker"",
+ ""umbmacro"",
+ ""umbembeddialog""
+ ],
+ ""stylesheets"": [
+ ""/Editor Styles.css""
+ ],
+ ""maxImageSize"": 500,
+ ""mode"": ""classic"",
+ ""dimensions"": {
+ ""width"": 500,
+ ""height"": 500
+ }
+ },
+ ""HideLabel"": false,
+ ""IgnoreUserStartNodes"": false,
+ ""MediaParentId"": ""umb://media/71332aa78bea44f19aa600de961b66e8"",
+ ""OverlaySize"": ""medium"",
+ ""UseLiveEditing"": false
+}";
+
+ private static string Target = @"{
+ ""blocks"": [
+ {
+ ""backgroundColor"": null,
+ ""contentElementTypeKey"": ""a0eec50c-54ce-47d3-97f1-c01843887567"",
+ ""displayInline"": false,
+ ""editorSize"": ""medium"",
+ ""forceHideContentEditorInOverlay"": false,
+ ""iconColor"": null,
+ ""label"": null,
+ ""settingsElementTypeKey"": null,
+ ""stylesheet"": null,
+ ""thumbnail"": null,
+ ""view"": null
+ }
+ ],
+ ""dimensions"": {
+ ""width"": 500,
+ ""height"": 500
+ },
+ ""hideLabel"": false,
+ ""ignoreUserStartNodes"": false,
+ ""maxImageSize"": 500,
+ ""mediaParentId"": [
+ {
+ ""crops"": [],
+ ""focalPoint"": null,
+ ""key"": ""5f6d6564-6961-5f70-6172-656e745f756d"",
+ ""mediaKey"": ""71332aa7-8bea-44f1-9aa6-00de961b66e8"",
+ ""mediaTypeAlias"": """"
+ }
+ ],
+ ""mode"": ""classic"",
+ ""overlaySize"": ""medium"",
+ ""stylesheets"": [
+ ""/Editor Styles.css""
+ ],
+ ""toolbar"": [
+ ""ace"",
+ ""styles"",
+ ""bold"",
+ ""italic"",
+ ""alignleft"",
+ ""aligncenter"",
+ ""alignright"",
+ ""bullist"",
+ ""numlist"",
+ ""outdent"",
+ ""indent"",
+ ""link"",
+ ""umbmediapicker"",
+ ""umbmacro"",
+ ""umbembeddialog""
+ ],
+ ""useLiveEditing"": false
+}";
+
+ [Test]
+ public void RichTextMigrationValueTest()
+ => TestSerializerPropertyMigration(_serializer, Source, Target);
+
+ [Test]
+ public void RichTextMigratedValueTest()
+ => TestSerializerPropertyMigration(_serializer, Target, Target);
+
+}
\ No newline at end of file
diff --git a/uSync.Tests/Migrations/SliderMigrationTests.cs b/uSync.Tests/Migrations/SliderMigrationTests.cs
new file mode 100644
index 00000000..4394257a
--- /dev/null
+++ b/uSync.Tests/Migrations/SliderMigrationTests.cs
@@ -0,0 +1,22 @@
+using NUnit.Framework;
+
+using uSync.Core.DataTypes.DataTypeSerializers;
+
+namespace uSync.Tests.Migrations;
+
+[TestFixture]
+internal class SliderMigrationTests : MigrationTestBase
+{
+ private SliderMigratingConfigSerializer _serializer = new SliderMigratingConfigSerializer();
+
+ private static string Source = "{\r\n \"EnableRange\": false,\r\n \"InitialValue\": 1,\r\n \"InitialValue2\": 2,\r\n \"MaximumValue\": 5,\r\n \"MinimumValue\": 1,\r\n \"StepIncrements\": 1\r\n}";
+ private static string Target = "{\r\n \"enableRange\": false,\r\n \"initVal1\": 1,\r\n \"initVal2\": 2,\r\n \"maxVal\": 5,\r\n \"minVal\": 1,\r\n \"step\": 1\r\n}";
+
+ [Test]
+ public void SliderMigrationValueTest()
+ => TestSerializerPropertyMigration(_serializer, Source, Target);
+
+ [Test]
+ public void SliderMigratedValueTest()
+ => TestSerializerPropertyMigration(_serializer, Target, Target);
+}
diff --git a/uSync.Tests/Migrations/TagMigrationTest.cs b/uSync.Tests/Migrations/TagMigrationTest.cs
new file mode 100644
index 00000000..3658770a
--- /dev/null
+++ b/uSync.Tests/Migrations/TagMigrationTest.cs
@@ -0,0 +1,32 @@
+using NUnit.Framework;
+
+using uSync.Core.DataTypes.DataTypeSerializers;
+
+namespace uSync.Tests.Migrations;
+
+[TestFixture]
+internal class TagMigrationTest : MigrationTestBase
+{
+ private TagMigratingConfigSerializer _serializer = new TagMigratingConfigSerializer();
+
+ private static string Source = @"{
+ ""Delimiter"": ""\u0000"",
+ ""Group"": ""taggroup"",
+ ""StorageType"": 0
+}";
+ private static string Target = @"{
+ ""group"": ""taggroup"",
+ ""storageType"": [
+ ""csv""
+ ]
+}";
+
+ [Test]
+ public void TagValueMigrationTest()
+ => TestSerializerPropertyMigration(_serializer, Source, Target);
+
+ [Test]
+
+ public void TagValueMigratedValuesTest()
+ => TestSerializerPropertyMigration(_serializer, Target, Target);
+}
\ No newline at end of file