UE4笔记-UStructToJsonObjectString首字母自动转换为小写的问题及解决方法
UE4:
JsonUtilities模块的FJsonObjectConverter::UStructToJsonObjectString 会自动将json key的首字母转换为小写
例如我有个
USTRUCT(BlueprintType) struct FDTOData_Test { GENERATED_USTRUCT_BODY() public: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "FDTO_AssetItem") FString TiTle; };
UStructToJsonObjectString 后会变成:
FDTOData_Test data; data.Title = TEXT("sssss"); FJsonObjectConverter::UStructToJsonObjectString(data , ret_json, 0, 0);
/*
{
"tiTle" : "sssss"
}
*/
原因是JsonObjectConverter.cpp中的StandardizeCase这个智障函数..将所有的属性名的首字母都修改成了lowerCase.
具体代码在JsonObjectConverter.cpp 261行
如下:
FString VariableName = StandardizeCase(Property->GetName());
解决方法:
知道原因就很好去处理问题了...
UE4 源码版:
修改JsonObjectConverter.cpp的261行(代码位于Runtime\JsonUtilities\Private\JsonObjectConverter.cpp)
从
FString VariableName = StandardizeCase(Property->GetName());
修改至:
FString VariableName =Property->GetName();
UE4 二进制安装版:
build.cs添加一下模块
"Core" , "CoreUObject" , "Json" , "JsonUtilities"
并使用以下修改扩展类进行UStructToJsonObjectString:
FJsonObjectConverter扩展类FJsonObjectConverterEx代码:
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "UObject/Class.h" #include "Serialization/JsonTypes.h" #include "Dom/JsonObject.h" #include "Serialization/JsonReader.h" #include "Serialization/JsonSerializer.h" #include "JsonObjectWrapper.h" /** Class that handles converting Json objects to and from UStructs */ class FJsonObjectConverterEX { public: /** FName case insensitivity can make the casing of UPROPERTIES unpredictable. Attempt to standardize output. */ static FString StandardizeCase(const FString &StringIn); /** Parse an FText from a json object (assumed to be of the form where keys are culture codes and values are strings) */ static bool GetTextFromObject(const TSharedRef<FJsonObject>& Obj, FText& TextOut); public: // UStruct -> JSON /** * Optional callback to run when exporting a type which we don't already understand. * If this returns a valid pointer it will be inserted into the export chain. If not, or if this is not * passed in, then we will call the generic ToString on the type and export as a JSON string. */ DECLARE_DELEGATE_RetVal_TwoParams(TSharedPtr<FJsonValue>, CustomExportCallback, UProperty* /* Property */, const void* /* Value */); /** * Utility Export Callback for having object properties expanded to full Json. */ static TSharedPtr<FJsonValue> ObjectJsonCallback(UProperty* Property , const void* Value); /** * Templated version of UStructToJsonObject to try and make most of the params. Also serves as an example use case * * @param InStruct The UStruct instance to read from * @param ExportCb Optional callback for types we don't understand. This is called right before falling back to the generic ToString() * @param CheckFlags Only convert properties that match at least one of these flags. If 0 check all properties. * @param SkipFlags Skip properties that match any of these flags * @return FJsonObject pointer. Invalid if an error occurred. */ template<typename InStructType> static TSharedPtr<FJsonObject> UStructToJsonObject(const InStructType& InStruct, int64 CheckFlags = 0, int64 SkipFlags = 0, const CustomExportCallback* ExportCb = nullptr) { TSharedRef<FJsonObject> JsonObject = MakeShareable(new FJsonObject()); if (UStructToJsonObject(InStructType::StaticStruct(), &InStruct, JsonObject, CheckFlags, SkipFlags, ExportCb)) { return JsonObject; } return TSharedPtr<FJsonObject>(); // something went wrong } /** * Converts from a UStruct to a Json Object, using exportText * * @param StructDefinition UStruct definition that is looked over for properties * @param Struct The UStruct instance to copy out of * @param JsonObject Json Object to be filled in with data from the ustruct * @param CheckFlags Only convert properties that match at least one of these flags. If 0 check all properties. * @param SkipFlags Skip properties that match any of these flags * @param ExportCb Optional callback for types we don't understand. This is called right before falling back to the generic ToString() * * @return False if any properties failed to write */ static bool UStructToJsonObject(const UStruct* StructDefinition, const void* Struct, TSharedRef<FJsonObject> OutJsonObject, int64 CheckFlags, int64 SkipFlags, const CustomExportCallback* ExportCb = nullptr); /** * Converts from a UStruct to a json string containing an object, using exportText * * @param StructDefinition UStruct definition that is looked over for properties * @param Struct The UStruct instance to copy out of * @param JsonObject Json Object to be filled in with data from the ustruct * @param CheckFlags Only convert properties that match at least one of these flags. If 0 check all properties. * @param SkipFlags Skip properties that match any of these flags * @param Indent How many tabs to add to the json serializer * @param ExportCb Optional callback for types we don't understand. This is called right before falling back to the generic ToString() * @param bPrettyPrint Option to use pretty print (e.g., adds line endings) or condensed print * * @return False if any properties failed to write */ static bool UStructToJsonObjectString(const UStruct* StructDefinition, const void* Struct, FString& OutJsonString, int64 CheckFlags, int64 SkipFlags, int32 Indent = 0, const CustomExportCallback* ExportCb = nullptr, bool bPrettyPrint = true); /** * Templated version; Converts from a UStruct to a json string containing an object, using exportText * * @param Struct The UStruct instance to copy out of * @param JsonObject Json Object to be filled in with data from the ustruct * @param CheckFlags Only convert properties that match at least one of these flags. If 0 check all properties. * @param SkipFlags Skip properties that match any of these flags * @param Indent How many tabs to add to the json serializer * @param ExportCb Optional callback for types we don't understand. This is called right before falling back to the generic ToString() * @param bPrettyPrint Option to use pretty print (e.g., adds line endings) or condensed print * * @return False if any properties failed to write */ template<typename InStructType> static bool UStructToJsonObjectString(const InStructType& InStruct, FString& OutJsonString, int64 CheckFlags = 0, int64 SkipFlags = 0, int32 Indent = 0, const CustomExportCallback* ExportCb = nullptr, bool bPrettyPrint = true) { return UStructToJsonObjectString(InStructType::StaticStruct(), &InStruct, OutJsonString, CheckFlags, SkipFlags, Indent, ExportCb, bPrettyPrint); } /** * Wrapper to UStructToJsonObjectString that allows a print policy to be specified. */ template<typename CharType, template<typename> class PrintPolicy> static bool UStructToFormattedJsonObjectString(const UStruct* StructDefinition, const void* Struct, FString& OutJsonString, int64 CheckFlags, int64 SkipFlags, int32 Indent = 0, const CustomExportCallback* ExportCb = nullptr) { TSharedRef<FJsonObject> JsonObject = MakeShareable(new FJsonObject()); if (UStructToJsonObject(StructDefinition, Struct, JsonObject, CheckFlags, SkipFlags, ExportCb)) { TSharedRef<TJsonWriter<CharType, PrintPolicy<CharType>>> JsonWriter = TJsonWriterFactory<CharType, PrintPolicy<CharType>>::Create(&OutJsonString, Indent); if (FJsonSerializer::Serialize(JsonObject, JsonWriter)) { JsonWriter->Close(); return true; } else { UE_LOG(LogJson, Warning, TEXT("UStructToFormattedObjectString - Unable to write out json")); JsonWriter->Close(); } } return false; } /** * Converts from a UStruct to a set of json attributes (possibly from within a JsonObject) * * @param StructDefinition UStruct definition that is looked over for properties * @param Struct The UStruct instance to copy out of * @param JsonAttributes Map of attributes to copy in to * @param CheckFlags Only convert properties that match at least one of these flags. If 0 check all properties. * @param SkipFlags Skip properties that match any of these flags * @param ExportCb Optional callback for types we don't understand. This is called right before falling back to the generic ToString() * * @return False if any properties failed to write */ static bool UStructToJsonAttributes(const UStruct* StructDefinition, const void* Struct, TMap< FString, TSharedPtr<FJsonValue> >& OutJsonAttributes, int64 CheckFlags, int64 SkipFlags, const CustomExportCallback* ExportCb = nullptr); /* * Converts from a UProperty to a Json Value using exportText * * @param Property The property to export * @param Value Pointer to the value of the property * @param CheckFlags Only convert properties that match at least one of these flags. If 0 check all properties. * @param SkipFlags Skip properties that match any of these flags * @param ExportCb Optional callback for types we don't understand. This is called right before falling back to the generic ToString() * * @return The constructed JsonValue from the property */ static TSharedPtr<FJsonValue> UPropertyToJsonValue(UProperty* Property, const void* Value, int64 CheckFlags, int64 SkipFlags, const CustomExportCallback* ExportCb = nullptr); public: // JSON -> UStruct /** * Converts from a Json Object to a UStruct, using importText * * @param JsonObject Json Object to copy data out of * @param StructDefinition UStruct definition that is looked over for properties * @param Struct The UStruct instance to copy in to * @param CheckFlags Only convert properties that match at least one of these flags. If 0 check all properties. * @param SkipFlags Skip properties that match any of these flags * * @return False if any properties matched but failed to deserialize */ static bool JsonObjectToUStruct(const TSharedRef<FJsonObject>& JsonObject, const UStruct* StructDefinition, void* OutStruct, int64 CheckFlags = 0, int64 SkipFlags = 0); /** * Templated version of JsonObjectToUStruct * * @param JsonObject Json Object to copy data out of * @param OutStruct The UStruct instance to copy in to * @param CheckFlags Only convert properties that match at least one of these flags. If 0 check all properties. * @param SkipFlags Skip properties that match any of these flags * * @return False if any properties matched but failed to deserialize */ template<typename OutStructType> static bool JsonObjectToUStruct(const TSharedRef<FJsonObject>& JsonObject, OutStructType* OutStruct, int64 CheckFlags = 0, int64 SkipFlags = 0) { return JsonObjectToUStruct(JsonObject, OutStructType::StaticStruct(), OutStruct, CheckFlags, SkipFlags); } /** * Converts a set of json attributes (possibly from within a JsonObject) to a UStruct, using importText * * @param JsonAttributes Json Object to copy data out of * @param StructDefinition UStruct definition that is looked over for properties * @param OutStruct The UStruct instance to copy in to * @param CheckFlags Only convert properties that match at least one of these flags. If 0 check all properties. * @param SkipFlags Skip properties that match any of these flags * * @return False if any properties matched but failed to deserialize */ static bool JsonAttributesToUStruct(const TMap< FString, TSharedPtr<FJsonValue> >& JsonAttributes, const UStruct* StructDefinition, void* OutStruct, int64 CheckFlags, int64 SkipFlags); /** * Converts a single JsonValue to the corresponding UProperty (this may recurse if the property is a UStruct for instance). * * @param JsonValue The value to assign to this property * @param Property The UProperty definition of the property we're setting. * @param OutValue Pointer to the property instance to be modified. * @param CheckFlags Only convert sub-properties that match at least one of these flags. If 0 check all properties. * @param SkipFlags Skip sub-properties that match any of these flags * * @return False if the property failed to serialize */ static bool JsonValueToUProperty(const TSharedPtr<FJsonValue>& JsonValue, UProperty* Property, void* OutValue, int64 CheckFlags, int64 SkipFlags); /** * Converts from a json string containing an object to a UStruct * * @param JsonString String containing JSON formatted data. * @param OutStruct The UStruct instance to copy in to * @param CheckFlags Only convert properties that match at least one of these flags. If 0 check all properties. * @param SkipFlags Skip properties that match any of these flags * * @return False if any properties matched but failed to deserialize */ template<typename OutStructType> static bool JsonObjectStringToUStruct(const FString& JsonString, OutStructType* OutStruct, int64 CheckFlags, int64 SkipFlags) { TSharedPtr<FJsonObject> JsonObject; TSharedRef<TJsonReader<> > JsonReader = TJsonReaderFactory<>::Create(JsonString); if (!FJsonSerializer::Deserialize(JsonReader, JsonObject) || !JsonObject.IsValid()) { UE_LOG(LogJson, Warning, TEXT("JsonObjectStringToUStruct - Unable to parse json=[%s]"), *JsonString); return false; } if (!FJsonObjectConverterEX::JsonObjectToUStruct(JsonObject.ToSharedRef(), OutStruct, CheckFlags, SkipFlags)) { UE_LOG(LogJson, Warning, TEXT("JsonObjectStringToUStruct - Unable to deserialize. json=[%s]"), *JsonString); return false; } return true; } /** * Converts from a json string containing an array to an array of UStructs * * @param JsonString String containing JSON formatted data. * @param OutStructArray The UStruct array to copy in to * @param CheckFlags Only convert properties that match at least one of these flags. If 0 check all properties. * @param SkipFlags Skip properties that match any of these flags. * * @return False if any properties matched but failed to deserialize. */ template<typename OutStructType> static bool JsonArrayStringToUStruct(const FString& JsonString, TArray<OutStructType>* OutStructArray, int64 CheckFlags, int64 SkipFlags) { TArray<TSharedPtr<FJsonValue> > JsonArray; TSharedRef<TJsonReader<> > JsonReader = TJsonReaderFactory<>::Create(JsonString); if (!FJsonSerializer::Deserialize(JsonReader, JsonArray)) { UE_LOG(LogJson, Warning, TEXT("JsonArrayStringToUStruct - Unable to parse. json=[%s]"), *JsonString); return false; } if (!JsonArrayToUStruct(JsonArray, OutStructArray, CheckFlags, SkipFlags)) { UE_LOG(LogJson, Warning, TEXT("JsonArrayStringToUStruct - Error parsing one of the elements. json=[%s]"), *JsonString); return false; } return true; } /** * Converts from an array of json values to an array of UStructs. * * @param JsonArray Array containing json values to convert. * @param OutStructArray The UStruct array to copy in to * @param CheckFlags Only convert properties that match at least one of these flags. If 0 check all properties. * @param SkipFlags Skip properties that match any of these flags. * * @return False if any of the matching elements are not an object, or if one of the matching elements could not be converted to the specified UStruct type. */ template<typename OutStructType> static bool JsonArrayToUStruct(const TArray<TSharedPtr<FJsonValue>>& JsonArray, TArray<OutStructType>* OutStructArray, int64 CheckFlags, int64 SkipFlags) { OutStructArray->SetNum(JsonArray.Num()); for (int32 i = 0; i < JsonArray.Num(); ++i) { const auto& Value = JsonArray[i]; if (Value->Type != EJson::Object) { UE_LOG(LogJson, Warning, TEXT("JsonArrayToUStruct - Array element [%i] was not an object."), i); return false; } if (!FJsonObjectConverterEX::JsonObjectToUStruct(Value->AsObject().ToSharedRef(), OutStructType::StaticStruct(), &(*OutStructArray)[i], CheckFlags, SkipFlags)) { UE_LOG(LogJson, Warning, TEXT("JsonArrayToUStruct - Unable to convert element [%i]."), i); return false; } } return true; } /* * Parses text arguments from Json into a map * @param JsonObject Object to parse arguments from */ static FFormatNamedArguments ParseTextArgumentsFromJson(const TSharedPtr<const FJsonObject>& JsonObject); };
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "JsonObjectConverterEx.h" #include "Internationalization/Culture.h" #include "UObject/ObjectMacros.h" #include "UObject/Class.h" #include "UObject/UnrealType.h" #include "UObject/EnumProperty.h" #include "UObject/TextProperty.h" #include "UObject/PropertyPortFlags.h" #include "UObject/Package.h" #include "Policies/CondensedJsonPrintPolicy.h" #include "JsonObjectWrapper.h" FString FJsonObjectConverterEX::StandardizeCase(const FString &StringIn) { // this probably won't work for all cases, consider downcaseing the string fully FString FixedString = StringIn; FixedString[0] = FChar::ToLower(FixedString[0]); // our json classes/variable start lower case FixedString.ReplaceInline(TEXT("ID"), TEXT("Id"), ESearchCase::CaseSensitive); // Id is standard instead of ID, some of our fnames use ID return FixedString; } namespace { /** Convert property to JSON, assuming either the property is not an array or the value is an individual array element */ TSharedPtr<FJsonValue> ConvertScalarUPropertyToJsonValue(UProperty* Property, const void* Value, int64 CheckFlags, int64 SkipFlags, const FJsonObjectConverterEX::CustomExportCallback* ExportCb) { // 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 (UEnumProperty* EnumProperty = Cast<UEnumProperty>(Property)) { // export enums as strings UEnum* EnumDef = EnumProperty->GetEnum(); FString StringValue = EnumDef->GetNameStringByValue(EnumProperty->GetUnderlyingProperty()->GetSignedIntPropertyValue(Value)); return MakeShared<FJsonValueString>(StringValue); } else if (UNumericProperty *NumericProperty = Cast<UNumericProperty>(Property)) { // see if it's an enum UEnum* EnumDef = NumericProperty->GetIntPropertyEnum(); if (EnumDef != NULL) { // export enums as strings FString StringValue = EnumDef->GetNameStringByValue(NumericProperty->GetSignedIntPropertyValue(Value)); return MakeShared<FJsonValueString>(StringValue); } // 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 (UBoolProperty *BoolProperty = Cast<UBoolProperty>(Property)) { // Export bools as bools return MakeShared<FJsonValueBoolean>(BoolProperty->GetPropertyValue(Value)); } else if (UStrProperty *StringProperty = Cast<UStrProperty>(Property)) { return MakeShared<FJsonValueString>(StringProperty->GetPropertyValue(Value)); } else if (UTextProperty *TextProperty = Cast<UTextProperty>(Property)) { return MakeShared<FJsonValueString>(TextProperty->GetPropertyValue(Value).ToString()); } else if (UArrayProperty *ArrayProperty = Cast<UArrayProperty>(Property)) { TArray< TSharedPtr<FJsonValue> > Out; FScriptArrayHelper Helper(ArrayProperty, Value); for (int32 i=0, n=Helper.Num(); i<n; ++i) { TSharedPtr<FJsonValue> Elem = FJsonObjectConverterEX::UPropertyToJsonValue(ArrayProperty->Inner, Helper.GetRawPtr(i), CheckFlags & ( ~CPF_ParmFlags ), SkipFlags, ExportCb); if ( Elem.IsValid() ) { // add to the array Out.Push(Elem); } } return MakeShared<FJsonValueArray>(Out); } else if ( USetProperty* SetProperty = Cast<USetProperty>(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 = FJsonObjectConverterEX::UPropertyToJsonValue(SetProperty->ElementProp, Helper.GetElementPtr(i), CheckFlags & ( ~CPF_ParmFlags ), SkipFlags, ExportCb); if ( Elem.IsValid() ) { // add to the array Out.Push(Elem); } --n; } } return MakeShared<FJsonValueArray>(Out); } else if ( UMapProperty* MapProperty = Cast<UMapProperty>(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 = FJsonObjectConverterEX::UPropertyToJsonValue(MapProperty->KeyProp, Helper.GetKeyPtr(i), CheckFlags & ( ~CPF_ParmFlags ), SkipFlags, ExportCb); TSharedPtr<FJsonValue> ValueElement = FJsonObjectConverterEX::UPropertyToJsonValue(MapProperty->ValueProp, Helper.GetValuePtr(i), CheckFlags & ( ~CPF_ParmFlags ), SkipFlags, ExportCb); if ( KeyElement.IsValid() && ValueElement.IsValid() ) { FString KeyString; 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); } } Out->SetField(KeyString, ValueElement); } --n; } } return MakeShared<FJsonValueObject>(Out); } else if (UStructProperty *StructProperty = Cast<UStructProperty>(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 (FJsonObjectConverterEX::UStructToJsonObject(StructProperty->Struct, Value, Out, CheckFlags & (~CPF_ParmFlags), SkipFlags, ExportCb)) { return MakeShared<FJsonValueObject>(Out); } // fall through to default } else { // Default to export as string for everything else FString StringValue; Property->ExportTextItem(StringValue, Value, NULL, NULL, PPF_None); return MakeShared<FJsonValueString>(StringValue); } // invalid return TSharedPtr<FJsonValue>(); } } TSharedPtr<FJsonValue> FJsonObjectConverterEX::ObjectJsonCallback(UProperty* Property, const void* Value) { if (UObjectProperty* ObjectProperty = Cast<UObjectProperty>(Property)) { if (!ObjectProperty->HasAnyFlags(RF_Transient)) // We are taking Transient to mean we don't want to serialize to Json either (could make a new flag if nessasary) { TSharedRef<FJsonObject> Out = MakeShared<FJsonObject>(); CustomExportCallback CustomCB; CustomCB.BindStatic(FJsonObjectConverterEX::ObjectJsonCallback); void** PtrToValuePtr = (void**)Value; if (FJsonObjectConverterEX::UStructToJsonObject(ObjectProperty->PropertyClass, (*PtrToValuePtr), Out, 0, 0, &CustomCB)) { return MakeShared<FJsonValueObject>(Out); } } } // invalid return TSharedPtr<FJsonValue>(); } TSharedPtr<FJsonValue> FJsonObjectConverterEX::UPropertyToJsonValue(UProperty* Property, const void* Value, int64 CheckFlags, int64 SkipFlags, const CustomExportCallback* ExportCb) { if (Property->ArrayDim == 1) { return ConvertScalarUPropertyToJsonValue(Property, Value, CheckFlags, SkipFlags, ExportCb); } TArray< TSharedPtr<FJsonValue> > Array; for (int Index = 0; Index != Property->ArrayDim; ++Index) { Array.Add(ConvertScalarUPropertyToJsonValue(Property, (char*)Value + Index * Property->ElementSize, CheckFlags, SkipFlags, ExportCb)); } return MakeShared<FJsonValueArray>(Array); } bool FJsonObjectConverterEX::UStructToJsonObject(const UStruct* StructDefinition, const void* Struct, TSharedRef<FJsonObject> OutJsonObject, int64 CheckFlags, int64 SkipFlags, const CustomExportCallback* ExportCb) { return UStructToJsonAttributes(StructDefinition, Struct, OutJsonObject->Values, CheckFlags, SkipFlags, ExportCb); } bool FJsonObjectConverterEX::UStructToJsonAttributes(const UStruct* StructDefinition, const void* Struct, TMap< FString, TSharedPtr<FJsonValue> >& OutJsonAttributes, int64 CheckFlags, int64 SkipFlags, const CustomExportCallback* ExportCb) { 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; } for (TFieldIterator<UProperty> It(StructDefinition); It; ++It) { UProperty* 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 = Property->GetName();// StandardizeCase(Property->GetName()); const void* Value = Property->ContainerPtrToValuePtr<uint8>(Struct); // convert the property to a FJsonValue TSharedPtr<FJsonValue> JsonValue = UPropertyToJsonValue(Property, Value, CheckFlags, SkipFlags, ExportCb); if (!JsonValue.IsValid()) { UClass* PropClass = Property->GetClass(); UE_LOG(LogJson, Error, TEXT("UStructToJsonObject - Unhandled property type '%s': %s"), *PropClass->GetName(), *Property->GetPathName()); return false; } // set the value on the output object OutJsonAttributes.Add(VariableName, JsonValue); } 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 FJsonObjectConverterEX::UStructToJsonObjectString(const UStruct* StructDefinition, const void* Struct, FString& OutJsonString, int64 CheckFlags, int64 SkipFlags, int32 Indent, const CustomExportCallback* ExportCb, bool bPrettyPrint) { TSharedRef<FJsonObject> JsonObject = MakeShared<FJsonObject>(); if (UStructToJsonObject(StructDefinition, Struct, JsonObject, CheckFlags, SkipFlags, ExportCb)) { bool bSuccess = false; if (bPrettyPrint) { bSuccess = UStructToJsonObjectStringInternal<TCHAR, TPrettyJsonPrintPolicy<TCHAR> >(JsonObject, OutJsonString, Indent); } else { bSuccess = UStructToJsonObjectStringInternal<TCHAR, TCondensedJsonPrintPolicy<TCHAR> >(JsonObject, OutJsonString, Indent); } if (bSuccess) { return true; } else { UE_LOG(LogJson, Warning, TEXT("UStructToJsonObjectString - Unable to write out json")); } } return false; } //static bool FJsonObjectConverterEX::GetTextFromObject(const TSharedRef<FJsonObject>& Obj, FText& TextOut) { // get the prioritized culture name list FCultureRef CurrentCulture = FInternationalization::Get().GetCurrentCulture(); TArray<FString> CultureList = CurrentCulture->GetPrioritizedParentCultureNames(); // try to follow the fall back chain that the engine uses FString TextString; for (const FString& CultureCode : CultureList) { if (Obj->TryGetStringField(CultureCode, TextString)) { TextOut = FText::FromString(TextString); return true; } } // try again but only search on the locale region (in the localized data). This is a common omission (i.e. en-US source text should be used if no en is defined) for (const FString& LocaleToMatch : CultureList) { int32 SeparatorPos; // only consider base language entries in culture chain (i.e. "en") if (!LocaleToMatch.FindChar('-', SeparatorPos)) { for (const auto& Pair : Obj->Values) { // only consider coupled entries now (base ones would have been matched on first path) (i.e. "en-US") if (Pair.Key.FindChar('-', SeparatorPos)) { if (Pair.Key.StartsWith(LocaleToMatch)) { TextOut = FText::FromString(Pair.Value->AsString()); return true; } } } } } // no luck, is this possibly an unrelated json object? return false; } namespace { bool JsonValueToUPropertyWithContainer(const TSharedPtr<FJsonValue>& JsonValue, UProperty* Property, void* OutValue, const UStruct* ContainerStruct, void* Container, int64 CheckFlags, int64 SkipFlags); bool JsonAttributesToUStructWithContainer(const TMap< FString, TSharedPtr<FJsonValue> >& JsonAttributes, const UStruct* StructDefinition, void* OutStruct, const UStruct* ContainerStruct, void* Container, int64 CheckFlags, int64 SkipFlags); /** Convert JSON to property, assuming either the property is not an array or the value is an individual array element */ bool ConvertScalarJsonValueToUPropertyWithContainer(const TSharedPtr<FJsonValue>& JsonValue, UProperty* Property, void* OutValue, const UStruct* ContainerStruct, void* Container, int64 CheckFlags, int64 SkipFlags) { if (UEnumProperty* EnumProperty = Cast<UEnumProperty>(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, Error, TEXT("JsonValueToUProperty - Unable import enum %s from string value %s for property %s"), *Enum->CppType, *StrValue, *Property->GetNameCPP()); return false; } 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 (UNumericProperty *NumericProperty = Cast<UNumericProperty>(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(); int64 IntValue = Enum->GetValueByName(FName(*StrValue)); if (IntValue == INDEX_NONE) { UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable import enum %s from string value %s for property %s"), *Enum->CppType, *StrValue, *Property->GetNameCPP()); return false; } 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 (UBoolProperty *BoolProperty = Cast<UBoolProperty>(Property)) { // AsBool will log an error for completely inappropriate types (then give us a default) BoolProperty->SetPropertyValue(OutValue, JsonValue->AsBool()); } else if (UStrProperty *StringProperty = Cast<UStrProperty>(Property)) { // AsString will log an error for completely inappropriate types (then give us a default) StringProperty->SetPropertyValue(OutValue, JsonValue->AsString()); } else if (UArrayProperty *ArrayProperty = Cast<UArrayProperty>(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 (!JsonValueToUPropertyWithContainer(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 (UMapProperty* MapProperty = Cast<UMapProperty>(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 = JsonValueToUPropertyWithContainer(TempKeyValue, MapProperty->KeyProp, Helper.GetKeyPtr(NewIndex), ContainerStruct, Container, CheckFlags & (~CPF_ParmFlags), SkipFlags); const bool bValueSuccess = JsonValueToUPropertyWithContainer(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 (USetProperty* SetProperty = Cast<USetProperty>(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 (!JsonValueToUPropertyWithContainer(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 (UTextProperty* TextProperty = Cast<UTextProperty>(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 (!FJsonObjectConverterEX::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 (UStructProperty *StructProperty = Cast<UStructProperty>(Property)) { static const FName NAME_DateTime(TEXT("DateTime")); static const FName NAME_Color(TEXT("Color")); static const FName NAME_LinearColor(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_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 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 Property->ImportText(ImportTextPtr, OutValue, PPF_None, nullptr); } } else if (JsonValue->Type == EJson::String) { FString ImportTextString = JsonValue->AsString(); const TCHAR* ImportTextPtr = *ImportTextString; Property->ImportText(ImportTextPtr, OutValue, PPF_None, nullptr); } 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 (UObjectProperty *ObjectProperty = Cast<UObjectProperty>(Property)) { if (JsonValue->Type == EJson::Object) { UObject* Outer = GetTransientPackage(); if (ContainerStruct->IsChildOf(UObject::StaticClass())) { Outer = (UObject*)Container; } UClass* PropertyClass = ObjectProperty->PropertyClass; UObject* createdObj = StaticAllocateObject(PropertyClass, Outer, NAME_None, EObjectFlags::RF_NoFlags, EInternalObjectFlags::None, false); (*PropertyClass->ClassConstructor)(FObjectInitializer(createdObj, PropertyClass->ClassDefaultObject, false, false)); ObjectProperty->SetObjectPropertyValue(OutValue, createdObj); TSharedPtr<FJsonObject> Obj = JsonValue->AsObject(); check(Obj.IsValid()); // should not fail if Type == EJson::Object if (!JsonAttributesToUStructWithContainer(Obj->Values, ObjectProperty->PropertyClass, createdObj, ObjectProperty->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 (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; } } } else { // Default to expect a string for everything 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; } } return true; } bool JsonValueToUPropertyWithContainer(const TSharedPtr<FJsonValue>& JsonValue, UProperty* 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<UArrayProperty>() || Property->IsA<USetProperty>(); 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 ConvertScalarJsonValueToUPropertyWithContainer(JsonValue, Property, OutValue, ContainerStruct, Container, CheckFlags, SkipFlags); } // In practice, the ArrayDim == 1 check ought to be redundant, since nested arrays of UPropertys are not supported if (bArrayOrSetProperty && Property->ArrayDim == 1) { // Read into TArray return ConvertScalarJsonValueToUPropertyWithContainer(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 (!ConvertScalarJsonValueToUPropertyWithContainer(ArrayValue[Index], Property, (char*)OutValue + Index * Property->ElementSize, ContainerStruct, Container, CheckFlags, SkipFlags)) { return false; } } return true; } 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<UProperty> PropIt(StructDefinition); PropIt; ++PropIt) { UProperty* 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()); 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 (!JsonValueToUPropertyWithContainer(*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 FJsonObjectConverterEX::JsonValueToUProperty(const TSharedPtr<FJsonValue>& JsonValue, UProperty* Property, void* OutValue, int64 CheckFlags, int64 SkipFlags) { return JsonValueToUPropertyWithContainer(JsonValue, Property, OutValue, nullptr, nullptr, CheckFlags, SkipFlags); } bool FJsonObjectConverterEX::JsonObjectToUStruct(const TSharedRef<FJsonObject>& JsonObject, const UStruct* StructDefinition, void* OutStruct, int64 CheckFlags, int64 SkipFlags) { return JsonAttributesToUStruct(JsonObject->Values, StructDefinition, OutStruct, CheckFlags, SkipFlags); } bool FJsonObjectConverterEX::JsonAttributesToUStruct(const TMap< FString, TSharedPtr<FJsonValue> >& JsonAttributes, const UStruct* StructDefinition, void* OutStruct, int64 CheckFlags, int64 SkipFlags) { return JsonAttributesToUStructWithContainer(JsonAttributes, StructDefinition, OutStruct, StructDefinition, OutStruct, CheckFlags, SkipFlags); } FFormatNamedArguments FJsonObjectConverterEX::ParseTextArgumentsFromJson(const TSharedPtr<const FJsonObject>& JsonObject) { FFormatNamedArguments NamedArgs; if (JsonObject.IsValid()) { for (const auto& It : JsonObject->Values) { if (!It.Value.IsValid()) continue; switch (It.Value->Type) { case EJson::Number: // number NamedArgs.Emplace(It.Key, It.Value->AsNumber()); break; case EJson::String: // culture invariant string NamedArgs.Emplace(It.Key, FText::FromString(It.Value->AsString())); break; case EJson::Object: { // localized string FText TextOut; if (FJsonObjectConverterEX::GetTextFromObject(It.Value->AsObject().ToSharedRef(), TextOut)) { NamedArgs.Emplace(It.Key, TextOut); } else { UE_LOG(LogJson, Error, TEXT("Unable to apply Json parameter %s (could not parse object)"), *It.Key); } } break; default: UE_LOG(LogJson, Error, TEXT("Unable to apply Json parameter %s (bad type)"), *It.Key); break; } } } return NamedArgs; }
FString ret_json; FDTOData_Test data; data.Title = TEXT("sssss"); FJsonObjectConverterEX::UStructToJsonObjectString(data , ret_json, 0, 0); /* { "Title" : "sssss" } */
本文原创,不定时更新
可以随意转载到任何网站
~但是~ 转载也要按“基本法”
请注明原文出处和作者