diff --git a/src/Umbraco.Infrastructure/Serialization/JsonBlockValueConverter.cs b/src/Umbraco.Infrastructure/Serialization/JsonBlockValueConverter.cs index 71624b6529b0..b29e829266fe 100644 --- a/src/Umbraco.Infrastructure/Serialization/JsonBlockValueConverter.cs +++ b/src/Umbraco.Infrastructure/Serialization/JsonBlockValueConverter.cs @@ -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 @@ private static Type GetLayoutItemType(Type blockValueType) } private List DeserializeBlockItemData(ref Utf8JsonReader reader, JsonSerializerOptions options, Type typeToConvert, string propertyName) - => DeserializeListOf(ref reader, options, typeToConvert, propertyName); + { + try + { + return DeserializeListOf(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 DeserializeBlockVariation(ref Utf8JsonReader reader, JsonSerializerOptions options, Type typeToConvert, string propertyName) => DeserializeListOf(ref reader, options, typeToConvert, propertyName); @@ -224,5 +235,38 @@ private void DeserializeAndSetLayout(ref Utf8JsonReader reader, JsonSerializerOp } } } -} + [Obsolete("Only needed to support the old data schema. Remove in V18.")] + private static List FallbackBlockItemDataDeserialization(ref Utf8JsonReader reader, JsonSerializerOptions options) + { + JsonArray? arrayElement = JsonSerializer.Deserialize(ref reader, options); + + return arrayElement? + .Select(itemElement => DeserializeBlockItemData(itemElement, options)) + .OfType() + .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(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(options); + if (blockItemData is not null) + { + blockItemData.RawPropertyValues["values"] = values.Deserialize(options); + } + + return blockItemData; + } +} diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Serialization/JsonBlockValueConverterTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Serialization/JsonBlockValueConverterTests.cs index f48c286a91e0..4d76b2ad8731 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Serialization/JsonBlockValueConverterTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Serialization/JsonBlockValueConverterTests.cs @@ -470,4 +470,42 @@ public void Try_Deserialize_Unknown_Block_Layout_With_Nested_Array() var serializer = new SystemTextJsonSerializer(new DefaultJsonSerializerEncoderFactory()); Assert.DoesNotThrow(() => serializer.Deserialize(json)); } + + /// + /// Test case that verifies the fix for https://github.com/umbraco/Umbraco-CMS/issues/20409. + /// + [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(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"]); + } }