// 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 >& OutJsonAttributes, int64 CheckFlags = 0, int64 SkipFlags = 0, const FJsonObjectConverter::CustomExportCallback* ExportCb = nullptr, bool bOmitEmpty = false); bool JsonAttributesToUStructWithContainer(const TMap< FString, TSharedPtr >& JsonAttributes, const UStruct* StructDefinition, void* OutStruct, const UStruct* ContainerStruct, void* Container, int64 CheckFlags, int64 SkipFlags); bool JsonValueToFPropertyWithContainer(const TSharedPtr& JsonValue, FProperty* Property, void* OutValue, const UStruct* ContainerStruct, void* Container, int64 CheckFlags, int64 SkipFlags); bool ConvertScalarJsonValueToFPropertyWithContainer(const TSharedPtr& JsonValue, FProperty* Property, void* OutValue, const UStruct* ContainerStruct, void* Container, int64 CheckFlags, int64 SkipFlags); TSharedPtr UPropertyToJsonValue(FProperty* Property, const void* Value, int64 CheckFlags = 0, int64 SkipFlags = 0, const FJsonObjectConverter::CustomExportCallback* ExportCb = nullptr, FProperty* OuterProperty = nullptr, bool bOmitEmpty = false); TSharedPtr 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 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 JsonObject; TSharedRef > 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 bool UStructToJsonObjectStringInternal(const TSharedRef& JsonObject, FString& OutJsonString, int32 Indent) { TSharedRef > JsonWriter = TJsonWriterFactory::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 JsonObject = MakeShared(); // ��struct���json value if (::UStructToJsonAttributes(Struct, StructPtr, JsonObject->Values, 1, 0, nullptr, bOmitEmpty)) { bool bSuccess = false; if(bPretty) { bSuccess = UStructToJsonObjectStringInternal >(JsonObject, OutJsonStr, 0); } else { bSuccess = UStructToJsonObjectStringInternal >(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 >& 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(); ProxyObject->JsonObject->Values = JsonAttributes; return true; } int32 NumUnclaimedProperties = JsonAttributes.Num(); if (NumUnclaimedProperties <= 0) { return true; } // iterate over the struct properties for (TFieldIterator 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* JsonValue = JsonAttributes.Find(Property->GetName()); const TSharedPtr* 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(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& 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() || Property->IsA(); 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& JsonValue, FProperty* Property, void* OutValue, const UStruct* ContainerStruct, void* Container, int64 CheckFlags, int64 SkipFlags) { if (FEnumProperty* EnumProperty = CastField(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(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(NumericProperty->GetIntPropertyEnum()); if (UDEnum) { for (const TTuple& 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(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(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(Property)) { if (JsonValue->Type == EJson::Array) { TArray< TSharedPtr > 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& 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(Property)) { if (JsonValue->Type == EJson::Object) { TSharedPtr 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 TempKeyValue = MakeShared(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(Property)) { if (JsonValue->Type == EJson::Array) { TArray< TSharedPtr > ArrayValue = JsonValue->AsArray(); int32 ArrLen = ArrayValue.Num(); FScriptSetHelper Helper(SetProperty, OutValue); // set the property values for (int32 i = 0; i < ArrLen; ++i) { const TSharedPtr& 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(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 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(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 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(Property)) { if (JsonValue->Type == EJson::Object) { UObject* Outer = (UObject*)(GetTransientPackage()); if (ContainerStruct->IsChildOf(UObject::StaticClass())) { Outer = (UObject*)Container; } TSharedPtr 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(*ClassString) : UClass::TryFindTypeSlow(ClassString); if (FoundClass) { PropertyClass = FoundClass; } #else UClass* FoundClass = FindObject((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 >& 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 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(Struct); // convert the property to a FJsonValue TSharedPtr 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 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 > 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(Array); } TSharedPtr 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 CustomValue = ExportCb->Execute(Property, Value); if (CustomValue.IsValid()) { return CustomValue; } // fall through to default cases } if (FEnumProperty* EnumProperty = CastField(Property)) { // export enums as strings UEnum* EnumDef = EnumProperty->GetEnum(); FString StringValue = EnumDef->GetNameStringByValue(EnumProperty->GetUnderlyingProperty()->GetSignedIntPropertyValue(Value)); return MakeShared(StringValue); } else if (FNumericProperty* NumericProperty = CastField(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(StringValue.ToString()); } // We want to export numbers as numbers if (NumericProperty->IsFloatingPoint()) { return MakeShared(NumericProperty->GetFloatingPointPropertyValue(Value)); } else if (NumericProperty->IsInteger()) { return MakeShared(NumericProperty->GetSignedIntPropertyValue(Value)); } // fall through to default } else if (FBoolProperty* BoolProperty = CastField(Property)) { // Export bools as bools return MakeShared(BoolProperty->GetPropertyValue(Value)); } else if (FStrProperty* StringProperty = CastField(Property)) { return MakeShared(StringProperty->GetPropertyValue(Value)); } else if (FTextProperty* TextProperty = CastField(Property)) { return MakeShared(TextProperty->GetPropertyValue(Value).ToString()); } else if (FArrayProperty* ArrayProperty = CastField(Property)) { TArray< TSharedPtr > Out; FScriptArrayHelper Helper(ArrayProperty, Value); for (int32 i = 0, n = Helper.Num(); i < n; ++i) { TSharedPtr 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(Out); } else if (FSetProperty* SetProperty = CastField(Property)) { TArray< TSharedPtr > Out; FScriptSetHelper Helper(SetProperty, Value); for (int32 i = 0, n = Helper.Num(); n; ++i) { if (Helper.IsValidIndex(i)) { TSharedPtr 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(Out); } else if (FMapProperty* MapProperty = CastField(Property)) { TSharedRef Out = MakeShared(); FScriptMapHelper Helper(MapProperty, Value); for (int32 i = 0, n = Helper.Num(); n; ++i) { if (Helper.IsValidIndex(i)) { TSharedPtr KeyElement = UPropertyToJsonValue(MapProperty->KeyProp, Helper.GetKeyPtr(i), CheckFlags & (~CPF_ParmFlags), SkipFlags, ExportCb, MapProperty, bOmitEmpty); TSharedPtr 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(MapProperty->KeyProp) || CastField(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(Out); } else if (FStructProperty* StructProperty = CastField(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(OutValueStr); } TSharedRef Out = MakeShared(); if (UStructToJsonObject(StructProperty->Struct, Value, Out, CheckFlags & (~CPF_ParmFlags), SkipFlags, ExportCb, bOmitEmpty)) { return MakeShared(Out); } } else if (FObjectProperty* ObjectProperty = CastField(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 Out = MakeShared(); Out->SetStringField(ObjectClassNameKey, Object->GetClass()->GetFName().ToString()); if (UStructToJsonObject(ObjectProperty->GetObjectPropertyValue(Value)->GetClass(), Object, Out, CheckFlags, SkipFlags, ExportCb, bOmitEmpty)) { TSharedRef JsonObject = MakeShared(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(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(StringValue); } // invalid return TSharedPtr(); } bool UStructToJsonObject(const UStruct* StructDefinition, const void* Struct, TSharedRef OutJsonObject, int64 CheckFlags, int64 SkipFlags, const FJsonObjectConverter::CustomExportCallback* ExportCb, bool bOmitEmpty) { return UStructToJsonAttributes(StructDefinition, Struct, OutJsonObject->Values, CheckFlags, SkipFlags, ExportCb, bOmitEmpty); } } #endif