// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "Dom/JsonObject.h" #include "Dom/JsonValue.h" /** * Utility to update an array of json objects from an array of elements (of arbitrary type). * Elements in the source array and the destination json object array are matched based on an * arbitrary key (provided by the FGetElementKey and FTryGetJsonObjectKey delegates respectively). * Existing elements get "updated" via the FUpdateJsonObject delegate. The update scheme is entirely * customizable; for example, it can be non-destructive and leave some json fields unchanged. * Elements from the source array that are not in the json array (based on the "key") are added to it. * Elements that are not present in the source array (based on the "key") are removed from the json array. * If the source array is empty the json array field is removed. */ template struct FJsonObjectArrayUpdater { DECLARE_DELEGATE_RetVal_OneParam(KeyType, FGetElementKey, const ElementType&); DECLARE_DELEGATE_RetVal_TwoParams(bool, FTryGetJsonObjectKey, const FJsonObject&, KeyType& /*OutKey*/); DECLARE_DELEGATE_TwoParams(FUpdateJsonObject, const ElementType&, FJsonObject&); DECLARE_DELEGATE_OneParam(FSortArray, TArray>&); static void Execute(FJsonObject& JsonObject, const FString& ArrayName, const TArray& SourceArray, FGetElementKey GetElementKey, FTryGetJsonObjectKey TryGetJsonObjectKey, FUpdateJsonObject UpdateJsonObject, FSortArray SortJsonArray = FSortArray()) { if (SourceArray.Num() > 0) { TArray> NewJsonValues; { const TArray>* ExistingJsonValues; if (JsonObject.TryGetArrayField(ArrayName, ExistingJsonValues)) { // Build a map of elements for quick access and to keep track of which ones got updated TMap ElementsMap; for (const ElementType& Element : SourceArray) { ElementsMap.Add(GetElementKey.Execute(Element), &Element); } // Update existing json values and discard entries that no longer exist or are invalid for (TSharedPtr ExistingJsonValue : *ExistingJsonValues) { const TSharedPtr* ExistingJsonValueAsObject; if (ExistingJsonValue->TryGetObject(ExistingJsonValueAsObject)) { KeyType ElementKey; if (TryGetJsonObjectKey.Execute(**ExistingJsonValueAsObject, ElementKey)) { if (const ElementType** ElementPtr = ElementsMap.Find(ElementKey)) { UpdateJsonObject.Execute(**ElementPtr, **ExistingJsonValueAsObject); NewJsonValues.Add(ExistingJsonValue); ElementsMap.Remove(ElementKey); } } } } // Add new elements for (auto It = ElementsMap.CreateConstIterator(); It; ++It) { TSharedPtr NewJsonObject = MakeShareable(new FJsonObject); UpdateJsonObject.Execute(*It.Value(), *NewJsonObject.Get()); NewJsonValues.Add(MakeShareable(new FJsonValueObject(NewJsonObject))); } } else { // Array doesn't exist in the given JsonObject, so build a new array for (const ElementType& Element : SourceArray) { TSharedPtr NewJsonObject = MakeShareable(new FJsonObject); UpdateJsonObject.Execute(Element, *NewJsonObject.Get()); NewJsonValues.Add(MakeShareable(new FJsonValueObject(NewJsonObject))); } } } // Optionally sort new json array before setting incase the user doesn't want the default // appending of new items at the end. SortJsonArray.ExecuteIfBound(NewJsonValues); // Set the new content of the json array JsonObject.SetArrayField(ArrayName, NewJsonValues); } else { // Source array is empty so remove the json array JsonObject.RemoveField(ArrayName); } } };