16 changed files with 1191 additions and 1 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,28 @@ |
|||||
|
{ |
||||
|
"FileVersion": 3, |
||||
|
"Version": 1, |
||||
|
"VersionName": "1.0", |
||||
|
"FriendlyName": "Struct2Json", |
||||
|
"Description": "Convert User Defined Struct to and from Json", |
||||
|
"Category": "Other", |
||||
|
"CreatedBy": "dan", |
||||
|
"CreatedByURL": "https://github.com/DAN-AND-DNA/", |
||||
|
"DocsURL": "https://docs.google.com/document/d/1Bqf_kjC484IPwsgcWBnXuXMCRY5AJPzvM0mCJBUGJxc/edit?usp=sharing", |
||||
|
"MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/ba62fdd8e61a4053ac7a3e6497f25d6c", |
||||
|
"SupportURL": "https://github.com/DAN-AND-DNA/UE_EasyStruct2Json", |
||||
|
"CanContainContent": true, |
||||
|
"Installed": true, |
||||
|
"Modules": [ |
||||
|
{ |
||||
|
"Name": "EasyStruct2Json", |
||||
|
"Type": "Runtime", |
||||
|
"LoadingPhase": "Default", |
||||
|
"PlatformAllowList": [ |
||||
|
"Win64", |
||||
|
"Mac", |
||||
|
"IOS", |
||||
|
"Android" |
||||
|
] |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
After Width: | Height: | Size: 18 KiB |
@ -0,0 +1,58 @@ |
|||||
|
// Copyright 2022 Danyang Chen https://github.com/DAN-AND-DNA
|
||||
|
|
||||
|
using UnrealBuildTool; |
||||
|
|
||||
|
public class EasyStruct2Json : ModuleRules |
||||
|
{ |
||||
|
public EasyStruct2Json(ReadOnlyTargetRules Target) : base(Target) |
||||
|
{ |
||||
|
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; |
||||
|
//OptimizeCode = CodeOptimization.Never;
|
||||
|
|
||||
|
PublicIncludePaths.AddRange( |
||||
|
new string[] { |
||||
|
// ... add public include paths required here ...
|
||||
|
} |
||||
|
); |
||||
|
|
||||
|
|
||||
|
PrivateIncludePaths.AddRange( |
||||
|
new string[] { |
||||
|
// ... add other private include paths required here ...
|
||||
|
} |
||||
|
); |
||||
|
|
||||
|
|
||||
|
PublicDependencyModuleNames.AddRange( |
||||
|
new string[] |
||||
|
{ |
||||
|
"Core", |
||||
|
// ... add other public dependencies that you statically link with here ...
|
||||
|
} |
||||
|
); |
||||
|
|
||||
|
|
||||
|
PrivateDependencyModuleNames.AddRange( |
||||
|
new string[] |
||||
|
{ |
||||
|
"CoreUObject", |
||||
|
"Engine", |
||||
|
"Slate", |
||||
|
"SlateCore", |
||||
|
"Json", |
||||
|
"JsonUtilities", |
||||
|
// ... add private dependencies that you statically link with here ...
|
||||
|
} |
||||
|
); |
||||
|
|
||||
|
|
||||
|
DynamicallyLoadedModuleNames.AddRange( |
||||
|
new string[] |
||||
|
{ |
||||
|
// ... add any modules that your module loads dynamically here ...
|
||||
|
} |
||||
|
); |
||||
|
|
||||
|
//OptimizeCode = CodeOptimization.Never;
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
// Copyright 2022 Danyang Chen https://github.com/DAN-AND-DNA
|
||||
|
|
||||
|
#include "EasyStruct2Json.h" |
||||
|
|
||||
|
#define LOCTEXT_NAMESPACE "FEasyStruct2JsonModule" |
||||
|
|
||||
|
void FEasyStruct2JsonModule::StartupModule() |
||||
|
{ |
||||
|
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
|
||||
|
} |
||||
|
|
||||
|
void FEasyStruct2JsonModule::ShutdownModule() |
||||
|
{ |
||||
|
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
|
||||
|
// we call this function before unloading the module.
|
||||
|
} |
||||
|
|
||||
|
#undef LOCTEXT_NAMESPACE |
||||
|
|
||||
|
IMPLEMENT_MODULE(FEasyStruct2JsonModule, EasyStruct2Json) |
||||
@ -0,0 +1,980 @@ |
|||||
|
// Copyright 2022 Danyang Chen https://github.com/DAN-AND-DNA
|
||||
|
|
||||
|
#include "Struct2JsonLibrary.h" |
||||
|
|
||||
|
#include "Engine/UserDefinedEnum.h" |
||||
|
#include "Runtime/JsonUtilities/Public/JsonObjectConverter.h" |
||||
|
#include "Runtime/Json/Public/Policies/PrettyJsonPrintPolicy.h" |
||||
|
#include "Runtime/CoreUObject/Public/UObject/TextProperty.h" |
||||
|
#include "Runtime/Json/Public/Policies/CondensedJsonPrintPolicy.h" |
||||
|
#include "Runtime/CoreUObject/Public/UObject/UObjectGlobals.h" |
||||
|
|
||||
|
namespace { |
||||
|
bool UStructToJsonAttributes(const UStruct* StructDefinition, const void* Struct, TMap< FString, TSharedPtr<FJsonValue> >& OutJsonAttributes, int64 CheckFlags = 0, int64 SkipFlags = 0, const FJsonObjectConverter::CustomExportCallback* ExportCb = nullptr, bool bOmitEmpty = false); |
||||
|
bool JsonAttributesToUStructWithContainer(const TMap< FString, TSharedPtr<FJsonValue> >& JsonAttributes, const UStruct* StructDefinition, void* OutStruct, const UStruct* ContainerStruct, void* Container, int64 CheckFlags, int64 SkipFlags); |
||||
|
bool JsonValueToFPropertyWithContainer(const TSharedPtr<FJsonValue>& JsonValue, FProperty* Property, void* OutValue, const UStruct* ContainerStruct, void* Container, int64 CheckFlags, int64 SkipFlags); |
||||
|
bool ConvertScalarJsonValueToFPropertyWithContainer(const TSharedPtr<FJsonValue>& JsonValue, FProperty* Property, void* OutValue, const UStruct* ContainerStruct, void* Container, int64 CheckFlags, int64 SkipFlags); |
||||
|
TSharedPtr<FJsonValue> UPropertyToJsonValue(FProperty* Property, const void* Value, int64 CheckFlags = 0, int64 SkipFlags = 0, const FJsonObjectConverter::CustomExportCallback* ExportCb = nullptr, FProperty* OuterProperty = nullptr, bool bOmitEmpty = false); |
||||
|
TSharedPtr<FJsonValue> ConvertScalarFPropertyToJsonValue(FProperty* Property, const void* Value, int64 CheckFlags, int64 SkipFlags, const FJsonObjectConverter::CustomExportCallback* ExportCb, FProperty* OuterProperty, bool bOmitEmpty); |
||||
|
bool UStructToJsonObject(const UStruct* StructDefinition, const void* Struct, TSharedRef<FJsonObject> OutJsonObject, int64 CheckFlags, int64 SkipFlags, const FJsonObjectConverter::CustomExportCallback* ExportCb, bool bOmitEmpty); |
||||
|
const FString ObjectClassNameKey = "_ClassName"; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
FString GetShortName(FProperty* Property) { |
||||
|
FString PropertyName = Property->GetName(); |
||||
|
|
||||
|
if (PropertyName.IsEmpty()) { |
||||
|
return PropertyName; |
||||
|
} |
||||
|
|
||||
|
FString Left; |
||||
|
FString Right; |
||||
|
FString Garbage; |
||||
|
FString Final; |
||||
|
|
||||
|
if (PropertyName.Reverse().Split("_", &Left, &Right)) { |
||||
|
if (Left.Len() == 32) { |
||||
|
Right.Split("_", &Garbage, &Final); |
||||
|
return Final.Reverse(); |
||||
|
} |
||||
|
else |
||||
|
return PropertyName; |
||||
|
} |
||||
|
|
||||
|
return PropertyName; |
||||
|
} |
||||
|
|
||||
|
bool UStruct2JsonLibaray::JsonStr2Struct(FStructProperty* StructProperty, void* StructPtr, const FString& InJsonStr) { |
||||
|
UScriptStruct* Struct = StructProperty->Struct; |
||||
|
|
||||
|
TSharedPtr<FJsonObject> JsonObject; |
||||
|
TSharedRef<TJsonReader<> > JsonReader = TJsonReaderFactory<>::Create(InJsonStr); |
||||
|
if (!FJsonSerializer::Deserialize(JsonReader, JsonObject) || !JsonObject.IsValid()) |
||||
|
{ |
||||
|
UE_LOG(LogJson, Warning, TEXT("JsonObjectStringToUStruct - Unable to parse json=[%s]"), *InJsonStr); |
||||
|
return false; |
||||
|
} |
||||
|
if (!::JsonAttributesToUStructWithContainer(JsonObject->Values, Struct, StructPtr, Struct, StructPtr, 1, 0)) |
||||
|
{ |
||||
|
UE_LOG(LogJson, Warning, TEXT("JsonObjectStringToUStruct - Unable to deserialize. json=[%s]"), *InJsonStr); |
||||
|
return false; |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
template<class CharType, class PrintPolicy> |
||||
|
bool UStructToJsonObjectStringInternal(const TSharedRef<FJsonObject>& JsonObject, FString& OutJsonString, int32 Indent) |
||||
|
{ |
||||
|
TSharedRef<TJsonWriter<CharType, PrintPolicy> > JsonWriter = TJsonWriterFactory<CharType, PrintPolicy>::Create(&OutJsonString, Indent); |
||||
|
bool bSuccess = FJsonSerializer::Serialize(JsonObject, JsonWriter); |
||||
|
JsonWriter->Close(); |
||||
|
return bSuccess; |
||||
|
} |
||||
|
|
||||
|
bool UStruct2JsonLibaray::Struct2JsonStr(FStructProperty* StructProperty, void* StructPtr, FString& OutJsonStr, bool bPretty, bool bOmitEmpty) { |
||||
|
UScriptStruct* Struct = StructProperty->Struct; |
||||
|
|
||||
|
TSharedRef<FJsonObject> JsonObject = MakeShared<FJsonObject>(); |
||||
|
// ��struct���json value
|
||||
|
if (::UStructToJsonAttributes(Struct, StructPtr, JsonObject->Values, 1, 0, nullptr, bOmitEmpty)) { |
||||
|
bool bSuccess = false; |
||||
|
if(bPretty) { |
||||
|
bSuccess = UStructToJsonObjectStringInternal<TCHAR, TPrettyJsonPrintPolicy<TCHAR> >(JsonObject, OutJsonStr, 0); |
||||
|
} else { |
||||
|
bSuccess = UStructToJsonObjectStringInternal<TCHAR, TCondensedJsonPrintPolicy<TCHAR> >(JsonObject, OutJsonStr, 0); |
||||
|
} |
||||
|
|
||||
|
if (bSuccess) { |
||||
|
return true; |
||||
|
} |
||||
|
else { |
||||
|
UE_LOG(LogJson, Warning, TEXT("UStructToJsonObjectString - Unable to write out json")); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
#if (ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION > 26 || ENGINE_MAJOR_VERSION > 4) |
||||
|
namespace { |
||||
|
bool JsonAttributesToUStructWithContainer(const TMap< FString, TSharedPtr<FJsonValue> >& JsonAttributes, const UStruct* StructDefinition, void* OutStruct, const UStruct* ContainerStruct, void* Container, int64 CheckFlags, int64 SkipFlags) |
||||
|
{ |
||||
|
if (StructDefinition == FJsonObjectWrapper::StaticStruct()) |
||||
|
{ |
||||
|
// Just copy it into the object
|
||||
|
FJsonObjectWrapper* ProxyObject = (FJsonObjectWrapper*)OutStruct; |
||||
|
ProxyObject->JsonObject = MakeShared<FJsonObject>(); |
||||
|
ProxyObject->JsonObject->Values = JsonAttributes; |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
int32 NumUnclaimedProperties = JsonAttributes.Num(); |
||||
|
if (NumUnclaimedProperties <= 0) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
// iterate over the struct properties
|
||||
|
for (TFieldIterator<FProperty> PropIt(StructDefinition); PropIt; ++PropIt) |
||||
|
{ |
||||
|
FProperty* Property = *PropIt; |
||||
|
|
||||
|
// Check to see if we should ignore this property
|
||||
|
if (CheckFlags != 0 && !Property->HasAnyPropertyFlags(CheckFlags)) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
if (Property->HasAnyPropertyFlags(SkipFlags)) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
// find a json value matching this property name
|
||||
|
|
||||
|
//const TSharedPtr<FJsonValue>* JsonValue = JsonAttributes.Find(Property->GetName());
|
||||
|
const TSharedPtr<FJsonValue>* JsonValue = JsonAttributes.Find(GetShortName(Property)); // TEXT(L"ID_6_A21DA3F94C77867B6CA8E290E62E467E") but we just need TEXT(L"ID")
|
||||
|
if (!JsonValue) |
||||
|
{ |
||||
|
// we allow values to not be found since this mirrors the typical UObject mantra that all the fields are optional when deserializing
|
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
if (JsonValue->IsValid() && !(*JsonValue)->IsNull()) |
||||
|
{ |
||||
|
void* Value = Property->ContainerPtrToValuePtr<uint8>(OutStruct); |
||||
|
if (!JsonValueToFPropertyWithContainer(*JsonValue, Property, Value, ContainerStruct, Container, CheckFlags, SkipFlags)) |
||||
|
{ |
||||
|
UE_LOG(LogJson, Error, TEXT("JsonObjectToUStruct - Unable to parse %s.%s from JSON"), *StructDefinition->GetName(), *Property->GetName()); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (--NumUnclaimedProperties <= 0) |
||||
|
{ |
||||
|
// If we found all properties that were in the JsonAttributes map, there is no reason to keep looking for more.
|
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
bool JsonValueToFPropertyWithContainer(const TSharedPtr<FJsonValue>& JsonValue, FProperty* Property, void* OutValue, const UStruct* ContainerStruct, void* Container, int64 CheckFlags, int64 SkipFlags) |
||||
|
{ |
||||
|
if (!JsonValue.IsValid()) |
||||
|
{ |
||||
|
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Invalid value JSON key")); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
bool bArrayOrSetProperty = Property->IsA<FArrayProperty>() || Property->IsA<FSetProperty>(); |
||||
|
bool bJsonArray = JsonValue->Type == EJson::Array; |
||||
|
|
||||
|
if (!bJsonArray) |
||||
|
{ |
||||
|
if (bArrayOrSetProperty) |
||||
|
{ |
||||
|
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Attempted to import TArray from non-array JSON key")); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (Property->ArrayDim != 1) |
||||
|
{ |
||||
|
UE_LOG(LogJson, Warning, TEXT("Ignoring excess properties when deserializing %s"), *Property->GetName()); |
||||
|
} |
||||
|
|
||||
|
return ConvertScalarJsonValueToFPropertyWithContainer(JsonValue, Property, OutValue, ContainerStruct, Container, CheckFlags, SkipFlags); |
||||
|
} |
||||
|
|
||||
|
// In practice, the ArrayDim == 1 check ought to be redundant, since nested arrays of FPropertys are not supported
|
||||
|
if (bArrayOrSetProperty && Property->ArrayDim == 1) |
||||
|
{ |
||||
|
// Read into TArray
|
||||
|
return ConvertScalarJsonValueToFPropertyWithContainer(JsonValue, Property, OutValue, ContainerStruct, Container, CheckFlags, SkipFlags); |
||||
|
} |
||||
|
|
||||
|
// We're deserializing a JSON array
|
||||
|
const auto& ArrayValue = JsonValue->AsArray(); |
||||
|
if (Property->ArrayDim < ArrayValue.Num()) |
||||
|
{ |
||||
|
UE_LOG(LogJson, Warning, TEXT("Ignoring excess properties when deserializing %s"), *Property->GetName()); |
||||
|
} |
||||
|
|
||||
|
// Read into native array
|
||||
|
int ItemsToRead = FMath::Clamp(ArrayValue.Num(), 0, Property->ArrayDim); |
||||
|
for (int Index = 0; Index != ItemsToRead; ++Index) |
||||
|
{ |
||||
|
if (!ConvertScalarJsonValueToFPropertyWithContainer(ArrayValue[Index], Property, (char*)OutValue + Index * Property->GetElementSize(), ContainerStruct, Container, CheckFlags, SkipFlags)) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
bool ConvertScalarJsonValueToFPropertyWithContainer(const TSharedPtr<FJsonValue>& JsonValue, FProperty* Property, void* OutValue, const UStruct* ContainerStruct, void* Container, int64 CheckFlags, int64 SkipFlags) |
||||
|
{ |
||||
|
|
||||
|
if (FEnumProperty* EnumProperty = CastField<FEnumProperty>(Property)) |
||||
|
{ |
||||
|
if (JsonValue->Type == EJson::String) |
||||
|
{ |
||||
|
// see if we were passed a string for the enum
|
||||
|
const UEnum* Enum = EnumProperty->GetEnum(); |
||||
|
check(Enum); |
||||
|
FString StrValue = JsonValue->AsString(); |
||||
|
int64 IntValue = Enum->GetValueByName(FName(*StrValue)); |
||||
|
if (IntValue == INDEX_NONE) |
||||
|
{ |
||||
|
UE_LOG(LogJson, Warning, TEXT("JsonValueToUProperty - Unable import enum %s from string value %s for property %s. Using default (first one)."), *Enum->CppType, *StrValue, *Property->GetNameCPP()); |
||||
|
EnumProperty->GetUnderlyingProperty()->SetIntPropertyValue(OutValue, (int64)0); |
||||
|
//return false;
|
||||
|
} |
||||
|
else { |
||||
|
EnumProperty->GetUnderlyingProperty()->SetIntPropertyValue(OutValue, IntValue); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// AsNumber will log an error for completely inappropriate types (then give us a default)
|
||||
|
EnumProperty->GetUnderlyingProperty()->SetIntPropertyValue(OutValue, (int64)JsonValue->AsNumber()); |
||||
|
} |
||||
|
} |
||||
|
else if (FNumericProperty* NumericProperty = CastField<FNumericProperty>(Property)) |
||||
|
{ |
||||
|
if (NumericProperty->IsEnum() && JsonValue->Type == EJson::String) |
||||
|
{ |
||||
|
// see if we were passed a string for the enum
|
||||
|
const UEnum* Enum = NumericProperty->GetIntPropertyEnum(); |
||||
|
check(Enum); // should be assured by IsEnum()
|
||||
|
FString StrValue = JsonValue->AsString(); |
||||
|
FName KeyStrValue = FName(*StrValue); |
||||
|
|
||||
|
UUserDefinedEnum* UDEnum = Cast<UUserDefinedEnum>(NumericProperty->GetIntPropertyEnum()); |
||||
|
if (UDEnum) { |
||||
|
for (const TTuple<FName, FText>& Tuple : UDEnum->DisplayNameMap) { |
||||
|
//if (Tuple.Value.EqualTo(FText::FromString(StrValue))) {
|
||||
|
if (Tuple.Value.ToUpper().EqualTo(FText::FromString(StrValue).ToUpper())){ |
||||
|
KeyStrValue = Tuple.Key; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
//int64 IntValue = Enum->GetValueByName(FName(*StrValue));
|
||||
|
int64 IntValue = Enum->GetValueByName(KeyStrValue); |
||||
|
if (IntValue == INDEX_NONE) |
||||
|
{ |
||||
|
|
||||
|
UE_LOG(LogJson, Warning, TEXT("JsonValueToUProperty - Unable import enum %s from string value %s for property %s. Using default (first one)."), *Enum->CppType, *StrValue, *Property->GetNameCPP()); |
||||
|
NumericProperty->SetIntPropertyValue(OutValue, (int64)0); |
||||
|
//return false;
|
||||
|
} |
||||
|
else { |
||||
|
NumericProperty->SetIntPropertyValue(OutValue, IntValue); |
||||
|
} |
||||
|
} |
||||
|
else if (NumericProperty->IsFloatingPoint()) |
||||
|
{ |
||||
|
// AsNumber will log an error for completely inappropriate types (then give us a default)
|
||||
|
NumericProperty->SetFloatingPointPropertyValue(OutValue, JsonValue->AsNumber()); |
||||
|
} |
||||
|
else if (NumericProperty->IsInteger()) |
||||
|
{ |
||||
|
if (JsonValue->Type == EJson::String) |
||||
|
{ |
||||
|
// parse string -> int64 ourselves so we don't lose any precision going through AsNumber (aka double)
|
||||
|
NumericProperty->SetIntPropertyValue(OutValue, FCString::Atoi64(*JsonValue->AsString())); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// AsNumber will log an error for completely inappropriate types (then give us a default)
|
||||
|
NumericProperty->SetIntPropertyValue(OutValue, (int64)JsonValue->AsNumber()); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to set numeric property type %s for property %s"), *Property->GetClass()->GetName(), *Property->GetNameCPP()); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
else if (FBoolProperty* BoolProperty = CastField<FBoolProperty>(Property)) |
||||
|
{ |
||||
|
// AsBool will log an error for completely inappropriate types (then give us a default)
|
||||
|
BoolProperty->SetPropertyValue(OutValue, JsonValue->AsBool()); |
||||
|
} |
||||
|
else if (FStrProperty* StringProperty = CastField<FStrProperty>(Property)) |
||||
|
{ |
||||
|
// AsString will log an error for completely inappropriate types (then give us a default)
|
||||
|
StringProperty->SetPropertyValue(OutValue, JsonValue->AsString()); |
||||
|
} |
||||
|
else if (FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Property)) |
||||
|
{ |
||||
|
if (JsonValue->Type == EJson::Array) |
||||
|
{ |
||||
|
TArray< TSharedPtr<FJsonValue> > ArrayValue = JsonValue->AsArray(); |
||||
|
int32 ArrLen = ArrayValue.Num(); |
||||
|
|
||||
|
// make the output array size match
|
||||
|
FScriptArrayHelper Helper(ArrayProperty, OutValue); |
||||
|
Helper.Resize(ArrLen); |
||||
|
|
||||
|
// set the property values
|
||||
|
for (int32 i = 0; i < ArrLen; ++i) |
||||
|
{ |
||||
|
const TSharedPtr<FJsonValue>& ArrayValueItem = ArrayValue[i]; |
||||
|
if (ArrayValueItem.IsValid() && !ArrayValueItem->IsNull()) |
||||
|
{ |
||||
|
if (!JsonValueToFPropertyWithContainer(ArrayValueItem, ArrayProperty->Inner, Helper.GetRawPtr(i), ContainerStruct, Container, CheckFlags & (~CPF_ParmFlags), SkipFlags)) |
||||
|
{ |
||||
|
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to deserialize array element [%d] for property %s"), i, *Property->GetNameCPP()); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Attempted to import TArray from non-array JSON key for property %s"), *Property->GetNameCPP()); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
else if (FMapProperty* MapProperty = CastField<FMapProperty>(Property)) |
||||
|
{ |
||||
|
if (JsonValue->Type == EJson::Object) |
||||
|
{ |
||||
|
TSharedPtr<FJsonObject> ObjectValue = JsonValue->AsObject(); |
||||
|
|
||||
|
FScriptMapHelper Helper(MapProperty, OutValue); |
||||
|
|
||||
|
check(ObjectValue); |
||||
|
|
||||
|
int32 MapSize = ObjectValue->Values.Num(); |
||||
|
Helper.EmptyValues(MapSize); |
||||
|
|
||||
|
// set the property values
|
||||
|
for (const auto& Entry : ObjectValue->Values) |
||||
|
{ |
||||
|
if (Entry.Value.IsValid() && !Entry.Value->IsNull()) |
||||
|
{ |
||||
|
int32 NewIndex = Helper.AddDefaultValue_Invalid_NeedsRehash(); |
||||
|
|
||||
|
TSharedPtr<FJsonValueString> TempKeyValue = MakeShared<FJsonValueString>(Entry.Key); |
||||
|
|
||||
|
const bool bKeySuccess = JsonValueToFPropertyWithContainer(TempKeyValue, MapProperty->KeyProp, Helper.GetKeyPtr(NewIndex), ContainerStruct, Container, CheckFlags & (~CPF_ParmFlags), SkipFlags); |
||||
|
const bool bValueSuccess = JsonValueToFPropertyWithContainer(Entry.Value, MapProperty->ValueProp, Helper.GetValuePtr(NewIndex), ContainerStruct, Container, CheckFlags & (~CPF_ParmFlags), SkipFlags); |
||||
|
|
||||
|
if (!(bKeySuccess && bValueSuccess)) |
||||
|
{ |
||||
|
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to deserialize map element [key: %s] for property %s"), *Entry.Key, *Property->GetNameCPP()); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Helper.Rehash(); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Attempted to import TMap from non-object JSON key for property %s"), *Property->GetNameCPP()); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
else if (FSetProperty* SetProperty = CastField<FSetProperty>(Property)) |
||||
|
{ |
||||
|
if (JsonValue->Type == EJson::Array) |
||||
|
{ |
||||
|
TArray< TSharedPtr<FJsonValue> > ArrayValue = JsonValue->AsArray(); |
||||
|
int32 ArrLen = ArrayValue.Num(); |
||||
|
|
||||
|
FScriptSetHelper Helper(SetProperty, OutValue); |
||||
|
|
||||
|
// set the property values
|
||||
|
for (int32 i = 0; i < ArrLen; ++i) |
||||
|
{ |
||||
|
const TSharedPtr<FJsonValue>& ArrayValueItem = ArrayValue[i]; |
||||
|
if (ArrayValueItem.IsValid() && !ArrayValueItem->IsNull()) |
||||
|
{ |
||||
|
int32 NewIndex = Helper.AddDefaultValue_Invalid_NeedsRehash(); |
||||
|
if (!JsonValueToFPropertyWithContainer(ArrayValueItem, SetProperty->ElementProp, Helper.GetElementPtr(NewIndex), ContainerStruct, Container, CheckFlags & (~CPF_ParmFlags), SkipFlags)) |
||||
|
{ |
||||
|
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to deserialize set element [%d] for property %s"), i, *Property->GetNameCPP()); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Helper.Rehash(); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Attempted to import TSet from non-array JSON key for property %s"), *Property->GetNameCPP()); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
else if (FTextProperty* TextProperty = CastField<FTextProperty>(Property)) |
||||
|
{ |
||||
|
if (JsonValue->Type == EJson::String) |
||||
|
{ |
||||
|
// assume this string is already localized, so import as invariant
|
||||
|
TextProperty->SetPropertyValue(OutValue, FText::FromString(JsonValue->AsString())); |
||||
|
} |
||||
|
else if (JsonValue->Type == EJson::Object) |
||||
|
{ |
||||
|
TSharedPtr<FJsonObject> Obj = JsonValue->AsObject(); |
||||
|
check(Obj.IsValid()); // should not fail if Type == EJson::Object
|
||||
|
|
||||
|
// import the subvalue as a culture invariant string
|
||||
|
FText Text; |
||||
|
if (!FJsonObjectConverter::GetTextFromObject(Obj.ToSharedRef(), Text)) |
||||
|
{ |
||||
|
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Attempted to import FText from JSON object with invalid keys for property %s"), *Property->GetNameCPP()); |
||||
|
return false; |
||||
|
} |
||||
|
TextProperty->SetPropertyValue(OutValue, Text); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Attempted to import FText from JSON that was neither string nor object for property %s"), *Property->GetNameCPP()); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
else if (FStructProperty* StructProperty = CastField<FStructProperty>(Property)) |
||||
|
{ |
||||
|
static const FName NAME_DateTime1(TEXT("DateTime")); |
||||
|
static const FName NAME_Color1(TEXT("Color")); |
||||
|
static const FName NAME_LinearColor1(TEXT("LinearColor")); |
||||
|
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)) |
||||
|
{ |
||||
|
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - FJsonObjectConverter::JsonObjectToUStruct failed for property %s"), *Property->GetNameCPP()); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
else if (JsonValue->Type == EJson::String && StructProperty->Struct->GetFName() == NAME_LinearColor1) |
||||
|
{ |
||||
|
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_Color1) |
||||
|
{ |
||||
|
FColor& ColorOut = *(FColor*)OutValue; |
||||
|
FString ColorString = JsonValue->AsString(); |
||||
|
|
||||
|
ColorOut = FColor::FromHex(ColorString); |
||||
|
} |
||||
|
else if (JsonValue->Type == EJson::String && StructProperty->Struct->GetFName() == NAME_DateTime1) |
||||
|
{ |
||||
|
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 FDateTime for property %s"), *Property->GetNameCPP()); |
||||
|
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 ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1 |
||||
|
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()); |
||||
|
return false; |
||||
|
} |
||||
|
#else |
||||
|
Property->ImportText(ImportTextPtr, OutValue, PPF_None, nullptr); |
||||
|
#endif |
||||
|
} |
||||
|
} |
||||
|
else if (JsonValue->Type == EJson::String) |
||||
|
{ |
||||
|
FString ImportTextString = JsonValue->AsString(); |
||||
|
const TCHAR* ImportTextPtr = *ImportTextString; |
||||
|
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1 |
||||
|
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()); |
||||
|
return false; |
||||
|
} |
||||
|
#else |
||||
|
Property->ImportText(ImportTextPtr, OutValue, PPF_None, nullptr); |
||||
|
#endif |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Attempted to import UStruct from non-object JSON key for property %s"), *Property->GetNameCPP()); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
else if (FObjectProperty* ObjectProperty = CastField<FObjectProperty>(Property)) |
||||
|
{ |
||||
|
if (JsonValue->Type == EJson::Object) |
||||
|
{ |
||||
|
UObject* Outer = (UObject*)(GetTransientPackage()); |
||||
|
if (ContainerStruct->IsChildOf(UObject::StaticClass())) |
||||
|
{ |
||||
|
Outer = (UObject*)Container; |
||||
|
} |
||||
|
|
||||
|
TSharedPtr<FJsonObject> Obj = JsonValue->AsObject(); |
||||
|
UClass* PropertyClass = ObjectProperty->PropertyClass; |
||||
|
|
||||
|
// If a specific subclass was stored in the Json, use that instead of the PropertyClass
|
||||
|
FString ClassString = Obj->GetStringField(ObjectClassNameKey); |
||||
|
Obj->RemoveField(ObjectClassNameKey); |
||||
|
if (!ClassString.IsEmpty()) |
||||
|
{ |
||||
|
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1 |
||||
|
UClass* FoundClass = FPackageName::IsShortPackageName(ClassString) ? FindFirstObject<UClass>(*ClassString) : UClass::TryFindTypeSlow<UClass>(ClassString); |
||||
|
if (FoundClass) |
||||
|
{ |
||||
|
PropertyClass = FoundClass; |
||||
|
} |
||||
|
#else |
||||
|
UClass* FoundClass = FindObject<UClass>((UObject*)(ANY_PACKAGE), *ClassString); |
||||
|
if (FoundClass) |
||||
|
{ |
||||
|
PropertyClass = FoundClass; |
||||
|
} |
||||
|
#endif |
||||
|
} |
||||
|
|
||||
|
UObject* createdObj = StaticAllocateObject(PropertyClass, Outer, NAME_None, EObjectFlags::RF_NoFlags, EInternalObjectFlags::None, false); |
||||
|
|
||||
|
#if ENGINE_MAJOR_VERSION > 4 |
||||
|
(*PropertyClass->ClassConstructor)(FObjectInitializer(createdObj, PropertyClass->GetDefaultObject(), EObjectInitializerOptions::None)); |
||||
|
#else |
||||
|
(*PropertyClass->ClassConstructor)(FObjectInitializer(createdObj, PropertyClass->GetDefaultObject(), false, false)); |
||||
|
#endif |
||||
|
|
||||
|
ObjectProperty->SetObjectPropertyValue(OutValue, createdObj); |
||||
|
|
||||
|
check(Obj.IsValid()); // should not fail if Type == EJson::Object
|
||||
|
if (!JsonAttributesToUStructWithContainer(Obj->Values, PropertyClass, createdObj, PropertyClass, createdObj, CheckFlags & (~CPF_ParmFlags), SkipFlags)) |
||||
|
{ |
||||
|
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - FJsonObjectConverter::JsonObjectToUStruct failed for property %s"), *Property->GetNameCPP()); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
else if (JsonValue->Type == EJson::String) |
||||
|
{ |
||||
|
// Default to expect a string for everything else
|
||||
|
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1 |
||||
|
if (Property->ImportText_Direct(*JsonValue->AsString(), OutValue, nullptr, 0) == nullptr) |
||||
|
{ |
||||
|
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import JSON string into %s property %s"), *ObjectProperty->PropertyClass->GetAuthoredName(), *Property->GetAuthoredName()); |
||||
|
return false; |
||||
|
} |
||||
|
#else |
||||
|
if (Property->ImportText(*JsonValue->AsString(), OutValue, 0, NULL) == NULL) |
||||
|
{ |
||||
|
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable import property type %s from string value for property %s"), *Property->GetClass()->GetName(), *Property->GetNameCPP()); |
||||
|
return false; |
||||
|
} |
||||
|
#endif |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1 |
||||
|
// Default to expect a string for everything else
|
||||
|
if (Property->ImportText_Direct(*JsonValue->AsString(), OutValue, nullptr, 0) == nullptr) |
||||
|
{ |
||||
|
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import JSON string into property %s"), *Property->GetAuthoredName()); |
||||
|
return false; |
||||
|
} |
||||
|
#else |
||||
|
if (Property->ImportText(*JsonValue->AsString(), OutValue, 0, NULL) == NULL) |
||||
|
{ |
||||
|
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable import property type %s from string value for property %s"), *Property->GetClass()->GetName(), *Property->GetNameCPP()); |
||||
|
return false; |
||||
|
} |
||||
|
#endif |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
bool UStructToJsonAttributes(const UStruct* StructDefinition, const void* Struct, TMap< FString, TSharedPtr<FJsonValue> >& OutJsonAttributes, int64 CheckFlags, int64 SkipFlags, const FJsonObjectConverter::CustomExportCallback* ExportCb, bool bOmitEmpty) |
||||
|
{ |
||||
|
if (SkipFlags == 0) |
||||
|
{ |
||||
|
// If we have no specified skip flags, skip deprecated, transient and skip serialization by default when writing
|
||||
|
SkipFlags |= CPF_Deprecated | CPF_Transient; |
||||
|
} |
||||
|
|
||||
|
if (StructDefinition == FJsonObjectWrapper::StaticStruct()) |
||||
|
{ |
||||
|
// Just copy it into the object
|
||||
|
const FJsonObjectWrapper* ProxyObject = (const FJsonObjectWrapper*)Struct; |
||||
|
|
||||
|
if (ProxyObject->JsonObject.IsValid()) |
||||
|
{ |
||||
|
OutJsonAttributes = ProxyObject->JsonObject->Values; |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
// ����struct��ÿ����Ա,���json value
|
||||
|
for (TFieldIterator<FProperty> It(StructDefinition); It; ++It) |
||||
|
{ |
||||
|
FProperty* Property = *It; |
||||
|
|
||||
|
// Check to see if we should ignore this property
|
||||
|
if (CheckFlags != 0 && !Property->HasAnyPropertyFlags(CheckFlags)) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
if (Property->HasAnyPropertyFlags(SkipFlags)) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
//FString VariableName = FJsonObjectConverter::StandardizeCase(GetShortName(Property));
|
||||
|
FString VariableName = GetShortName(Property); |
||||
|
const void* Value = Property->ContainerPtrToValuePtr<uint8>(Struct); |
||||
|
|
||||
|
// convert the property to a FJsonValue
|
||||
|
TSharedPtr<FJsonValue> JsonValue = UPropertyToJsonValue(Property, Value, CheckFlags, SkipFlags, ExportCb, nullptr, bOmitEmpty); |
||||
|
if (!JsonValue.IsValid()) |
||||
|
{ |
||||
|
FFieldClass* PropClass = Property->GetClass(); |
||||
|
UE_LOG(LogJson, Error, TEXT("UPropertyToJsonValue - Unhandled property type '%s': %s"), *PropClass->GetName(), *Property->GetPathName()); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
/*
|
||||
|
* None, |
||||
|
Null, |
||||
|
String, |
||||
|
Number, |
||||
|
Boolean, |
||||
|
Array, |
||||
|
Object |
||||
|
*/ |
||||
|
if (bOmitEmpty) { |
||||
|
switch (JsonValue->Type) { |
||||
|
case EJson::None: |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
case EJson::Null: |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
case EJson::String: |
||||
|
{ |
||||
|
if (JsonValue->AsString().IsEmpty()) { |
||||
|
continue; |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
case EJson::Number: |
||||
|
{ |
||||
|
if (JsonValue->AsNumber() == 0) { |
||||
|
continue; |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
case EJson::Array: |
||||
|
{ |
||||
|
if (JsonValue->AsArray().Num() == 0) { |
||||
|
continue; |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
case EJson::Boolean: |
||||
|
{ |
||||
|
if (JsonValue->AsBool() == false) { |
||||
|
continue; |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
case EJson::Object: |
||||
|
{ |
||||
|
if (JsonValue->AsObject().IsValid() == false || JsonValue->AsObject()->Values.Num() == 0) { |
||||
|
continue; |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// set the value on the output object
|
||||
|
OutJsonAttributes.Add(VariableName, JsonValue); |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
TSharedPtr<FJsonValue> UPropertyToJsonValue(FProperty* Property, const void* Value, int64 CheckFlags, int64 SkipFlags, const FJsonObjectConverter::CustomExportCallback* ExportCb, FProperty* OuterProperty, bool bOmitEmpty) |
||||
|
{ |
||||
|
if (Property->ArrayDim == 1) |
||||
|
{ |
||||
|
return ConvertScalarFPropertyToJsonValue(Property, Value, CheckFlags, SkipFlags, ExportCb, OuterProperty, bOmitEmpty); |
||||
|
} |
||||
|
|
||||
|
TArray< TSharedPtr<FJsonValue> > Array; |
||||
|
for (int Index = 0; Index != Property->ArrayDim; ++Index) |
||||
|
{ |
||||
|
Array.Add(ConvertScalarFPropertyToJsonValue(Property, (char*)Value + Index * Property->GetElementSize(), CheckFlags, SkipFlags, ExportCb, OuterProperty, bOmitEmpty)); |
||||
|
} |
||||
|
return MakeShared<FJsonValueArray>(Array); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
TSharedPtr<FJsonValue> ConvertScalarFPropertyToJsonValue(FProperty* Property, const void* Value, int64 CheckFlags, int64 SkipFlags, const FJsonObjectConverter::CustomExportCallback* ExportCb, FProperty* OuterProperty, bool bOmitEmpty) |
||||
|
{ |
||||
|
// See if there's a custom export callback first, so it can override default behavior
|
||||
|
if (ExportCb && ExportCb->IsBound()) |
||||
|
{ |
||||
|
TSharedPtr<FJsonValue> CustomValue = ExportCb->Execute(Property, Value); |
||||
|
if (CustomValue.IsValid()) |
||||
|
{ |
||||
|
return CustomValue; |
||||
|
} |
||||
|
// fall through to default cases
|
||||
|
} |
||||
|
|
||||
|
if (FEnumProperty* EnumProperty = CastField<FEnumProperty>(Property)) |
||||
|
{ |
||||
|
// export enums as strings
|
||||
|
UEnum* EnumDef = EnumProperty->GetEnum(); |
||||
|
FString StringValue = EnumDef->GetNameStringByValue(EnumProperty->GetUnderlyingProperty()->GetSignedIntPropertyValue(Value)); |
||||
|
return MakeShared<FJsonValueString>(StringValue); |
||||
|
} |
||||
|
else if (FNumericProperty* NumericProperty = CastField<FNumericProperty>(Property)) |
||||
|
{ |
||||
|
// see if it's an enum
|
||||
|
UEnum* EnumDef = NumericProperty->GetIntPropertyEnum(); |
||||
|
if (EnumDef != NULL) |
||||
|
{ |
||||
|
FText StringValue = EnumDef->GetDisplayNameTextByIndex(NumericProperty->GetSignedIntPropertyValue(Value)); |
||||
|
//EnumDef->GetDisplayNameTextByIndex
|
||||
|
// export enums as strings
|
||||
|
//FString StringValue = EnumDef->GetNameStringByValue(NumericProperty->GetSignedIntPropertyValue(Value));
|
||||
|
return MakeShared<FJsonValueString>(StringValue.ToString()); |
||||
|
} |
||||
|
|
||||
|
// We want to export numbers as numbers
|
||||
|
if (NumericProperty->IsFloatingPoint()) |
||||
|
{ |
||||
|
return MakeShared<FJsonValueNumber>(NumericProperty->GetFloatingPointPropertyValue(Value)); |
||||
|
} |
||||
|
else if (NumericProperty->IsInteger()) |
||||
|
{ |
||||
|
return MakeShared<FJsonValueNumber>(NumericProperty->GetSignedIntPropertyValue(Value)); |
||||
|
} |
||||
|
|
||||
|
// fall through to default
|
||||
|
} |
||||
|
else if (FBoolProperty* BoolProperty = CastField<FBoolProperty>(Property)) |
||||
|
{ |
||||
|
// Export bools as bools
|
||||
|
return MakeShared<FJsonValueBoolean>(BoolProperty->GetPropertyValue(Value)); |
||||
|
} |
||||
|
else if (FStrProperty* StringProperty = CastField<FStrProperty>(Property)) |
||||
|
{ |
||||
|
return MakeShared<FJsonValueString>(StringProperty->GetPropertyValue(Value)); |
||||
|
} |
||||
|
else if (FTextProperty* TextProperty = CastField<FTextProperty>(Property)) |
||||
|
{ |
||||
|
return MakeShared<FJsonValueString>(TextProperty->GetPropertyValue(Value).ToString()); |
||||
|
} |
||||
|
else if (FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Property)) |
||||
|
{ |
||||
|
TArray< TSharedPtr<FJsonValue> > Out; |
||||
|
FScriptArrayHelper Helper(ArrayProperty, Value); |
||||
|
for (int32 i = 0, n = Helper.Num(); i < n; ++i) |
||||
|
{ |
||||
|
TSharedPtr<FJsonValue> Elem = UPropertyToJsonValue(ArrayProperty->Inner, Helper.GetRawPtr(i), CheckFlags & (~CPF_ParmFlags), SkipFlags, ExportCb, ArrayProperty, bOmitEmpty); |
||||
|
if (Elem.IsValid()) |
||||
|
{ |
||||
|
// add to the array
|
||||
|
Out.Push(Elem); |
||||
|
} |
||||
|
} |
||||
|
return MakeShared<FJsonValueArray>(Out); |
||||
|
} |
||||
|
else if (FSetProperty* SetProperty = CastField<FSetProperty>(Property)) |
||||
|
{ |
||||
|
TArray< TSharedPtr<FJsonValue> > Out; |
||||
|
FScriptSetHelper Helper(SetProperty, Value); |
||||
|
for (int32 i = 0, n = Helper.Num(); n; ++i) |
||||
|
{ |
||||
|
if (Helper.IsValidIndex(i)) |
||||
|
{ |
||||
|
TSharedPtr<FJsonValue> Elem = UPropertyToJsonValue(SetProperty->ElementProp, Helper.GetElementPtr(i), CheckFlags & (~CPF_ParmFlags), SkipFlags, ExportCb, SetProperty, bOmitEmpty); |
||||
|
if (Elem.IsValid()) |
||||
|
{ |
||||
|
// add to the array
|
||||
|
Out.Push(Elem); |
||||
|
} |
||||
|
|
||||
|
--n; |
||||
|
} |
||||
|
} |
||||
|
return MakeShared<FJsonValueArray>(Out); |
||||
|
} |
||||
|
else if (FMapProperty* MapProperty = CastField<FMapProperty>(Property)) |
||||
|
{ |
||||
|
TSharedRef<FJsonObject> Out = MakeShared<FJsonObject>(); |
||||
|
|
||||
|
FScriptMapHelper Helper(MapProperty, Value); |
||||
|
for (int32 i = 0, n = Helper.Num(); n; ++i) |
||||
|
{ |
||||
|
if (Helper.IsValidIndex(i)) |
||||
|
{ |
||||
|
TSharedPtr<FJsonValue> KeyElement = UPropertyToJsonValue(MapProperty->KeyProp, Helper.GetKeyPtr(i), CheckFlags & (~CPF_ParmFlags), SkipFlags, ExportCb, MapProperty, bOmitEmpty); |
||||
|
TSharedPtr<FJsonValue> ValueElement = UPropertyToJsonValue(MapProperty->ValueProp, Helper.GetValuePtr(i), CheckFlags & (~CPF_ParmFlags), SkipFlags, ExportCb, MapProperty, bOmitEmpty); |
||||
|
if (KeyElement.IsValid() && ValueElement.IsValid()) |
||||
|
{ |
||||
|
FString KeyString; |
||||
|
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1 |
||||
|
if (!KeyElement->TryGetString(KeyString)) |
||||
|
{ |
||||
|
MapProperty->KeyProp->ExportTextItem_Direct(KeyString, Helper.GetKeyPtr(i), nullptr, nullptr, 0); |
||||
|
if (KeyString.IsEmpty()) |
||||
|
{ |
||||
|
UE_LOG(LogJson, Error, TEXT("Unable to convert key to string for property %s."), *MapProperty->GetAuthoredName()) |
||||
|
KeyString = FString::Printf(TEXT("Unparsed Key %d"), i); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Coerce camelCase map keys for Enum/FName properties
|
||||
|
if (CastField<FEnumProperty>(MapProperty->KeyProp) || |
||||
|
CastField<FNameProperty>(MapProperty->KeyProp)) |
||||
|
{ |
||||
|
KeyString = FJsonObjectConverter::StandardizeCase(KeyString); |
||||
|
} |
||||
|
#else |
||||
|
if (!KeyElement->TryGetString(KeyString)) |
||||
|
{ |
||||
|
MapProperty->KeyProp->ExportTextItem(KeyString, Helper.GetKeyPtr(i), nullptr, nullptr, 0); |
||||
|
if (KeyString.IsEmpty()) |
||||
|
{ |
||||
|
UE_LOG(LogJson, Error, TEXT("Unable to convert key to string for property %s."), *MapProperty->GetName()) |
||||
|
KeyString = FString::Printf(TEXT("Unparsed Key %d"), i); |
||||
|
} |
||||
|
} |
||||
|
#endif |
||||
|
Out->SetField(KeyString, ValueElement); |
||||
|
} |
||||
|
|
||||
|
--n; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return MakeShared<FJsonValueObject>(Out); |
||||
|
} |
||||
|
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 (UStructToJsonObject(StructProperty->Struct, Value, Out, CheckFlags & (~CPF_ParmFlags), SkipFlags, ExportCb, bOmitEmpty)) |
||||
|
{ |
||||
|
return MakeShared<FJsonValueObject>(Out); |
||||
|
} |
||||
|
} |
||||
|
else if (FObjectProperty* ObjectProperty = CastField<FObjectProperty>(Property)) |
||||
|
{ |
||||
|
// Instanced properties should be copied by value, while normal UObject* properties should output as asset references
|
||||
|
UObject* Object = ObjectProperty->GetObjectPropertyValue(Value); |
||||
|
if (Object && (ObjectProperty->HasAnyPropertyFlags(CPF_PersistentInstance) || (OuterProperty && OuterProperty->HasAnyPropertyFlags(CPF_PersistentInstance)))) |
||||
|
{ |
||||
|
TSharedRef<FJsonObject> Out = MakeShared<FJsonObject>(); |
||||
|
|
||||
|
Out->SetStringField(ObjectClassNameKey, Object->GetClass()->GetFName().ToString()); |
||||
|
if (UStructToJsonObject(ObjectProperty->GetObjectPropertyValue(Value)->GetClass(), Object, Out, CheckFlags, SkipFlags, ExportCb, bOmitEmpty)) |
||||
|
{ |
||||
|
TSharedRef<FJsonValueObject> JsonObject = MakeShared<FJsonValueObject>(Out); |
||||
|
JsonObject->Type = EJson::Object; |
||||
|
return JsonObject; |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
FString StringValue; |
||||
|
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1 |
||||
|
Property->ExportTextItem_Direct(StringValue, Value, nullptr, nullptr, PPF_None); |
||||
|
#else |
||||
|
Property->ExportTextItem(StringValue, Value, nullptr, nullptr, PPF_None); |
||||
|
#endif |
||||
|
return MakeShared<FJsonValueString>(StringValue); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// Default to export as string for everything else
|
||||
|
FString StringValue; |
||||
|
#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 1 |
||||
|
Property->ExportTextItem_Direct(StringValue, Value, NULL, NULL, PPF_None); |
||||
|
#else |
||||
|
Property->ExportTextItem(StringValue, Value, NULL, NULL, PPF_None); |
||||
|
#endif |
||||
|
return MakeShared<FJsonValueString>(StringValue); |
||||
|
} |
||||
|
|
||||
|
// invalid
|
||||
|
return TSharedPtr<FJsonValue>(); |
||||
|
} |
||||
|
|
||||
|
bool UStructToJsonObject(const UStruct* StructDefinition, const void* Struct, TSharedRef<FJsonObject> OutJsonObject, int64 CheckFlags, int64 SkipFlags, const FJsonObjectConverter::CustomExportCallback* ExportCb, bool bOmitEmpty) { |
||||
|
return UStructToJsonAttributes(StructDefinition, Struct, OutJsonObject->Values, CheckFlags, SkipFlags, ExportCb, bOmitEmpty); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
#endif |
||||
@ -0,0 +1,15 @@ |
|||||
|
// Copyright 2022 Danyang Chen https://github.com/DAN-AND-DNA
|
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "CoreMinimal.h" |
||||
|
#include "Modules/ModuleManager.h" |
||||
|
|
||||
|
class FEasyStruct2JsonModule : public IModuleInterface |
||||
|
{ |
||||
|
public: |
||||
|
|
||||
|
/** IModuleInterface implementation */ |
||||
|
virtual void StartupModule() override; |
||||
|
virtual void ShutdownModule() override; |
||||
|
}; |
||||
@ -0,0 +1,68 @@ |
|||||
|
// Copyright 2022 Danyang Chen https://github.com/DAN-AND-DNA
|
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "CoreMinimal.h" |
||||
|
#include "Kismet/BlueprintFunctionLibrary.h" |
||||
|
#include "Runtime/Launch/Resources/Version.h" |
||||
|
#include "Struct2JsonLibrary.generated.h" |
||||
|
|
||||
|
UCLASS() |
||||
|
class UWrapperStructProperty : public UObject { |
||||
|
GENERATED_BODY() |
||||
|
public: |
||||
|
FStructProperty* InterStruct; |
||||
|
}; |
||||
|
|
||||
|
UCLASS() |
||||
|
class EASYSTRUCT2JSON_API UStruct2JsonLibaray : public UBlueprintFunctionLibrary { |
||||
|
GENERATED_BODY() |
||||
|
public: |
||||
|
|
||||
|
UFUNCTION(BlueprintCallable, Category = "Struct2Json", CustomThunk, meta = (CustomStructureParam = "OutStruct")) |
||||
|
static UPARAM(DisplayName = "Ok") bool Json2Struct(const FString& JsonStr, const int32& OutStruct); |
||||
|
|
||||
|
DECLARE_FUNCTION(execJson2Struct) { |
||||
|
#if (ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION > 26 || ENGINE_MAJOR_VERSION > 4) |
||||
|
|
||||
|
P_GET_PROPERTY(FStrProperty, JsonStr); |
||||
|
|
||||
|
Stack.StepCompiledIn<FStructProperty>(NULL); |
||||
|
FStructProperty* StructProperty = ExactCastField<FStructProperty>(Stack.MostRecentProperty); |
||||
|
void* StructPropertyPtr = Stack.MostRecentPropertyAddress; |
||||
|
P_FINISH; |
||||
|
|
||||
|
*(bool*) RESULT_PARAM = JsonStr2Struct(StructProperty, StructPropertyPtr, JsonStr); |
||||
|
#else |
||||
|
*(bool*) RESULT_PARAM = false; |
||||
|
#endif |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
UFUNCTION(BlueprintCallable, Category = "Struct2Json", CustomThunk, meta = (CustomStructureParam = "InStruct")) |
||||
|
static UPARAM(DisplayName = "Ok") bool Struct2Json(const int32& InStruct, FString& JsonStr, bool bPretty = false, bool bOmitEmpty = true); |
||||
|
|
||||
|
DECLARE_FUNCTION(execStruct2Json) { |
||||
|
#if (ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION > 26 || ENGINE_MAJOR_VERSION > 4) |
||||
|
|
||||
|
Stack.StepCompiledIn<FStructProperty>(NULL); |
||||
|
FStructProperty* StructProperty = ExactCastField<FStructProperty>(Stack.MostRecentProperty); |
||||
|
void* StructPropertyPtr = Stack.MostRecentPropertyAddress; |
||||
|
|
||||
|
P_GET_PROPERTY_REF(FStrProperty, JsonStr); |
||||
|
P_GET_PROPERTY(FBoolProperty, bPretty); |
||||
|
P_GET_PROPERTY(FBoolProperty, bOmitEmpty); |
||||
|
P_FINISH; |
||||
|
|
||||
|
*(bool*)RESULT_PARAM = Struct2JsonStr(StructProperty, StructPropertyPtr, JsonStr, bPretty, bOmitEmpty); |
||||
|
#else |
||||
|
*(bool*)RESULT_PARAM = false; |
||||
|
#endif |
||||
|
} |
||||
|
|
||||
|
|
||||
|
static bool JsonStr2Struct(FStructProperty* StructProperty, void* StructPtr, const FString& InJsonStr); |
||||
|
static bool Struct2JsonStr(FStructProperty* StructProperty, void* StructPtr, FString& OutJsonStr, bool bPretty, bool bOmitEmpty); |
||||
|
|
||||
|
}; |
||||
Loading…
Reference in new issue