PrivateDependencyModuleNames.AddRange(
new string[]
{
"Json",
"JsonUtilities"
}
);
Build.cs에 Json / JsonUtilities 모듈을 추가
PrivateDependencyModuleNames.AddRange(
new string[]
{
"Json",
"JsonUtilities"
}
);
Json 모듈은 Json파일을 읽을 수 있는 기능들이 있고,
JsonUtilities 모듈은 언리얼의 타입이나 구조체를 JsonObject 나 JsonValue로 바꾸어주는, 언리얼을 위한 기능들이 있다.
FString JsonString;
if (!FFileHelper::LoadFileToString(JsonString, *FilePath))
{
PROCESS_FAIL(TEXT("JSON 파일 로드 실패"));
return;
}
TSharedPtr<FJsonObject> JsonObject = MakeShared<FJsonObject>();
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonString);
if (!FJsonSerializer::Deserialize(Reader, JsonObject) || !JsonObject.IsValid())
{
PROCESS_FAIL(TEXT("JSON 파싱 실패"));
return;
}
Deserialize 함수를 통하여 Json 파일을 FJsonObject 타입으로 읽을 수 있다.
const TSharedPtr<FJsonObject> JsonObject = MakeShareable(new FJsonObject());
// ... JsonObject 내용 추가
FString OutputJson;
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutputJson);
FJsonSerializer::Serialize(JsonObject.ToSharedRef(), Writer);
const bool bSuccess = FFileHelper::SaveStringToFile(OutputJson, *FilePath);
if (bSuccess) { PROCESS_SUCCESS(TEXT("JSON 파일 변환 성공")); }
else { PROCESS_FAIL(TEXT("JSON 파일 변환 실패")); }
반대로 Serialize 함수를 통해 FJsonObject를 Json파일로 뽑아낼 수 있다.
FJsonObject / FJsonValue
{
"SaveGameClass": "/Script/TestSaveGame.EconomySaveGameData",
"GoldPoint": 300,
"CrystalPoint": 0,
"CrystalFreePoint": 0,
"LastAccessTime": "2025-04-03T12:28:14.212Z",
"WildGinsengInfo":
{
"leftSellCount": 3,
"preRateValue": 100,
"currentRateValue": 100
},
"SaveFileRevision": 1
}
FJsonObject는 FJsonValueObject가 될 수고 있고 FJsonValueObject는 FJsonValue를 상속 받고
FJsonValue 는 AsObject() 로 JsonObject가 될 수도 있고
그냥 둘 다 서로 치환 가능하다. FJsonValue <-> FJsonObject
// JsonValue -> JsonArray
TArray<TSharedPtr<FJsonValue>>& JsonArray = JsonValue->AsArray();
// JsonValue -> JsonObject
TSharedPtr<FJsonObject> ObjectJson = JsonValue->AsObject();
// JsonObject 내부는 Map 으로 매핑되어있고, JsonValue 또한 JsonObject 가 될 수 있다.
TMap<FString, TSharedPtr<FJsonValue>> JsonValues = JsonObject->Values;
언리얼 오브젝트 -> Json
TFieldIterator 을 통하여 해당 클래스의 리플렉션 (UPROPERTY / UFUNCION) 매크로가 지정된 속성과 함수 순회가 가능하다.
TSharedPtr<FJsonObject>& JsonObject; // 값 채워져 있다고 가정
// 멤버변수
const UClass* SaveGameClass = SaveGame->GetClass();
for (TFieldIterator<FProperty> It(SaveGameClass); It; ++It)
{
FProperty* Property = *It;
void* ValuePtr = Property->ContainerPtrToValuePtr<void>(SaveGame);
TSharedPtr<FJsonValue> JsonValue = FJsonObjectConverter::UPropertyToJsonValue(Property, ValuePtr);
JsonObject->SetField(Property->GetName(), JsonValue);
}
중단점을 찍어보면 현재 PropertyName (Key) 와 어떤 Property 타입인지, 다음 필드는 어떤건지도 확인할 수 있다.
SaveGameClass 에 선언된 타입으로 알아서 매칭하여 FInt64Property 타입으로 가져오는걸 확인할 수 있다.
// 생략...
UPROPERTY()
int32 INumber;
UPROPERTY()
float fNumber;
UPROPERTY()
int64 I64Number;
UPROPERTY()
FString String;
// 생략...
void*
어떤 자료형이든 주소값을 저장하는 타입없는 포인터
void* ValuePtr = Property->ContainerPtrToValuePtr<void>(SaveGame);
ContainerPtrToValuePtr 함수는 SaveGame에서 해당 Property 값에 대한 포인터를 반환
FProperty 만으론 해당 Property 값이 어디있는지 모르므로 실제 인스턴스에서 해당 Property가 있는 주소를 가져옴
이제 해당 멤버변수의 주소값 Json 에 Key - Value 형식으로 지정해보자
언리얼에 이미 잘 구현되어있는 함수가 있다.
TSharedPtr<FJsonValue> JsonValue = FJsonObjectConverter::UPropertyToJsonValue(Property, ValuePtr);
JsonObject->SetField(Property->GetName(), JsonValue);
![]() |
![]() |
구조체일때 배열일 때 맵 일때 다 나누어서 따로 구현을 다 해놓았었는데 이미 언리얼 기능이 있다는것을 알아버렸다.
보면 기가막히게 알아서 바꾸어준다.
구조체 같은 경우 TFieldIterator<FProperty> 를 이용하여 멤버변수를 순회하여 값을 기록하는데 DateTime 구조체 같은경우 멤버변수가 Int64 InTicks만 있어 String으로 변환하여 예외처리를 하였는데 내부에서 이미 구현되어있었다.
// ConvertScalarFPropertyToJsonValue 중..
else if (FStructProperty *StructProperty = CastField<FStructProperty>(Property))
{
UScriptStruct::ICppStructOps* TheCppStructOps = StructProperty->Struct->GetCppStructOps();
// Intentionally exclude the JSON Object wrapper, which specifically needs to export JSON in an object representation instead of a string
if (StructProperty->Struct != FJsonObjectWrapper::StaticStruct() && TheCppStructOps && TheCppStructOps->HasExportTextItem())
{
FString OutValueStr;
TheCppStructOps->ExportTextItem(OutValueStr, Value, nullptr, nullptr, PPF_None, nullptr);
return MakeShared<FJsonValueString>(OutValueStr);
}
TSharedRef<FJsonObject> Out = MakeShared<FJsonObject>();
if (FJsonObjectConverter::UStructToJsonObject(StructProperty->Struct, Value, Out, CheckFlags & (~CPF_ParmFlags), SkipFlags, ExportCb, ConversionFlags))
{
return MakeShared<FJsonValueObject>(Out);
}
}
CPP 구조체 중에서도 ExportTextItem 이라는 함수를 통해 텍스트로 변환할 수 있는 구조체이면 첫번 째 if문을,
그것이 아니라면 두번 째 if문을 통하여 TFieldIterator<FProperty> 를 순회하여 멤버변수 모두를 기록한다.
참고로 Property의 ArrayDim은 컴파일 타임에 배열 크기를 나타내는데 사용
예를들어 Int32[5] 의 ArrayDim은 5가 나오고
TArray / TMap / TSet 은 동적 컨테이너이기 때문에 무조건 1이 나온다
Json -> 언리얼 오브젝트
JsonObject 내부에 있는 Value들을 하나씩 순회하여 해당 Key에 맞는 인스턴스 Property의 주소값을 찾은 뒤 Json값을 대입하는 식으로 하면 된다.
USaveGame* SaveGame = NewObject<USaveGame>(GetTransientPackage(), SaveGameClass);
for (const TTuple<FString, TSharedPtr<FJsonValue>>& Entry : JsonObject->Values)
{
FProperty* Property = SaveGame->GetClass()->FindPropertyByName(FName(*Entry.Key));
void* ValuePtr = Property->ContainerPtrToValuePtr<void>(SaveGame);
TSharedPtr<FJsonValue> JsonValue = Entry.Value;
FJsonObjectConverter::JsonValueToUProperty(JsonValue, Property, ValuePtr, 0, 0, false, OutFailReason);
}
이 JsonValueToUproperty도 알아서 값을 넣어주는데 구조체 부분쪽을 살펴보면 각 구조체에 대해 예외처리가 좀 되어있는것을 알 수 있다.
// ConvertScalarJsonValueToFPropertyWithContainer 중..
else if (FStructProperty *StructProperty = CastField<FStructProperty>(Property))
{
if (JsonValue->Type == EJson::Object)
{
TSharedPtr<FJsonObject> Obj = JsonValue->AsObject();
check(Obj.IsValid()); // should not fail if Type == EJson::Object
if (!JsonAttributesToUStructWithContainer(Obj->Values, StructProperty->Struct, OutValue, ContainerStruct, Container, CheckFlags & (~CPF_ParmFlags), SkipFlags, bStrictMode, OutFailReason))
{
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import JSON object into %s property %s"), *StructProperty->Struct->GetAuthoredName(), *Property->GetAuthoredName());
if (OutFailReason)
{
*OutFailReason = FText::Format(LOCTEXT("FailImportStructFromObject", "Unable to import JSON object into {0} property {1}\n{2}"), FText::FromString(StructProperty->Struct->GetAuthoredName()), FText::FromString(Property->GetAuthoredName()), *OutFailReason);
}
return false;
}
}
else if (JsonValue->Type == EJson::String && StructProperty->Struct->GetFName() == NAME_LinearColor)
{
FLinearColor& ColorOut = *(FLinearColor*)OutValue;
FString ColorString = JsonValue->AsString();
FColor IntermediateColor;
IntermediateColor = FColor::FromHex(ColorString);
ColorOut = IntermediateColor;
}
else if (JsonValue->Type == EJson::String && StructProperty->Struct->GetFName() == NAME_Color)
{
FColor& ColorOut = *(FColor*)OutValue;
FString ColorString = JsonValue->AsString();
ColorOut = FColor::FromHex(ColorString);
}
else if (JsonValue->Type == EJson::String && StructProperty->Struct->GetFName() == NAME_DateTime)
{
FString DateString = JsonValue->AsString();
FDateTime& DateTimeOut = *(FDateTime*)OutValue;
if (DateString == TEXT("min"))
{
// min representable value for our date struct. Actual date may vary by platform (this is used for sorting)
DateTimeOut = FDateTime::MinValue();
}
else if (DateString == TEXT("max"))
{
// max representable value for our date struct. Actual date may vary by platform (this is used for sorting)
DateTimeOut = FDateTime::MaxValue();
}
else if (DateString == TEXT("now"))
{
// this value's not really meaningful from JSON serialization (since we don't know timezone) but handle it anyway since we're handling the other keywords
DateTimeOut = FDateTime::UtcNow();
}
else if (FDateTime::ParseIso8601(*DateString, DateTimeOut))
{
// ok
}
else if (FDateTime::Parse(DateString, DateTimeOut))
{
// ok
}
else
{
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import JSON string into DateTime property %s"), *Property->GetAuthoredName());
if (OutFailReason)
{
*OutFailReason = FText::Format(LOCTEXT("FailImportDateTimeFromString", "Unable to import JSON string into DateTime property {0}"), FText::FromString(Property->GetAuthoredName()));
}
return false;
}
}
else if (JsonValue->Type == EJson::String && StructProperty->Struct->GetCppStructOps() && StructProperty->Struct->GetCppStructOps()->HasImportTextItem())
{
UScriptStruct::ICppStructOps* TheCppStructOps = StructProperty->Struct->GetCppStructOps();
FString ImportTextString = JsonValue->AsString();
const TCHAR* ImportTextPtr = *ImportTextString;
if (!TheCppStructOps->ImportTextItem(ImportTextPtr, OutValue, PPF_None, nullptr, (FOutputDevice*)GWarn))
{
// Fall back to trying the tagged property approach if custom ImportTextItem couldn't get it done
if (Property->ImportText_Direct(ImportTextPtr, OutValue, nullptr, PPF_None) == nullptr)
{
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import JSON string into %s property %s"), *StructProperty->Struct->GetAuthoredName(), *Property->GetAuthoredName());
if (OutFailReason)
{
*OutFailReason = FText::Format(LOCTEXT("FailImportStructFromString", "Unable to import JSON string into {0} property {1}"), FText::FromString(StructProperty->Struct->GetAuthoredName()), FText::FromString(Property->GetAuthoredName()));
}
return false;
}
}
}
else if (JsonValue->Type == EJson::String)
{
FString ImportTextString = JsonValue->AsString();
const TCHAR* ImportTextPtr = *ImportTextString;
if (Property->ImportText_Direct(ImportTextPtr, OutValue, nullptr, PPF_None) == nullptr)
{
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import JSON string into %s property %s"), *StructProperty->Struct->GetAuthoredName(), *Property->GetAuthoredName());
if (OutFailReason)
{
*OutFailReason = FText::Format(LOCTEXT("FailImportStructFromString", "Unable to import JSON string into {0} property {1}"), FText::FromString(StructProperty->Struct->GetAuthoredName()), FText::FromString(Property->GetAuthoredName()));
}
return false;
}
}
else
{
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import JSON value that is neither string nor object into %s property %s"), *StructProperty->Struct->GetAuthoredName(), *Property->GetAuthoredName());
if (OutFailReason)
{
*OutFailReason = FText::Format(LOCTEXT("FailImportStruct", "Unable to import JSON value that is neither string nor object into {0} property {1}"), FText::FromString(StructProperty->Struct->GetAuthoredName()), FText::FromString(Property->GetAuthoredName()));
}
return false;
}
}
Object 형식으로 되어있는 커스텀 구조체들은 JsonAttributesToUStructWithContainer 함수 내부에서 TFieldIterator 으로 순회하여 값을 대입하고,
FDateTime 같이 String 으로 되어있는 값들은 다시 구조체로 변환 시켜준다.
JsonValue는 String 이지만, UObject 클래스 에서 순회한 Property가 구조체 타입이기 때문에 FDateTime으로 인식할 수 있는것
참고로 TArray / TSet 은 EJson::Array , TMap 은 EJson::Object 타입으로 이루어진다.
https://moris0712.tistory.com/112
Save / Load GameData (.sav)
Sav 파일메모리 카드나 하드 드라이브와 같은 저장 매체와 함께 사용하도록 개발된 데이터 파일 형식언리얼은 윈도우 기준으로 Saved/SavedGames 폴더에 .sav 파일로 저장된다.이 파일을 사용하려면 US
moris0712.tistory.com
Sav 파일에 데이터를 넣기위해 FArchive 를 이용해 Serialize 하여 UObject를 직렬화 한적이있다.
해당 ObjectRecord를 Json에 어떻게 기록되는지 확인해본 결과 TArray<uint8> 형식으로 기록되는것을 볼 수 있다.
하지만 이렇게 되면 ObjectData를 알아볼 수 없기 때문에 Json 파일로 추출한 의미가 없기 때문에, ObjectRecord구조체에 대한 예외처리를 진행하였다.
FString SSaveEditWindowMenu::ConvertSavToJson(USaveGame* SaveGame)
{
const TSharedPtr<FJsonObject> JsonObject = MakeShareable(new FJsonObject());
// SaveGame 클래스 명 저장
JsonObject->SetStringField(SaveGameClassField, SaveGame->GetClass()->GetPathName());
const UClass* SaveGameClass = SaveGame->GetClass();
for (TFieldIterator<FProperty> It(SaveGameClass); It; ++It)
{
FProperty* Property = *It;
void* ValuePtr = Property->ContainerPtrToValuePtr<void>(SaveGame);
TSharedPtr<FJsonValue> JsonValue = UPropertyRuleBPLibrary::GetJsonValue(Property, ValuePtr);
JsonObject->SetField(Property->GetName(), JsonValue);
}
FString OutputJson;
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutputJson);
FJsonSerializer::Serialize(JsonObject.ToSharedRef(), Writer);
return OutputJson;
}
TSharedPtr<FJsonValue> UPropertyRuleBPLibrary::GetJsonValue(FProperty* Property, void* ValuePtr)
{
if (FStructProperty* StructProp = CastField<FStructProperty>(Property); StructProp != nullptr)
{
return GetStructJsonValue(StructProp, ValuePtr);
}
if (const FArrayProperty* ArrayProp = CastField<FArrayProperty>(Property); ArrayProp != nullptr)
{
return GetJsonArrayValue(ArrayProp, ValuePtr);
}
return FJsonObjectConverter::UPropertyToJsonValue(Property, ValuePtr);
}
TSharedPtr<FJsonValue> UPropertyRuleBPLibrary::GetStructJsonValue(FStructProperty* StructProp, void* ValuePtr)
{
// 구조체 타입일 경우
const TSharedPtr<FJsonObject> StructJson = MakeShareable(new FJsonObject());
if (StructProp->Struct != FObjectRecord::StaticStruct())
{
return FJsonObjectConverter::UPropertyToJsonValue(StructProp, ValuePtr);
}
FObjectRecord* ObjectRecord = reinterpret_cast<FObjectRecord*>(ValuePtr);
if (ObjectRecord != nullptr)
{
// FObjectRecord를 수동으로 복사하여 TArray에 추가
FObjectRecord NewObjectRecord;
// ObjectRecord의 멤버 변수를 JSON에 추가
StructJson->SetStringField(ObjectClassKey, ObjectRecord->ObjectClass->GetName());
StructJson->SetStringField(ObjectNameKey, ObjectRecord->ObjectName);
UObject* CreatedObject = DeSerializeObjectRecord(*ObjectRecord);
// UObject → JSON 변환
TSharedPtr<FJsonObject> ObjectJson = MakeShareable(new FJsonObject());
if (FJsonObjectConverter::UStructToJsonObject(CreatedObject->GetClass(), CreatedObject, ObjectJson.ToSharedRef(), 0, 0))
{
StructJson->SetObjectField(ObjectDataKey, ObjectJson);
}
else
{
StructJson->SetStringField(ObjectDataKey, TEXT("Serialization Failed"));
}
}
return MakeShared<FJsonValueObject>(StructJson);
}
TSharedPtr<FJsonValueArray> UPropertyRuleBPLibrary::GetJsonArrayValue(const FArrayProperty* ArrayProp, const void* ValuePtr)
{
TArray<TSharedPtr<FJsonValue>> JsonArray; // 배열 값들을 담을 JSON 배열
// FScriptArrayHelper를 사용하여 TArray 데이터 접근
FScriptArrayHelper ArrayHelper(ArrayProp, ValuePtr);
const int32 NumElements = ArrayHelper.Num(); // 배열 크기
for (int32 Index = 0; Index < NumElements; ++Index)
{
void* ElementPtr = ArrayHelper.GetRawPtr(Index);
TSharedPtr<FJsonValue> JsonValue = GetJsonValue(ArrayProp->Inner, ElementPtr);
if (JsonValue.IsValid()) { JsonArray.Push(JsonValue); }
}
return MakeShared<FJsonValueArray>(JsonArray);
}
ObjectRecord 기반으로 해당 UObject를 생성한(DeSerializeObjectRecord) 후, ObjectData는 UStructToJsonObject 함수를 통해 JsonObject 형식으로 만들어 반환한다.
해당 Json을 되돌리는 코드는 다음과 같다.
USaveGame* SSaveEditWindowMenu::ConvertJsonToSav(const TSharedPtr<FJsonObject>& JsonObject)
{
// SaveGameClass 이름 가져오기
FString SaveGameClassName;
if (!JsonObject->TryGetStringField(SaveGameClassField, SaveGameClassName))
{
PROCESS_FAIL(JsonNoticeTextBlock, TEXT("JSON에 SaveGameClass 필드가 없습니다."));
return nullptr;
}
// SaveGameClass 검증
const UClass* SaveGameClass = FindObject<UClass>(ANY_PACKAGE, *SaveGameClassName);
if (!SaveGameClass || !SaveGameClass->IsChildOf(USaveGame::StaticClass()))
{
PROCESS_FAIL(JsonNoticeTextBlock, TEXT("유효한 SaveGameClass가 아닙니다: %s"), *SaveGameClassName);
return nullptr;
}
// SaveGame 객체 생성
USaveGame* SaveGame = NewObject<USaveGame>(GetTransientPackage(), SaveGameClass);
if (!SaveGame)
{
PROCESS_FAIL(JsonNoticeTextBlock, TEXT("SaveGame 객체 생성 실패"));
return nullptr;
}
// SaveGameClass 필드를 제외하고 나머지 필드 채우기
for (const TTuple<FString, TSharedPtr<FJsonValue>>& Entry : JsonObject->Values)
{
if (Entry.Key == SaveGameClassField) continue;
FProperty* Property = SaveGame->GetClass()->FindPropertyByName(FName(*Entry.Key));
if (!Property)
{
PROCESS_FAIL(JsonNoticeTextBlock, TEXT("SaveGameClass에 없는 필드: %s"), *Entry.Key);
continue;
}
void* ValuePtr = Property->ContainerPtrToValuePtr<void>(SaveGame);
if (!ValuePtr)
{
PROCESS_FAIL(JsonNoticeTextBlock, TEXT("값을 가져올 수 없음: %s"), *Entry.Key);
continue;
}
FText* FailReason = nullptr;
if (!UPropertyRuleBPLibrary::AddSaveField(Entry.Value, Property, ValuePtr, FailReason))
{
UE_LOG(LogTemp, Log, TEXT("%s: %s Failed"), *Entry.Key, *FailReason->ToString());
PROCESS_FAIL(JsonNoticeTextBlock, TEXT("%s: %s Failed"), *Entry.Key, *FailReason->ToString());
return nullptr;
}
}
return SaveGame;
}
bool UPropertyRuleBPLibrary::AddSaveField(const TSharedPtr<FJsonValue>& JsonValue, FProperty* Property, void* ValuePtr, FText* OutFailReason)
{
if (FStructProperty* StructProp = CastField<FStructProperty>(Property); StructProp != nullptr)
{
return AddSaveStructField(JsonValue, StructProp, ValuePtr, OutFailReason);
}
else if (FArrayProperty* ArrayProp = CastField<FArrayProperty>(Property); ArrayProp != nullptr)
{
return AddSaveArrayField(JsonValue, ArrayProp, ValuePtr, OutFailReason);
}
return FJsonObjectConverter::JsonValueToUProperty(JsonValue, Property, ValuePtr, 0, 0, false, OutFailReason);
}
bool UPropertyRuleBPLibrary::AddSaveStructField(const TSharedPtr<FJsonValue>& JsonValue, FStructProperty* StructProp, void* ValuePtr, FText* OutFailReason)
{
if(StructProp->Struct != FObjectRecord::StaticStruct())
{
return FJsonObjectConverter::JsonValueToUProperty(JsonValue, StructProp, ValuePtr, 0, 0, false, OutFailReason);
}
FObjectRecord* ObjectRecord = reinterpret_cast<FObjectRecord*>(ValuePtr);
if (ObjectRecord == nullptr)
{
UE_LOG(LogTemp, Warning, TEXT("AddSaveStructField: FObjectRecord is null"));
*OutFailReason = FText::FromString(TEXT("AddSaveStructField: FObjectRecord is null"));
return false;
}
// JSON에서 데이터를 읽어와 FObjectRecord에 적용
const TSharedPtr<FJsonObject> ObjectJson = JsonValue->AsObject();
FString ObjectClassName;
FString ObjectName;
if (ObjectJson->TryGetStringField(ObjectClassKey, ObjectClassName))
{
ObjectRecord->ObjectClass = FindObject<UClass>(ANY_PACKAGE, *ObjectClassName);
}
if (ObjectJson->TryGetStringField(ObjectNameKey, ObjectName))
{
ObjectRecord->ObjectName = ObjectName;
}
if (ObjectJson->HasField(ObjectDataKey))
{
const TSharedPtr<FJsonObject> ObjectDataJson = ObjectJson->GetObjectField(ObjectDataKey);
UObject* CreatedObject = DeSerializeObjectRecord(*ObjectRecord);
if (CreatedObject != nullptr &&
FJsonObjectConverter::JsonObjectToUStruct(ObjectDataJson.ToSharedRef(), CreatedObject->GetClass(), CreatedObject, 0, 0))
{
const FObjectRecord SerializedObjectRecord = SerializeObjectRecord(CreatedObject);
*ObjectRecord = SerializedObjectRecord;
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Failed to deserialize ObjectData for FObjectRecord"));
*OutFailReason = FText::FromString(TEXT("Failed to deserialize ObjectData for FObjectRecord"));
return false;
}
}
return true;
}
bool UPropertyRuleBPLibrary::AddSaveArrayField(const TSharedPtr<FJsonValue>& JsonValue, const FArrayProperty* ArrayProp, void* ValuePtr, FText* OutFailReason)
{
const TArray<TSharedPtr<FJsonValue>>& JsonArray = JsonValue->AsArray();
const int32 Length = JsonArray.Num();
FScriptArrayHelper ArrayHelper(ArrayProp, ValuePtr);
ArrayHelper.Resize(Length);
for (int32 Index = 0; Index < Length; ++Index)
{
const TSharedPtr<FJsonValue>& JsonElement = JsonArray[Index];
if (JsonElement.IsValid() && !JsonElement->IsNull())
{
void* ElementPtr = ArrayHelper.GetRawPtr(Index);
if (!AddSaveField(JsonElement, ArrayProp->Inner, ElementPtr, OutFailReason))
{
*OutFailReason = FText::FromString(TEXT("Failed to Add Array Element"));
return false;
}
}
}
return true;
}
'언리얼 > 언리얼 기능' 카테고리의 다른 글
쿼터니언 정규화 (0) | 2024.11.14 |
---|---|
채널 프리셋 (1) | 2024.09.23 |
FRotator (0) | 2024.09.11 |
언리얼 보간 (Interpolation) (1) | 2023.10.19 |
UStruct static_cast (0) | 2023.09.04 |