Files
UnrealEngine/Engine/Source/Runtime/JsonUtilities/Private/JsonObjectConverter.cpp
2025-05-18 13:04:45 +08:00

1184 lines
48 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "JsonObjectConverter.h"
#include "Internationalization/Culture.h"
#include "Misc/PackageName.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"
#define LOCTEXT_NAMESPACE "JsonObjectConverter"
FString FJsonObjectConverter::StandardizeCase(const FString &StringIn)
{
// this probably won't work for all cases, consider downcasing 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
{
const FString ObjectClassNameKey = "_ClassName";
const FName NAME_DateTime(TEXT("DateTime"));
TSharedPtr<FJsonValue> FPropertyToJsonValueWithContainer(FProperty* Property, const void* Value, const UObject* Container, TSet<const UObject*>* ExportedObjects, int64 CheckFlags, int64 SkipFlags, const FJsonObjectConverter::CustomExportCallback* ExportCb, FProperty* OuterProperty, EJsonObjectConversionFlags ConversionFlags);
bool UStructToJsonAttributesWithContainer(const UStruct* StructDefinition, const void* Struct, const UObject* Container, TSet<const UObject*>* ExportedObjects, TMap< FString, TSharedPtr<FJsonValue> >& OutJsonAttributes, int64 CheckFlags, int64 SkipFlags, const FJsonObjectConverter::CustomExportCallback* ExportCb, EJsonObjectConversionFlags ConversionFlags);
bool ShouldExportObjectPropertyByValue(const FObjectProperty* Property, const UObject* Value, const UObject* Container, const TSet<const UObject*>* ExportedObjects, const FProperty* OuterProperty)
{
// Nothing to export if NULL.
if (!Value)
{
return false;
}
// Check the instanced flag for backwards compatibility - always export by value in this case.
if (Property->HasAnyPropertyFlags(CPF_PersistentInstance)
|| (OuterProperty && OuterProperty->HasAnyPropertyFlags(CPF_PersistentInstance)))
{
return true;
}
// Check if it's a reference to the container (self) to guard against cycles.
if (Value == Container)
{
return false;
}
// Check if we've already exported this value.
if (ExportedObjects
&& ExportedObjects->Contains(Value))
{
return false;
}
// Export by value if it is scoped within the current container context (if set).
if (Container
&& Value->IsInOuter(Container))
{
return true;
}
return false;
}
/** Convert property to JSON, assuming either the property is not an array or the value is an individual array element */
TSharedPtr<FJsonValue> ConvertScalarFPropertyToJsonValueWithContainer(FProperty* Property, const void* Value, const UObject* Container, TSet<const UObject*>* ExportedObjects, int64 CheckFlags, int64 SkipFlags, const FJsonObjectConverter::CustomExportCallback* ExportCb, FProperty* OuterProperty, EJsonObjectConversionFlags ConversionFlags)
{
// 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->GetAuthoredNameStringByValue(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)
{
// export enums as strings
FString StringValue = EnumDef->GetAuthoredNameStringByValue(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 (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))
{
if (EnumHasAnyFlags(ConversionFlags, EJsonObjectConversionFlags::WriteTextAsComplexString))
{
FString TextValueString;
FTextStringHelper::WriteToBuffer(TextValueString, TextProperty->GetPropertyValue(Value));
return MakeShared<FJsonValueString>(TextValueString);
}
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 = FPropertyToJsonValueWithContainer(ArrayProperty->Inner, Helper.GetRawPtr(i), Container, ExportedObjects, CheckFlags & ( ~CPF_ParmFlags ), SkipFlags, ExportCb, ArrayProperty, ConversionFlags);
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 (FScriptSetHelper::FIterator It(Helper); It; ++It)
{
TSharedPtr<FJsonValue> Elem = FPropertyToJsonValueWithContainer(SetProperty->ElementProp, Helper.GetElementPtr(It), Container, ExportedObjects, CheckFlags & (~CPF_ParmFlags), SkipFlags, ExportCb, SetProperty, ConversionFlags);
if (Elem.IsValid())
{
// add to the array
Out.Push(Elem);
}
}
return MakeShared<FJsonValueArray>(Out);
}
else if ( FMapProperty* MapProperty = CastField<FMapProperty>(Property) )
{
TSharedRef<FJsonObject> Out = MakeShared<FJsonObject>();
FScriptMapHelper Helper(MapProperty, Value);
for (FScriptMapHelper::FIterator It(Helper); It; ++It)
{
TSharedPtr<FJsonValue> KeyElement = FPropertyToJsonValueWithContainer(MapProperty->KeyProp, Helper.GetKeyPtr(It), Container, ExportedObjects, CheckFlags & (~CPF_ParmFlags), SkipFlags, ExportCb, MapProperty, ConversionFlags);
TSharedPtr<FJsonValue> ValueElement = FPropertyToJsonValueWithContainer(MapProperty->ValueProp, Helper.GetValuePtr(It), Container, ExportedObjects, CheckFlags & (~CPF_ParmFlags), SkipFlags, ExportCb, MapProperty, ConversionFlags);
if (KeyElement.IsValid() && ValueElement.IsValid())
{
FString KeyString;
if (!KeyElement->TryGetString(KeyString))
{
MapProperty->KeyProp->ExportTextItem_Direct(KeyString, Helper.GetKeyPtr(It), 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"), It.GetLogicalIndex());
}
}
// Coerce camelCase map keys for Enum/FName properties
if (CastField<FEnumProperty>(MapProperty->KeyProp) ||
CastField<FNameProperty>(MapProperty->KeyProp))
{
if (!EnumHasAnyFlags(ConversionFlags, EJsonObjectConversionFlags::SkipStandardizeCase))
{
KeyString = FJsonObjectConverter::StandardizeCase(KeyString);
}
}
Out->SetField(KeyString, ValueElement);
}
}
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 (UStructToJsonAttributesWithContainer(StructProperty->Struct, Value, Container, ExportedObjects, Out->Values, CheckFlags & (~CPF_ParmFlags), SkipFlags, ExportCb, ConversionFlags))
{
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 (ShouldExportObjectPropertyByValue(ObjectProperty, Object, Container, ExportedObjects, OuterProperty))
{
TSharedRef<FJsonObject> Out = MakeShared<FJsonObject>();
if (!EnumHasAnyFlags(ConversionFlags, EJsonObjectConversionFlags::SuppressClassNameForPersistentObject))
{
Out->SetStringField(ObjectClassNameKey, Object->GetClass()->GetPathName());
}
// Track it to ensure that we only export this object by value once; other instances of this value should export as the object's path (i.e. by reference)
if (ExportedObjects)
{
ExportedObjects->Emplace(Object);
}
// Use the subobject as the container context for this conversion so that we only create inner JsonObject values for instanced subobjects contained within.
// Also note we don't clear the ExportedObjects set here to ensure the subobject does not convert references we've already exported by value on an ancestor.
if (UStructToJsonAttributesWithContainer(Object->GetClass(), Object, Object, ExportedObjects, Out->Values, CheckFlags, SkipFlags, ExportCb, ConversionFlags))
{
TSharedRef<FJsonValueObject> JsonObject = MakeShared<FJsonValueObject>(Out);
JsonObject->Type = EJson::Object;
return JsonObject;
}
}
else
{
FString StringValue;
Property->ExportTextItem_Direct(StringValue, Value, nullptr, nullptr, PPF_None);
return MakeShared<FJsonValueString>(StringValue);
}
}
else
{
// Default to export as string for everything else
FString StringValue;
Property->ExportTextItem_Direct(StringValue, Value, NULL, NULL, PPF_None);
return MakeShared<FJsonValueString>(StringValue);
}
// invalid
return TSharedPtr<FJsonValue>();
}
TSharedPtr<FJsonValue> FPropertyToJsonValueWithContainer(FProperty* Property, const void* Value, const UObject* Container, TSet<const UObject*>* ExportedObjects, int64 CheckFlags, int64 SkipFlags, const FJsonObjectConverter::CustomExportCallback* ExportCb, FProperty* OuterProperty, EJsonObjectConversionFlags ConversionFlags)
{
if (Property->ArrayDim == 1)
{
return ConvertScalarFPropertyToJsonValueWithContainer(Property, Value, Container, ExportedObjects, CheckFlags, SkipFlags, ExportCb, OuterProperty, ConversionFlags);
}
TArray< TSharedPtr<FJsonValue> > Array;
for (int Index = 0; Index != Property->ArrayDim; ++Index)
{
Array.Add(ConvertScalarFPropertyToJsonValueWithContainer(Property, (char*)Value + Index * Property->GetElementSize(), Container, ExportedObjects, CheckFlags, SkipFlags, ExportCb, OuterProperty, ConversionFlags));
}
return MakeShared<FJsonValueArray>(Array);
}
bool UStructToJsonAttributesWithContainer(const UStruct* StructDefinition, const void* Struct, const UObject* Container, TSet<const UObject*>* ExportedObjects, TMap< FString, TSharedPtr<FJsonValue> >& OutJsonAttributes, int64 CheckFlags, int64 SkipFlags, const FJsonObjectConverter::CustomExportCallback* ExportCb, EJsonObjectConversionFlags ConversionFlags)
{
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<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 = Property->GetAuthoredName();
if (!EnumHasAnyFlags(ConversionFlags, EJsonObjectConversionFlags::SkipStandardizeCase))
{
VariableName = FJsonObjectConverter::StandardizeCase(VariableName);
}
const void* Value = Property->ContainerPtrToValuePtr<uint8>(Struct);
// convert the property to a FJsonValue
TSharedPtr<FJsonValue> JsonValue = FPropertyToJsonValueWithContainer(Property, Value, Container, ExportedObjects, CheckFlags, SkipFlags, ExportCb, nullptr, ConversionFlags);
if (!JsonValue.IsValid())
{
FFieldClass* 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;
}
}
TSharedPtr<FJsonValue> FJsonObjectConverter::UPropertyToJsonValue(FProperty* Property, const void* Value, int64 CheckFlags, int64 SkipFlags, const CustomExportCallback* ExportCb, FProperty* OuterProperty, EJsonObjectConversionFlags ConversionFlags)
{
return FPropertyToJsonValueWithContainer(Property, Value, nullptr, nullptr, CheckFlags, SkipFlags, ExportCb, OuterProperty, ConversionFlags);
}
bool FJsonObjectConverter::UStructToJsonObject(const UStruct* StructDefinition, const void* Struct, TSharedRef<FJsonObject> OutJsonObject, int64 CheckFlags, int64 SkipFlags, const CustomExportCallback* ExportCb, EJsonObjectConversionFlags ConversionFlags)
{
return UStructToJsonAttributes(StructDefinition, Struct, OutJsonObject->Values, CheckFlags, SkipFlags, ExportCb, ConversionFlags);
}
bool FJsonObjectConverter::UStructToJsonAttributes(const UStruct* StructDefinition, const void* Struct, TMap< FString, TSharedPtr<FJsonValue> >& OutJsonAttributes, int64 CheckFlags, int64 SkipFlags, const CustomExportCallback* ExportCb, EJsonObjectConversionFlags ConversionFlags)
{
const UObject* ContainerObject = nullptr;
if (StructDefinition->IsA<UClass>())
{
ContainerObject = static_cast<const UObject*>(Struct);
}
TSet<const UObject*> ExportedObjects;
return UStructToJsonAttributesWithContainer(StructDefinition, Struct, ContainerObject, &ExportedObjects, OutJsonAttributes, CheckFlags, SkipFlags, ExportCb, ConversionFlags);
}
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 FJsonObjectConverter::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 FJsonObjectConverter::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 JsonValueToFPropertyWithContainer(const TSharedPtr<FJsonValue>& JsonValue, FProperty* Property, void* OutValue, const UStruct* ContainerStruct, void* Container, int64 CheckFlags, int64 SkipFlags, const bool bStrictMode, FText* OutFailReason, const FJsonObjectConverter::CustomImportCallback* ImportCb);
bool JsonAttributesToUStructWithContainer(const TMap< FString, TSharedPtr<FJsonValue> >& JsonAttributes, const UStruct* StructDefinition, void* OutStruct, const UStruct* ContainerStruct, void* Container, int64 CheckFlags, int64 SkipFlags, const bool bStrictMode, FText* OutFailReason, const FJsonObjectConverter::CustomImportCallback* ImportCb);
/** Convert JSON to property, assuming either the property is not an array or the value is an individual array element */
bool ConvertScalarJsonValueToFPropertyWithContainer(const TSharedPtr<FJsonValue>& JsonValue, FProperty* Property, void* OutValue, const UStruct* ContainerStruct, void* Container, int64 CheckFlags, int64 SkipFlags, const bool bStrictMode, FText* OutFailReason, const FJsonObjectConverter::CustomImportCallback* ImportCb)
{
if (ImportCb && ImportCb->IsBound())
{
if (ImportCb->Execute(JsonValue, Property, OutValue))
{
return true;
}
// fall through to default cases
}
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), EGetByNameFlags::CheckAuthoredName);
if (IntValue == INDEX_NONE)
{
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import enum %s from string value %s for property %s"), *Enum->CppType, *StrValue, *Property->GetAuthoredName());
if (OutFailReason)
{
*OutFailReason = FText::Format(LOCTEXT("FailImportEnumFromString", "Unable to import enum {0} from string value {1} for property {2}"), FText::FromString(Enum->CppType), FText::FromString(StrValue), FText::FromString(Property->GetAuthoredName()));
}
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 (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();
int64 IntValue = Enum->GetValueByName(FName(*StrValue), EGetByNameFlags::CheckAuthoredName);
if (IntValue == INDEX_NONE)
{
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import enum %s from numeric value %s for property %s"), *Enum->CppType, *StrValue, *Property->GetAuthoredName());
if (OutFailReason)
{
*OutFailReason = FText::Format(LOCTEXT("FailImportEnumFromNumeric", "Unable to import enum {0} from numeric value {1} for property {2}"), FText::FromString(Enum->CppType), FText::FromString(StrValue), FText::FromString(Property->GetAuthoredName()));
}
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 import json value into %s numeric property %s"), *Property->GetClass()->GetName(), *Property->GetAuthoredName());
if (OutFailReason)
{
*OutFailReason = FText::Format(LOCTEXT("FailImportNumericProperty", "Unable to import json value into {0} numeric property {1}"), FText::FromString(Property->GetClass()->GetName()), FText::FromString(Property->GetAuthoredName()));
}
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, bStrictMode, OutFailReason, ImportCb))
{
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import Array element %d for property %s"), i, *Property->GetAuthoredName());
if (OutFailReason)
{
*OutFailReason = FText::Format(LOCTEXT("FailImportArrayElement", "Unable to import Array element {0} for property {1}\n{2}"), FText::AsNumber(i), FText::FromString(Property->GetAuthoredName()), *OutFailReason);
}
return false;
}
}
}
}
else
{
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import non-array JSON value into Array property %s"), *Property->GetAuthoredName());
if (OutFailReason)
{
*OutFailReason = FText::Format(LOCTEXT("FailImportArray", "Unable to import non-array JSON value into Array property {0}"), FText::FromString(Property->GetAuthoredName()));
}
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);
if (!JsonValueToFPropertyWithContainer(TempKeyValue, MapProperty->KeyProp, Helper.GetKeyPtr(NewIndex), ContainerStruct, Container, CheckFlags & (~CPF_ParmFlags), SkipFlags, bStrictMode, OutFailReason, ImportCb))
{
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import Map element %s key for property %s"), *Entry.Key, *Property->GetAuthoredName());
if (OutFailReason)
{
*OutFailReason = FText::Format(LOCTEXT("FailImportMapElementKey", "Unable to import Map element {0} key for property {1}\n{2}"), FText::FromString(Entry.Key), FText::FromString(Property->GetAuthoredName()), *OutFailReason);
}
return false;
}
if (!JsonValueToFPropertyWithContainer(Entry.Value, MapProperty->ValueProp, Helper.GetValuePtr(NewIndex), ContainerStruct, Container, CheckFlags & (~CPF_ParmFlags), SkipFlags, bStrictMode, OutFailReason, ImportCb))
{
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import Map element %s value for property %s"), *Entry.Key, *Property->GetAuthoredName());
if (OutFailReason)
{
*OutFailReason = FText::Format(LOCTEXT("FailImportMapElementValue", "Unable to import Map element {0} value for property {1}\n{2}"), FText::FromString(Entry.Key), FText::FromString(Property->GetAuthoredName()), *OutFailReason);
}
return false;
}
}
}
Helper.Rehash();
}
else
{
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import non-object JSON value into Map property %s"), *Property->GetAuthoredName());
if (OutFailReason)
{
*OutFailReason = FText::Format(LOCTEXT("FailImportMap", "Unable to import non-object JSON value into Map property {0}"), FText::FromString(Property->GetAuthoredName()));
}
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);
Helper.EmptyElements(ArrLen);
// 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, bStrictMode, OutFailReason, ImportCb))
{
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import Set element %d for property %s"), i, *Property->GetAuthoredName());
if (OutFailReason)
{
*OutFailReason = FText::Format(LOCTEXT("FailImportSetElement", "Unable to import Set element {0} for property {1}\n{2}"), FText::AsNumber(i), FText::FromString(Property->GetAuthoredName()), *OutFailReason);
}
return false;
}
}
}
Helper.Rehash();
}
else
{
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import non-array JSON value into Set property %s"), *Property->GetAuthoredName());
if (OutFailReason)
{
*OutFailReason = FText::Format(LOCTEXT("FailImportSet", "Unable to import non-array JSON value into Set property {0}"), FText::FromString(Property->GetAuthoredName()));
}
return false;
}
}
else if (FTextProperty* TextProperty = CastField<FTextProperty>(Property))
{
if (JsonValue->Type == EJson::String)
{
FString StringValue = JsonValue->AsString();
FText TextValue;
if (!FTextStringHelper::ReadFromBuffer(*StringValue, TextValue))
{
TextValue = FText::FromString(StringValue);
}
// assume this string is already localized, so import as invariant
TextProperty->SetPropertyValue(OutValue, TextValue);
}
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 - Unable to import JSON object with invalid keys into Text property %s"), *Property->GetAuthoredName());
if (OutFailReason)
{
*OutFailReason = FText::Format(LOCTEXT("FailImportTextFromObject", "Unable to import JSON object with invalid keys into Text property {0}"), FText::FromString(Property->GetAuthoredName()));
}
return false;
}
TextProperty->SetPropertyValue(OutValue, Text);
}
else
{
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import JSON value that is neither string nor object into Text property %s"), *Property->GetAuthoredName());
if (OutFailReason)
{
*OutFailReason = FText::Format(LOCTEXT("FailImportText", "Unable to import JSON value that is neither string nor object into Text property {0}"), FText::FromString(Property->GetAuthoredName()));
}
return false;
}
}
else if (FStructProperty *StructProperty = CastField<FStructProperty>(Property))
{
if (JsonValue->Type == EJson::Object)
{
TSharedPtr<FJsonObject> Obj = JsonValue->AsObject();
check(Obj.IsValid()); // should not fail if Type == EJson::Object
if (!JsonAttributesToUStructWithContainer(Obj->Values, StructProperty->Struct, OutValue, ContainerStruct, Container, CheckFlags & (~CPF_ParmFlags), SkipFlags, bStrictMode, OutFailReason, ImportCb))
{
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import JSON object into %s property %s"), *StructProperty->Struct->GetAuthoredName(), *Property->GetAuthoredName());
if (OutFailReason)
{
*OutFailReason = FText::Format(LOCTEXT("FailImportStructFromObject", "Unable to import JSON object into {0} property {1}\n{2}"), FText::FromString(StructProperty->Struct->GetAuthoredName()), FText::FromString(Property->GetAuthoredName()), *OutFailReason);
}
return false;
}
}
else if (JsonValue->Type == EJson::String && StructProperty->Struct->GetFName() == NAME_LinearColor)
{
FLinearColor& ColorOut = *(FLinearColor*)OutValue;
FString ColorString = JsonValue->AsString();
FColor IntermediateColor;
IntermediateColor = FColor::FromHex(ColorString);
ColorOut = IntermediateColor;
}
else if (JsonValue->Type == EJson::String && StructProperty->Struct->GetFName() == NAME_Color)
{
FColor& ColorOut = *(FColor*)OutValue;
FString ColorString = JsonValue->AsString();
ColorOut = FColor::FromHex(ColorString);
}
else if (JsonValue->Type == EJson::String && StructProperty->Struct->GetFName() == NAME_DateTime)
{
FString DateString = JsonValue->AsString();
FDateTime& DateTimeOut = *(FDateTime*)OutValue;
if (DateString == TEXT("min"))
{
// min representable value for our date struct. Actual date may vary by platform (this is used for sorting)
DateTimeOut = FDateTime::MinValue();
}
else if (DateString == TEXT("max"))
{
// max representable value for our date struct. Actual date may vary by platform (this is used for sorting)
DateTimeOut = FDateTime::MaxValue();
}
else if (DateString == TEXT("now"))
{
// this value's not really meaningful from JSON serialization (since we don't know timezone) but handle it anyway since we're handling the other keywords
DateTimeOut = FDateTime::UtcNow();
}
else if (FDateTime::ParseIso8601(*DateString, DateTimeOut))
{
// ok
}
else if (FDateTime::Parse(DateString, DateTimeOut))
{
// ok
}
else
{
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import JSON string into DateTime property %s"), *Property->GetAuthoredName());
if (OutFailReason)
{
*OutFailReason = FText::Format(LOCTEXT("FailImportDateTimeFromString", "Unable to import JSON string into DateTime property {0}"), FText::FromString(Property->GetAuthoredName()));
}
return false;
}
}
else if (JsonValue->Type == EJson::String && StructProperty->Struct->GetCppStructOps() && StructProperty->Struct->GetCppStructOps()->HasImportTextItem())
{
UScriptStruct::ICppStructOps* TheCppStructOps = StructProperty->Struct->GetCppStructOps();
FString ImportTextString = JsonValue->AsString();
const TCHAR* ImportTextPtr = *ImportTextString;
if (!TheCppStructOps->ImportTextItem(ImportTextPtr, OutValue, PPF_None, nullptr, (FOutputDevice*)GWarn))
{
// Fall back to trying the tagged property approach if custom ImportTextItem couldn't get it done
if (Property->ImportText_Direct(ImportTextPtr, OutValue, nullptr, PPF_None) == nullptr)
{
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import JSON string into %s property %s"), *StructProperty->Struct->GetAuthoredName(), *Property->GetAuthoredName());
if (OutFailReason)
{
*OutFailReason = FText::Format(LOCTEXT("FailImportStructFromString", "Unable to import JSON string into {0} property {1}"), FText::FromString(StructProperty->Struct->GetAuthoredName()), FText::FromString(Property->GetAuthoredName()));
}
return false;
}
}
}
else if (JsonValue->Type == EJson::String)
{
FString ImportTextString = JsonValue->AsString();
const TCHAR* ImportTextPtr = *ImportTextString;
if (Property->ImportText_Direct(ImportTextPtr, OutValue, nullptr, PPF_None) == nullptr)
{
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import JSON string into %s property %s"), *StructProperty->Struct->GetAuthoredName(), *Property->GetAuthoredName());
if (OutFailReason)
{
*OutFailReason = FText::Format(LOCTEXT("FailImportStructFromString", "Unable to import JSON string into {0} property {1}"), FText::FromString(StructProperty->Struct->GetAuthoredName()), FText::FromString(Property->GetAuthoredName()));
}
return false;
}
}
else
{
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import JSON value that is neither string nor object into %s property %s"), *StructProperty->Struct->GetAuthoredName(), *Property->GetAuthoredName());
if (OutFailReason)
{
*OutFailReason = FText::Format(LOCTEXT("FailImportStruct", "Unable to import JSON value that is neither string nor object into {0} property {1}"), FText::FromString(StructProperty->Struct->GetAuthoredName()), FText::FromString(Property->GetAuthoredName()));
}
return false;
}
}
else if (FObjectProperty *ObjectProperty = CastField<FObjectProperty>(Property))
{
if (JsonValue->Type == EJson::Object)
{
UObject* Outer = 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())
{
UClass* FoundClass = FPackageName::IsShortPackageName(ClassString) ? FindFirstObject<UClass>(*ClassString) : LoadClass<UObject>(nullptr, *ClassString);
if (FoundClass)
{
PropertyClass = FoundClass;
}
}
UObject* createdObj = StaticAllocateObject(PropertyClass, Outer, NAME_None, EObjectFlags::RF_NoFlags, EInternalObjectFlags::None, false);
(*PropertyClass->ClassConstructor)(FObjectInitializer(createdObj, PropertyClass->GetDefaultObject(false), EObjectInitializerOptions::None));
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, bStrictMode, OutFailReason, ImportCb))
{
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import JSON object into %s property %s"), *PropertyClass->GetAuthoredName(), *Property->GetAuthoredName());
if (OutFailReason)
{
*OutFailReason = FText::Format(LOCTEXT("FailImportObjectFromObject", "Unable to import JSON object into {0} property {1}\n{2}"), FText::FromString(PropertyClass->GetAuthoredName()), FText::FromString(Property->GetAuthoredName()), *OutFailReason);
}
return false;
}
}
else if (JsonValue->Type == EJson::String)
{
// 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 %s property %s"), *ObjectProperty->PropertyClass->GetAuthoredName(), *Property->GetAuthoredName());
if (OutFailReason)
{
*OutFailReason = FText::Format(LOCTEXT("FailImportObjectFromString", "Unable to import JSON string into {0} property {1}"), FText::FromString(*ObjectProperty->PropertyClass->GetAuthoredName()), FText::FromString(Property->GetAuthoredName()));
}
return false;
}
}
}
else
{
// 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());
if (OutFailReason)
{
*OutFailReason = FText::Format(LOCTEXT("FailImportFromString", "Unable to import JSON string into property {0}"), FText::FromString(Property->GetAuthoredName()));
}
return false;
}
}
return true;
}
bool JsonValueToFPropertyWithContainer(const TSharedPtr<FJsonValue>& JsonValue, FProperty* Property, void* OutValue, const UStruct* ContainerStruct, void* Container, int64 CheckFlags, int64 SkipFlags, const bool bStrictMode, FText* OutFailReason, const FJsonObjectConverter::CustomImportCallback* ImportCb)
{
if (!JsonValue.IsValid())
{
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Invalid JSON value"));
if (OutFailReason)
{
*OutFailReason = LOCTEXT("InvalidJsonValue", "Invalid JSON value");
}
return false;
}
const bool bArrayOrSetProperty = Property->IsA<FArrayProperty>() || Property->IsA<FSetProperty>();
const bool bJsonArray = JsonValue->Type == EJson::Array;
if (!bJsonArray)
{
if (bArrayOrSetProperty)
{
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Expecting JSON array"));
if (OutFailReason)
{
*OutFailReason = LOCTEXT("ExpectingJsonArray", "Expecting JSON array");
}
return false;
}
if (Property->ArrayDim != 1)
{
if (bStrictMode)
{
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Property %s is not an array but has %d elements"), *Property->GetAuthoredName(), Property->ArrayDim);
if (OutFailReason)
{
*OutFailReason = FText::Format(LOCTEXT("InvalidDimensionOfNonArrayProperty", "Property {0} is not an array but has {1} elements"), FText::FromString(Property->GetAuthoredName()), FText::AsNumber(Property->ArrayDim));
}
return false;
}
UE_LOG(LogJson, Warning, TEXT("Ignoring excess properties when deserializing %s"), *Property->GetAuthoredName());
}
return ConvertScalarJsonValueToFPropertyWithContainer(JsonValue, Property, OutValue, ContainerStruct, Container, CheckFlags, SkipFlags, bStrictMode, OutFailReason, ImportCb);
}
// In practice, the ArrayDim == 1 check ought to be redundant, since nested arrays of FProperties are not supported
if (bArrayOrSetProperty && Property->ArrayDim == 1)
{
// Read into TArray
return ConvertScalarJsonValueToFPropertyWithContainer(JsonValue, Property, OutValue, ContainerStruct, Container, CheckFlags, SkipFlags, bStrictMode, OutFailReason, ImportCb);
}
// We're deserializing a JSON array
const auto& ArrayValue = JsonValue->AsArray();
if (bStrictMode && (Property->ArrayDim != ArrayValue.Num()))
{
UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - JSON array size is incorrect (has %d elements, but needs %d)"), ArrayValue.Num(), Property->ArrayDim);
if (OutFailReason)
{
*OutFailReason = FText::Format(LOCTEXT("IncorrectArraySize", "JSON array size is incorrect (has {0} elements, but needs {1})"), FText::AsNumber(ArrayValue.Num()), FText::AsNumber(Property->ArrayDim));
}
return false;
}
if (Property->ArrayDim < ArrayValue.Num())
{
UE_LOG(LogJson, Warning, TEXT("Ignoring excess properties when deserializing %s"), *Property->GetAuthoredName());
}
// Read into native array
const int32 ItemsToRead = FMath::Clamp(ArrayValue.Num(), 0, Property->ArrayDim);
for (int Index = 0; Index != ItemsToRead; ++Index)
{
if (!ConvertScalarJsonValueToFPropertyWithContainer(ArrayValue[Index], Property, static_cast<char*>(OutValue) + Index * Property->GetElementSize(), ContainerStruct, Container, CheckFlags, SkipFlags, bStrictMode, OutFailReason, ImportCb))
{
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, const bool bStrictMode, FText* OutFailReason, const FJsonObjectConverter::CustomImportCallback* ImportCb)
{
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
FString PropertyName = StructDefinition->GetAuthoredNameForField(Property);
const TSharedPtr<FJsonValue>* JsonValue = JsonAttributes.Find(PropertyName);
if (!JsonValue)
{
if (bStrictMode)
{
UE_LOG(LogJson, Error, TEXT("JsonObjectToUStruct - Missing JSON value named %s"), *PropertyName);
if (OutFailReason)
{
*OutFailReason = FText::Format(LOCTEXT("MissingJsonField", "Missing JSON value named {0}"), FText::FromString(PropertyName));
}
return false;
}
// 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, bStrictMode, OutFailReason, ImportCb))
{
UE_LOG(LogJson, Error, TEXT("JsonObjectToUStruct - Unable to import JSON value into property %s"), *PropertyName);
if (OutFailReason)
{
*OutFailReason = FText::Format(LOCTEXT("FailImportValueToProperty", "Unable to import JSON value into property {0}\n{1}"), FText::FromString(PropertyName), *OutFailReason);
}
return false;
}
}
if (--NumUnclaimedProperties <= 0)
{
// Should we log a warning/error if we still have properties in the JSON data that aren't in the struct definition in strict mode?
// If we found all properties that were in the JsonAttributes map, there is no reason to keep looking for more.
break;
}
}
return true;
}
}
bool FJsonObjectConverter::JsonValueToUProperty(const TSharedPtr<FJsonValue>& JsonValue, FProperty* Property, void* OutValue, int64 CheckFlags, int64 SkipFlags, const bool bStrictMode, FText* OutFailReason, const CustomImportCallback* ImportCb)
{
return JsonValueToFPropertyWithContainer(JsonValue, Property, OutValue, nullptr, nullptr, CheckFlags, SkipFlags, bStrictMode, OutFailReason, ImportCb);
}
bool FJsonObjectConverter::JsonObjectToUStruct(const TSharedRef<FJsonObject>& JsonObject, const UStruct* StructDefinition, void* OutStruct, int64 CheckFlags, int64 SkipFlags, const bool bStrictMode, FText* OutFailReason, const CustomImportCallback* ImportCb)
{
return JsonAttributesToUStruct(JsonObject->Values, StructDefinition, OutStruct, CheckFlags, SkipFlags, bStrictMode, OutFailReason, ImportCb);
}
bool FJsonObjectConverter::JsonAttributesToUStruct(const TMap< FString, TSharedPtr<FJsonValue> >& JsonAttributes, const UStruct* StructDefinition, void* OutStruct, int64 CheckFlags, int64 SkipFlags, const bool bStrictMode, FText* OutFailReason, const CustomImportCallback* ImportCb)
{
return JsonAttributesToUStructWithContainer(JsonAttributes, StructDefinition, OutStruct, StructDefinition, OutStruct, CheckFlags, SkipFlags, bStrictMode, OutFailReason, ImportCb);
}
//static
bool FJsonObjectConverter::GetTextFromField(const FString& FieldName, const TSharedPtr<FJsonValue>& FieldValue, FText& TextOut)
{
if (FieldValue.IsValid())
{
switch (FieldValue->Type)
{
case EJson::Number:
{
// number
TextOut = FText::AsNumber(FieldValue->AsNumber());
return true;
}
case EJson::String:
{
if (FieldName.StartsWith(TEXT("date-")))
{
FDateTime Dte;
if (FDateTime::ParseIso8601(*FieldValue->AsString(), Dte))
{
TextOut = FText::AsDate(Dte);
return true;
}
}
else if (FieldName.StartsWith(TEXT("datetime-")))
{
FDateTime Dte;
if (FDateTime::ParseIso8601(*FieldValue->AsString(), Dte))
{
TextOut = FText::AsDateTime(Dte);
return true;
}
}
else
{
// culture invariant string
TextOut = FText::FromString(FieldValue->AsString());
return true;
}
break;
}
case EJson::Object:
{
// localized string
if (FJsonObjectConverter::GetTextFromObject(FieldValue->AsObject().ToSharedRef(), TextOut))
{
return true;
}
UE_LOG(LogJson, Error, TEXT("Unable to apply JSON parameter %s (could not parse object)"), *FieldName);
break;
}
default:
{
UE_LOG(LogJson, Error, TEXT("Unable to apply JSON parameter %s (bad type)"), *FieldName);
break;
}
}
}
return false;
}
FFormatNamedArguments FJsonObjectConverter::ParseTextArgumentsFromJson(const TSharedPtr<const FJsonObject>& JsonObject)
{
FFormatNamedArguments NamedArgs;
if (JsonObject.IsValid())
{
for (const auto& It : JsonObject->Values)
{
FText TextValue;
if (GetTextFromField(It.Key, It.Value, TextValue))
{
NamedArgs.Emplace(It.Key, TextValue);
}
}
}
return NamedArgs;
}
const FJsonObjectConverter::CustomExportCallback FJsonObjectConverter::ExportCallback_WriteISO8601Dates =
FJsonObjectConverter::CustomExportCallback::CreateLambda(
[](FProperty* Prop, const void* Data) -> TSharedPtr<FJsonValue>
{
if (FStructProperty* StructProperty = CastField<FStructProperty>(Prop))
{
checkSlow(StructProperty->Struct);
if (StructProperty->Struct->GetFName() == NAME_DateTime)
{
return MakeShared<FJsonValueString>(static_cast<const FDateTime*>(Data)->ToIso8601());
}
}
return {};
});
#undef LOCTEXT_NAMESPACE