diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonMerger.cpp b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonMerger.cpp index de9aa70362..db1172d245 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonMerger.cpp +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonMerger.cpp @@ -607,10 +607,12 @@ namespace AZ } else if (source.Size() > target.Size()) { - rapidjson::SizeType sourceCount = source.Size(); - for (rapidjson::SizeType i = count; i < sourceCount; ++i) + // Loop backwards through the removals so that each removal has a valid index when processing in order. + for (rapidjson::SizeType i = source.Size(); i > count; --i) { - ScopedStackedString entryName(element, i); + // (We use "i - 1" here instead of in the loop to ensure we don't wrap around our unsigned numbers in the case + // where count is 0.) + ScopedStackedString entryName(element, i - 1); patch.PushBack(CreatePatchInternal_Remove(allocator, element), allocator); resultCode.Combine(settings.m_reporting("Removed member from array in JSON Patch.", ResultCode(Tasks::CreatePatch, Outcomes::Success), element)); diff --git a/Code/Framework/AzCore/Tests/Serialization/Json/TestCases_Patching.cpp b/Code/Framework/AzCore/Tests/Serialization/Json/TestCases_Patching.cpp index 0906eb45f8..1f6c6142e8 100644 --- a/Code/Framework/AzCore/Tests/Serialization/Json/TestCases_Patching.cpp +++ b/Code/Framework/AzCore/Tests/Serialization/Json/TestCases_Patching.cpp @@ -303,6 +303,29 @@ namespace JsonSerializationTests R"( { "foo": [ "bar", "baz" ] })"); } + TEST_F(JsonPatchingSerializationTests, ApplyPatch_UseJsonPatchRemoveArrayMembersInCorrectOrder_ReportsSuccess) + { + CheckApplyPatch( + R"( { "foo": [ "bar", "qux", "baz" ] })", + R"( [ + { "op": "remove", "path": "/foo/2" }, + { "op": "remove", "path": "/foo/1" } + ])", + R"( { "foo": [ "bar" ] })"); + } + + TEST_F(JsonPatchingSerializationTests, ApplyPatch_UseJsonPatchRemoveArrayMembersInWrongOrder_ReportsError) + { + using namespace AZ::JsonSerializationResult; + CheckApplyPatchOutcome( + R"( { "foo": [ "bar", "qux", "baz" ] })", + R"( [ + { "op": "remove", "path": "/foo/1" }, + { "op": "remove", "path": "/foo/2" } + ])", + Outcomes::Invalid, Processing::Halted); + } + TEST_F(JsonPatchingSerializationTests, ApplyPatch_UseJsonPatchRemoveOperationInvalidParent_ReportError) { using namespace AZ::JsonSerializationResult; @@ -949,6 +972,27 @@ namespace JsonSerializationTests ); } + TEST_F(JsonPatchingSerializationTests, CreatePatch_UseJsonPatchRemoveLastArrayEntries_MultipleOperationsInCorrectOrder) + { + CheckCreatePatch( + R"( [ "foo", "hello", "bar" ])", R"( [ "foo" ])", + R"( [ + { "op": "remove", "path": "/2" }, + { "op": "remove", "path": "/1" } + ])"); + } + + TEST_F(JsonPatchingSerializationTests, CreatePatch_UseJsonPatchRemoveAllArrayEntries_MultipleOperationsInCorrectOrder) + { + CheckCreatePatch( + R"( [ "foo", "hello", "bar" ])", R"( [])", + R"( [ + { "op": "remove", "path": "/2" }, + { "op": "remove", "path": "/1" }, + { "op": "remove", "path": "/0" } + ])"); + } + TEST_F(JsonPatchingSerializationTests, CreatePatch_UseJsonPatchRemoveObjectFromArrayInMiddle_OperationToUpdateMember) { CheckCreatePatch( diff --git a/Code/Tools/SerializeContextTools/SliceConverter.cpp b/Code/Tools/SerializeContextTools/SliceConverter.cpp index e7dc49d5b3..70412e23bc 100644 --- a/Code/Tools/SerializeContextTools/SliceConverter.cpp +++ b/Code/Tools/SerializeContextTools/SliceConverter.cpp @@ -385,24 +385,36 @@ namespace AZ return false; } - // Now, convert the nested slice to a prefab. - bool nestedSliceResult = ConvertSliceFile(serializeContext, assetPath, isDryRun); - if (!nestedSliceResult) - { - AZ_Warning("Convert-Slice", nestedSliceResult, " Nested slice '%s' could not be converted.", assetPath.c_str()); - return false; - } + // Check to see if we've already converted this slice at a higher level of slice nesting, or if this is our first + // occurrence and we need to convert it now. - // Find the prefab template we created for the newly-created nested prefab. - // To get the template, we need to take our absolute slice path and turn it into a project-relative prefab path. + // First, take our absolute slice path and turn it into a project-relative prefab path. AZ::IO::Path nestedPrefabPath = assetPath; nestedPrefabPath.ReplaceExtension("prefab"); auto prefabLoaderInterface = AZ::Interface::Get(); nestedPrefabPath = prefabLoaderInterface->GenerateRelativePath(nestedPrefabPath); + // Now, see if we already have a template ID in memory for it. AzToolsFramework::Prefab::TemplateId nestedTemplateId = prefabSystemComponentInterface->GetTemplateIdFromFilePath(nestedPrefabPath); + + // If we don't have a template ID yet, convert the nested slice to a prefab and get the template ID. + if (nestedTemplateId == AzToolsFramework::Prefab::InvalidTemplateId) + { + bool nestedSliceResult = ConvertSliceFile(serializeContext, assetPath, isDryRun); + if (!nestedSliceResult) + { + AZ_Warning("Convert-Slice", nestedSliceResult, " Nested slice '%s' could not be converted.", assetPath.c_str()); + return false; + } + + nestedTemplateId = prefabSystemComponentInterface->GetTemplateIdFromFilePath(nestedPrefabPath); + AZ_Assert(nestedTemplateId != AzToolsFramework::Prefab::InvalidTemplateId, + "Template ID for %s is invalid", nestedPrefabPath.c_str()); + } + + // Get the nested prefab template. AzToolsFramework::Prefab::TemplateReference nestedTemplate = prefabSystemComponentInterface->FindTemplate(nestedTemplateId);