Migrations: Adjust the JsonBlockValueConverter to handle conflicts with 'values' property (#20429)

* Adjust the `JsonBlockValueConverter` to handle conflicts with 'values' property (due to old data schema)

* Simplify code

* Add unit test to verify change.

---------

Co-authored-by: Andy Butland <abutland73@gmail.com>
This commit is contained in:
Laura Neto
2025-10-09 09:41:41 +02:00
committed by GitHub
parent 16132b0075
commit 1fe7931d07
2 changed files with 84 additions and 2 deletions

View File

@@ -1,4 +1,5 @@
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using Umbraco.Cms.Core.Models.Blocks;
using Umbraco.Extensions;
@@ -121,7 +122,17 @@ public class JsonBlockValueConverter : JsonConverter<BlockValue>
}
private List<BlockItemData> DeserializeBlockItemData(ref Utf8JsonReader reader, JsonSerializerOptions options, Type typeToConvert, string propertyName)
=> DeserializeListOf<BlockItemData>(ref reader, options, typeToConvert, propertyName);
{
try
{
return DeserializeListOf<BlockItemData>(ref reader, options, typeToConvert, propertyName);
}
catch (JsonException ex) when (ex.Path?.EndsWith(".values") is true)
{
// If we hit a JsonException due to the "values" property conflict, attempt the fallback deserialization
return FallbackBlockItemDataDeserialization(ref reader, options);
}
}
private List<BlockItemVariation> DeserializeBlockVariation(ref Utf8JsonReader reader, JsonSerializerOptions options, Type typeToConvert, string propertyName)
=> DeserializeListOf<BlockItemVariation>(ref reader, options, typeToConvert, propertyName);
@@ -224,5 +235,38 @@ public class JsonBlockValueConverter : JsonConverter<BlockValue>
}
}
}
}
[Obsolete("Only needed to support the old data schema. Remove in V18.")]
private static List<BlockItemData> FallbackBlockItemDataDeserialization(ref Utf8JsonReader reader, JsonSerializerOptions options)
{
JsonArray? arrayElement = JsonSerializer.Deserialize<JsonArray>(ref reader, options);
return arrayElement?
.Select(itemElement => DeserializeBlockItemData(itemElement, options))
.OfType<BlockItemData>()
.ToList() ?? [];
}
[Obsolete("Only needed to support the old data schema. Remove in V18.")]
private static BlockItemData? DeserializeBlockItemData(JsonNode? jsonNode, JsonSerializerOptions options)
{
if (jsonNode is not JsonObject jsonObject || jsonObject.ContainsKey("values") is false)
{
// Nothing to be done, just deserialize as usual
return jsonNode.Deserialize<BlockItemData>(options);
}
// Handle the "values" property conflict by extracting the "values" property first and adding it to the
// RawPropertyValues dictionary after deserialization
JsonNode? values = jsonObject["values"];
jsonObject.Remove("values");
BlockItemData? blockItemData = jsonObject.Deserialize<BlockItemData>(options);
if (blockItemData is not null)
{
blockItemData.RawPropertyValues["values"] = values.Deserialize<object?>(options);
}
return blockItemData;
}
}

View File

@@ -470,4 +470,42 @@ public class JsonBlockValueConverterTests
var serializer = new SystemTextJsonSerializer(new DefaultJsonSerializerEncoderFactory());
Assert.DoesNotThrow(() => serializer.Deserialize<BlockListValue>(json));
}
/// <summary>
/// Test case that verifies the fix for https://github.com/umbraco/Umbraco-CMS/issues/20409.
/// </summary>
[Test]
public void Can_Deserialize_BlockGrid_With_Blocks_Using_Values_As_Property_Alias()
{
// Create a serialized BlockGridValue in Umbraco 13 format that has a block with a property alias "values".
var serialized = @"{
""layout"":{
""Umbraco.BlockList"":[
{
""contentUdi"":""umb://element/6ad18441631140d48515ea0fc5b00425""
}
]
},
""contentData"":[
{
""contentTypeKey"":""a1d1123c-289b-4a05-b33f-9f06cb723da1"",
""udi"":""umb://element/6ad18441631140d48515ea0fc5b00425"",
""text"":""Text"",
""values"":""Values""
}
],
""settingsData"":[
]
}";
var serializer = new SystemTextJsonSerializer(new DefaultJsonSerializerEncoderFactory());
var deserialized = serializer.Deserialize<BlockGridValue>(serialized);
Assert.IsNotNull(deserialized);
Assert.AreEqual(1, deserialized.ContentData.Count);
Assert.AreEqual(2, deserialized.ContentData[0].RawPropertyValues.Count);
Assert.AreEqual("Text", deserialized.ContentData[0].RawPropertyValues["text"]);
Assert.AreEqual("Values", deserialized.ContentData[0].RawPropertyValues["values"]);
}
}