123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083 |
- // Copyright 2018-current Getnamo. All Rights Reserved
- #include "SIOJConvert.h"
- //#include "Json.h"
- #include "UObject/TextProperty.h"
- #include "JsonGlobals.h"
- #include "Policies/CondensedJsonPrintPolicy.h"
- #include "Misc/FileHelper.h"
- #include "SIOJsonValue.h"
- #include "SIOJsonObject.h"
- #include "JsonObjectConverter.h"
- #include "UObject/PropertyPortFlags.h"
- #include "Misc/Base64.h"
- typedef TJsonWriterFactory< TCHAR, TCondensedJsonPrintPolicy<TCHAR> > FCondensedJsonStringWriterFactory;
- typedef TJsonWriter< TCHAR, TCondensedJsonPrintPolicy<TCHAR> > FCondensedJsonStringWriter;
- //The one key that will break
- #define TMAP_STRING TEXT("!__!INTERNAL_TMAP")
- namespace
- {
- FJsonObjectConverter::CustomExportCallback EnumOverrideExportCallback;
- //Begin partial copy of FJsonObjectConverter for BP enum workaround
- bool JsonValueToFPropertyWithContainer(const TSharedPtr<FJsonValue>& JsonValue, FProperty* 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 ConvertScalarJsonValueToFPropertyWithContainer(const TSharedPtr<FJsonValue>& JsonValue, FProperty* Property, void* OutValue, const UStruct* ContainerStruct, void* Container, int64 CheckFlags, int64 SkipFlags)
- {
- if (FEnumProperty* EnumProperty = CastField<FEnumProperty>(Property))
- {
- if (JsonValue->Type == EJson::String)
- {
- // see if we were passed a string for the enum
- const UEnum* Enum = EnumProperty->GetEnum();
- check(Enum);
- FString StrValue = JsonValue->AsString();
- int64 IntValue = Enum->GetValueByName(FName(*StrValue));
- if (IntValue == INDEX_NONE)
- {
- UE_LOG(LogJson, Error, TEXT("BPEnumWA-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 (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));
- //BEGIN WORKAROUND MODIFICATION
- if (IntValue == INDEX_NONE)
- {
- //Failed 'NewEnumeratorX' lookup, try via DisplayNames
- const FString LowerStrValue = StrValue.ToLower();
- //blueprints only support int8 sized enums
- int8 MaxEnum = (int8)Enum->GetMaxEnumValue();
- for (int32 i = 0; i < MaxEnum; i++)
- {
- //Case insensitive match
- if (LowerStrValue.Equals(Enum->GetDisplayNameTextByIndex(i).ToString().ToLower()))
- {
- IntValue = i;
- }
- }
- //END WORKAROUND MODIFICATION
- if (IntValue == INDEX_NONE)
- {
- UE_LOG(LogJson, Error, TEXT("BPEnumWA-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("BPEnumWA-JsonValueToUProperty - Unable to set numeric property type %s for property %s"), *Property->GetClass()->GetName(), *Property->GetNameCPP());
- return false;
- }
- }
- else if (FBoolProperty *BoolProperty = CastField<FBoolProperty>(Property))
- {
- // AsBool will log an error for completely inappropriate types (then give us a default)
- BoolProperty->SetPropertyValue(OutValue, JsonValue->AsBool());
- }
- else if (FStrProperty *StringProperty = CastField<FStrProperty>(Property))
- {
- // AsString will log an error for completely inappropriate types (then give us a default)
- StringProperty->SetPropertyValue(OutValue, JsonValue->AsString());
- }
- else if (FArrayProperty *ArrayProperty = CastField<FArrayProperty>(Property))
- {
- if (JsonValue->Type == EJson::Array)
- {
- TArray< TSharedPtr<FJsonValue> > ArrayValue = JsonValue->AsArray();
- int32 ArrLen = ArrayValue.Num();
- // make the output array size match
- FScriptArrayHelper Helper(ArrayProperty, OutValue);
- Helper.Resize(ArrLen);
- // set the property values
- for (int32 i = 0; i < ArrLen; ++i)
- {
- const TSharedPtr<FJsonValue>& ArrayValueItem = ArrayValue[i];
- if (ArrayValueItem.IsValid() && !ArrayValueItem->IsNull())
- {
- if (!JsonValueToFPropertyWithContainer(ArrayValueItem, ArrayProperty->Inner, Helper.GetRawPtr(i), ContainerStruct, Container, CheckFlags & (~CPF_ParmFlags), SkipFlags))
- {
- UE_LOG(LogJson, Error, TEXT("BPEnumWA-JsonValueToUProperty - Unable to deserialize array element [%d] for property %s"), i, *Property->GetNameCPP());
- return false;
- }
- }
- }
- }
- else
- {
- UE_LOG(LogJson, Error, TEXT("BPEnumWA-JsonValueToUProperty - Attempted to import TArray from non-array JSON key for property %s"), *Property->GetNameCPP());
- return false;
- }
- }
- else if (FMapProperty* MapProperty = CastField<FMapProperty>(Property))
- {
- if (JsonValue->Type == EJson::Object)
- {
- TSharedPtr<FJsonObject> ObjectValue = JsonValue->AsObject();
- FScriptMapHelper Helper(MapProperty, OutValue);
- check(ObjectValue);
- int32 MapSize = ObjectValue->Values.Num();
- Helper.EmptyValues(MapSize);
- // set the property values
- for (const auto& Entry : ObjectValue->Values)
- {
- if (Entry.Value.IsValid() && !Entry.Value->IsNull())
- {
- int32 NewIndex = Helper.AddDefaultValue_Invalid_NeedsRehash();
- TSharedPtr<FJsonValueString> TempKeyValue = MakeShared<FJsonValueString>(Entry.Key);
- const bool bKeySuccess = JsonValueToFPropertyWithContainer(TempKeyValue, MapProperty->KeyProp, Helper.GetKeyPtr(NewIndex), ContainerStruct, Container, CheckFlags & (~CPF_ParmFlags), SkipFlags);
- const bool bValueSuccess = JsonValueToFPropertyWithContainer(Entry.Value, MapProperty->ValueProp, Helper.GetValuePtr(NewIndex), ContainerStruct, Container, CheckFlags & (~CPF_ParmFlags), SkipFlags);
- if (!(bKeySuccess && bValueSuccess))
- {
- UE_LOG(LogJson, Error, TEXT("BPEnumWA-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("BPEnumWA-JsonValueToUProperty - Attempted to import TMap from non-object JSON key for property %s"), *Property->GetNameCPP());
- return false;
- }
- }
- else if (FSetProperty* SetProperty = CastField<FSetProperty>(Property))
- {
- if (JsonValue->Type == EJson::Array)
- {
- TArray< TSharedPtr<FJsonValue> > ArrayValue = JsonValue->AsArray();
- int32 ArrLen = ArrayValue.Num();
- FScriptSetHelper Helper(SetProperty, OutValue);
- // set the property values
- for (int32 i = 0; i < ArrLen; ++i)
- {
- const TSharedPtr<FJsonValue>& ArrayValueItem = ArrayValue[i];
- if (ArrayValueItem.IsValid() && !ArrayValueItem->IsNull())
- {
- int32 NewIndex = Helper.AddDefaultValue_Invalid_NeedsRehash();
- if (!JsonValueToFPropertyWithContainer(ArrayValueItem, SetProperty->ElementProp, Helper.GetElementPtr(NewIndex), ContainerStruct, Container, CheckFlags & (~CPF_ParmFlags), SkipFlags))
- {
- UE_LOG(LogJson, Error, TEXT("BPEnumWA-JsonValueToUProperty - Unable to deserialize set element [%d] for property %s"), i, *Property->GetNameCPP());
- return false;
- }
- }
- }
- Helper.Rehash();
- }
- else
- {
- UE_LOG(LogJson, Error, TEXT("BPEnumWA-JsonValueToUProperty - Attempted to import TSet from non-array JSON key for property %s"), *Property->GetNameCPP());
- return false;
- }
- }
- else if (FTextProperty* TextProperty = CastField<FTextProperty>(Property))
- {
- if (JsonValue->Type == EJson::String)
- {
- // assume this string is already localized, so import as invariant
- TextProperty->SetPropertyValue(OutValue, FText::FromString(JsonValue->AsString()));
- }
- else if (JsonValue->Type == EJson::Object)
- {
- TSharedPtr<FJsonObject> Obj = JsonValue->AsObject();
- check(Obj.IsValid()); // should not fail if Type == EJson::Object
- // import the subvalue as a culture invariant string
- FText Text;
- if (!FJsonObjectConverter::GetTextFromObject(Obj.ToSharedRef(), Text))
- {
- UE_LOG(LogJson, Error, TEXT("BPEnumWA-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("BPEnumWA-JsonValueToUProperty - Attempted to import FText from JSON that was neither string nor object for property %s"), *Property->GetNameCPP());
- return false;
- }
- }
- else if (FStructProperty *StructProperty = CastField<FStructProperty>(Property))
- {
- static const FName NAME_DateTime(TEXT("DateTime"));
- static const FName NAME_Color_Local(TEXT("Color"));
- static const FName NAME_LinearColor_Local(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("BPEnumWA-JsonValueToUProperty - FJsonObjectConverter::JsonObjectToUStruct failed for property %s"), *Property->GetNameCPP());
- return false;
- }
- }
- else if (JsonValue->Type == EJson::String && StructProperty->Struct->GetFName() == NAME_LinearColor_Local)
- {
- 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("BPEnumWA-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_Direct(ImportTextPtr, OutValue, nullptr, PPF_None);
- }
- }
- else if (JsonValue->Type == EJson::String)
- {
- FString ImportTextString = JsonValue->AsString();
- const TCHAR* ImportTextPtr = *ImportTextString;
- Property->ImportText_Direct(ImportTextPtr, OutValue, nullptr, PPF_None);
- }
- else
- {
- UE_LOG(LogJson, Error, TEXT("BPEnumWA-JsonValueToUProperty - Attempted to import UStruct from non-object JSON key for property %s"), *Property->GetNameCPP());
- return false;
- }
- }
- else if (FObjectProperty *ObjectProperty = CastField<FObjectProperty>(Property))
- {
- if (JsonValue->Type == EJson::Object)
- {
- UObject* Outer = 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, EObjectInitializerOptions::None));
- 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("BPEnumWA-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_Direct(*JsonValue->AsString(), OutValue, nullptr, PPF_None) == nullptr)
- {
- UE_LOG(LogJson, Error, TEXT("BPEnumWA-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_Direct(*JsonValue->AsString(), OutValue, nullptr, PPF_None) == nullptr)
- {
- UE_LOG(LogJson, Error, TEXT("BPEnumWA-JsonValueToUProperty - Unable import property type %s from string value for property %s"), *Property->GetClass()->GetName(), *Property->GetNameCPP());
- return false;
- }
- }
- return true;
- }
- bool JsonValueToFPropertyWithContainer(const TSharedPtr<FJsonValue>& JsonValue, FProperty* Property, void* OutValue, const UStruct* ContainerStruct, void* Container, int64 CheckFlags, int64 SkipFlags)
- {
- if (!JsonValue.IsValid())
- {
- UE_LOG(LogJson, Error, TEXT("BPEnumWA-JsonValueToUProperty - Invalid value JSON key"));
- return false;
- }
- bool bArrayOrSetProperty = Property->IsA<FArrayProperty>() || Property->IsA<FSetProperty>();
- bool bJsonArray = JsonValue->Type == EJson::Array;
- if (!bJsonArray)
- {
- if (bArrayOrSetProperty)
- {
- //Begin custom workaround - support string -> binary array conversion
- FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Property);
- if (ArrayProperty->Inner->IsA<FByteProperty>())
- {
- //Did we get a direct binary?
- TArray<uint8> ByteArray;
- if (FJsonValueBinary::IsBinary(JsonValue))
- {
- ByteArray = FJsonValueBinary::AsBinary(JsonValue);
- }
- //it's a string, convert use base64 to bytes
- else if(JsonValue->Type == EJson::String)
- {
- bool bDidDecodeCorrectly = FBase64::Decode(JsonValue->AsString(), ByteArray);
- if (!bDidDecodeCorrectly)
- {
- UE_LOG(LogJson, Warning, TEXT("FBase64::Decode failed on %s"), *Property->GetName());
- return false;
- }
- }
- else
- {
- UE_LOG(LogJson, Error, TEXT("BPEnumWA-JsonValueToUProperty - Attempted to import TArray from unsupported non-array JSON key: %s"), *Property->GetName());
- return false;
- }
- //Memcpy raw arrays
- FScriptArrayHelper ArrayHelper(ArrayProperty, OutValue);
- ArrayHelper.EmptyAndAddUninitializedValues(ByteArray.Num());
- FGenericPlatformMemory::Memcpy(ArrayHelper.GetRawPtr(), ByteArray.GetData(), ByteArray.Num());
- return true;
- }
- //End custom workaround
- UE_LOG(LogJson, Error, TEXT("BPEnumWA-JsonValueToUProperty - Attempted to import TArray from non-array JSON key"));
- return false;
- }
- if (Property->ArrayDim != 1)
- {
- UE_LOG(LogJson, Warning, TEXT("Ignoring excess properties when deserializing %s"), *Property->GetName());
- }
- return ConvertScalarJsonValueToFPropertyWithContainer(JsonValue, Property, OutValue, ContainerStruct, Container, CheckFlags, SkipFlags);
- }
- // In practice, the ArrayDim == 1 check ought to be redundant, since nested arrays of UPropertys are not supported
- if (bArrayOrSetProperty && Property->ArrayDim == 1)
- {
- // Read into TArray
- return ConvertScalarJsonValueToFPropertyWithContainer(JsonValue, Property, OutValue, ContainerStruct, Container, CheckFlags, SkipFlags);
- }
- // We're deserializing a JSON array
- const auto& ArrayValue = JsonValue->AsArray();
- if (Property->ArrayDim < ArrayValue.Num())
- {
- UE_LOG(LogJson, Warning, TEXT("BPEnumWA-Ignoring excess properties when deserializing %s"), *Property->GetName());
- }
- // Read into native array
- int ItemsToRead = FMath::Clamp(ArrayValue.Num(), 0, Property->ArrayDim);
- for (int Index = 0; Index != ItemsToRead; ++Index)
- {
- if (!ConvertScalarJsonValueToFPropertyWithContainer(ArrayValue[Index], Property, (char*)OutValue + Index * Property->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<FProperty> PropIt(StructDefinition); PropIt; ++PropIt)
- {
- FProperty* Property = *PropIt;
- // Check to see if we should ignore this property
- if (CheckFlags != 0 && !Property->HasAnyPropertyFlags(CheckFlags))
- {
- continue;
- }
- if (Property->HasAnyPropertyFlags(SkipFlags))
- {
- continue;
- }
- // find a json value matching this property name
- const TSharedPtr<FJsonValue>* JsonValue = JsonAttributes.Find(Property->GetName());
- if (!JsonValue)
- {
- // we allow values to not be found since this mirrors the typical UObject mantra that all the fields are optional when deserializing
- continue;
- }
- if (JsonValue->IsValid() && !(*JsonValue)->IsNull())
- {
- void* Value = Property->ContainerPtrToValuePtr<uint8>(OutStruct);
- if (!JsonValueToFPropertyWithContainer(*JsonValue, Property, Value, ContainerStruct, Container, CheckFlags, SkipFlags))
- {
- UE_LOG(LogJson, Error, TEXT("BPEnumWA-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;
- }
- //End FJsonObjectConverter BPEnum Workaround
- class FJsonObjectConverterBPEnum : public FJsonObjectConverter
- {
- public:
- static bool JsonObjectToUStruct(const TSharedRef<FJsonObject>& JsonObject, const UStruct* StructDefinition, void* OutStruct, int64 CheckFlags, int64 SkipFlags)
- {
- return JsonAttributesToUStructWithContainer(JsonObject->Values, StructDefinition, OutStruct, StructDefinition, OutStruct, CheckFlags, SkipFlags);
- }
- };
- }
- FString FTrimmedKeyMap::ToString()
- {
- FString SubMapString;
- for (auto Pair : SubMap)
- {
- FString PairString = FString::Printf(TEXT("{%s:%s}"), *Pair.Key, *Pair.Value->ToString());
- SubMapString.Append(PairString);
- SubMapString.Append(",");
- }
- return FString::Printf(TEXT("{%s:%s}"), *LongKey, *SubMapString);
- }
- FString USIOJConvert::ToJsonString(const TSharedPtr<FJsonObject>& JsonObject)
- {
- FString OutputString;
- TSharedRef< FCondensedJsonStringWriter > Writer = FCondensedJsonStringWriterFactory::Create(&OutputString);
- FJsonSerializer::Serialize(JsonObject.ToSharedRef(), Writer);
- return OutputString;
- }
- FString USIOJConvert::ToJsonString(const TArray<TSharedPtr<FJsonValue>>& JsonValueArray)
- {
- FString OutputString;
- TSharedRef< FCondensedJsonStringWriter > Writer = FCondensedJsonStringWriterFactory::Create(&OutputString);
- FJsonSerializer::Serialize(JsonValueArray, Writer);
- return OutputString;
- }
- FString USIOJConvert::ToJsonString(const TSharedPtr<FJsonValue>& JsonValue)
- {
- if (JsonValue->Type == EJson::None)
- {
- return FString();
- }
- else if (JsonValue->Type == EJson::Null)
- {
- return FString();
- }
- else if (JsonValue->Type == EJson::String)
- {
- return JsonValue->AsString();
- }
- else if (JsonValue->Type == EJson::Number)
- {
- return FString::Printf(TEXT("%f"), JsonValue->AsNumber());
- }
- else if (JsonValue->Type == EJson::Boolean)
- {
- return FString::Printf(TEXT("%d"), JsonValue->AsBool());
- }
- else if (JsonValue->Type == EJson::Array)
- {
- return ToJsonString(JsonValue->AsArray());
- }
- else if (JsonValue->Type == EJson::Object)
- {
- return ToJsonString(JsonValue->AsObject());
- }
- else
- {
- return FString();
- }
- }
- USIOJsonValue* USIOJConvert::ToSIOJsonValue(const TArray<TSharedPtr<FJsonValue>>& JsonValueArray)
- {
- TArray< TSharedPtr<FJsonValue> > ValueArray;
- for (auto InVal : JsonValueArray)
- {
- ValueArray.Add(InVal);
- }
- USIOJsonValue* ResultValue = NewObject<USIOJsonValue>();
- TSharedPtr<FJsonValue> NewVal = MakeShareable(new FJsonValueArray(ValueArray));
- ResultValue->SetRootValue(NewVal);
- return ResultValue;
- }
- #if PLATFORM_WINDOWS
- #pragma endregion ToJsonValue
- #endif
- TSharedPtr<FJsonValue> USIOJConvert::JsonStringToJsonValue(const FString& JsonString)
- {
- //Null
- if (JsonString.IsEmpty())
- {
- return MakeShareable(new FJsonValueNull);
- }
- //Number
- if (JsonString.IsNumeric())
- {
- //convert to double
- return MakeShareable(new FJsonValueNumber(FCString::Atod(*JsonString)));
- }
- //Object
- if (JsonString.StartsWith(FString(TEXT("{"))))
- {
- TSharedPtr< FJsonObject > JsonObject = ToJsonObject(JsonString);
- return MakeShareable(new FJsonValueObject(JsonObject));
- }
- //Array
- if (JsonString.StartsWith(FString(TEXT("["))))
- {
- TArray < TSharedPtr<FJsonValue>> RawJsonValueArray;
- TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create(*JsonString);
- bool success = FJsonSerializer::Deserialize(Reader, RawJsonValueArray);
- if (success)
- {
- return MakeShareable(new FJsonValueArray(RawJsonValueArray));
- }
- }
- //Bool
- if (JsonString == FString("true") || JsonString == FString("false"))
- {
- bool BooleanValue = (JsonString == FString("true"));
- return MakeShareable(new FJsonValueBoolean(BooleanValue));
- }
- //String
- return MakeShareable(new FJsonValueString(JsonString));
- }
- TSharedPtr<FJsonValue> USIOJConvert::ToJsonValue(const TSharedPtr<FJsonObject>& JsonObject)
- {
- return MakeShareable(new FJsonValueObject(JsonObject));
- }
- TSharedPtr<FJsonValue> USIOJConvert::ToJsonValue(const FString& StringValue)
- {
- return MakeShareable(new FJsonValueString(StringValue));
- }
- TSharedPtr<FJsonValue> USIOJConvert::ToJsonValue(double NumberValue)
- {
- return MakeShareable(new FJsonValueNumber(NumberValue));
- }
- TSharedPtr<FJsonValue> USIOJConvert::ToJsonValue(bool BoolValue)
- {
- return MakeShareable(new FJsonValueBoolean(BoolValue));
- }
- TSharedPtr<FJsonValue> USIOJConvert::ToJsonValue(const TArray<uint8>& BinaryValue)
- {
- return MakeShareable(new FJsonValueBinary(BinaryValue));
- }
- TSharedPtr<FJsonValue> USIOJConvert::ToJsonValue(const TArray<TSharedPtr<FJsonValue>>& ArrayValue)
- {
- return MakeShareable(new FJsonValueArray(ArrayValue));
- }
- #if PLATFORM_WINDOWS
- #pragma endregion ToJsonValue
- #endif
- TArray<TSharedPtr<FJsonValue>> USIOJConvert::JsonStringToJsonArray(const FString& JsonString)
- {
- TArray < TSharedPtr<FJsonValue>> RawJsonValueArray;
- TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create(*JsonString);
- FJsonSerializer::Deserialize(Reader, RawJsonValueArray);
- return RawJsonValueArray;
- }
- TSharedPtr<FJsonObject> USIOJConvert::ToJsonObject(const FString& JsonString)
- {
- TSharedPtr< FJsonObject > JsonObject = MakeShareable(new FJsonObject);
- TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create(*JsonString);
- FJsonSerializer::Deserialize(Reader, JsonObject);
- return JsonObject;
- }
- TSharedPtr<FJsonObject> USIOJConvert::ToJsonObject(UStruct* StructDefinition, void* StructPtr, bool IsBlueprintStruct, bool BinaryStructCppSupport /*= false */)
- {
- TSharedRef<FJsonObject> JsonObject = MakeShareable(new FJsonObject);
- if (IsBlueprintStruct || BinaryStructCppSupport)
- {
- //Handle BP enum override
- if (!EnumOverrideExportCallback.IsBound())
- {
- EnumOverrideExportCallback.BindLambda([](FProperty* Property, const void* Value)
- {
- if (FByteProperty* BPEnumProperty = CastField<FByteProperty>(Property))
- {
- //Override default enum behavior by fetching display name text
- UEnum* EnumDef = BPEnumProperty->Enum;
- uint8 IntValue = *(uint8*)Value;
- //It's an enum byte
- if (EnumDef)
- {
- FString StringValue = EnumDef->GetDisplayNameTextByIndex(IntValue).ToString();
- return (TSharedPtr<FJsonValue>)MakeShared<FJsonValueString>(StringValue);
- }
- //it's a regular byte, convert to number
- else
- {
- return (TSharedPtr<FJsonValue>)MakeShared<FJsonValueNumber>(IntValue);
- }
- }
- //byte array special case
- else if (FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Property))
- {
- //is it a byte array?
- if (ArrayProperty->Inner->IsA<FByteProperty>())
- {
- FScriptArrayHelper ArrayHelper(ArrayProperty, Value);
- TArray<uint8> ByteArray(ArrayHelper.GetRawPtr(), ArrayHelper.Num());
- return USIOJConvert::ToJsonValue(ByteArray);
- }
- }
- // invalid
- return TSharedPtr<FJsonValue>();
- });
- }
- //Get the object keys
- FJsonObjectConverter::UStructToJsonObject(StructDefinition, StructPtr, JsonObject, 0, 0, &EnumOverrideExportCallback);
- //Wrap it into a value and pass it into the trimmer
- TSharedPtr<FJsonValue> JsonValue = MakeShareable(new FJsonValueObject(JsonObject));
- TrimValueKeyNames(JsonValue);
- //Return object with trimmed names
- return JsonValue->AsObject();
- }
- else
- {
- FJsonObjectConverter::UStructToJsonObject(StructDefinition, StructPtr, JsonObject, 0, 0);
- return JsonObject;
- }
- }
- TSharedPtr<FJsonObject> USIOJConvert::MakeJsonObject()
- {
- return MakeShareable(new FJsonObject);
- }
- bool USIOJConvert::JsonObjectToUStruct(TSharedPtr<FJsonObject> JsonObject, UStruct* Struct, void* StructPtr, bool IsBlueprintStruct /*= false*/, bool BinaryStructCppSupport /*= false*/)
- {
- if (IsBlueprintStruct || BinaryStructCppSupport)
- {
- //Json object we pass will have their trimmed BP names, e.g. boolKey vs boolKey_8_EDBB36654CF43866C376DE921373AF23
- //so we have to match them to the verbose versions, get a map of the names
- TSharedPtr<FTrimmedKeyMap> KeyMap = MakeShareable(new FTrimmedKeyMap);
- SetTrimmedKeyMapForStruct(KeyMap, Struct);
- //Print our keymap for debug
- //UE_LOG(LogTemp, Log, TEXT("Keymap: %s"), *KeyMap->ToString());
- //Adjust our passed in JsonObject to use the long key names
- TSharedPtr<FJsonValue> JsonValue = MakeShareable(new FJsonValueObject(JsonObject));
- ReplaceJsonValueNamesWithMap(JsonValue, KeyMap);
- /*Todo: add support for enums by pretty name and not by NewEnumeratorX
- Will require re-writing FJsonObjectConverter::JsonObjectToUStruct to lookup by display name in numeric case
- of https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Source/Runtime/JsonUtilities/Private/JsonObjectConverter.cpp#L377,
- or getting engine pull request merge.
- */
- //Use custom blueprint JsonObjectToUStruct to fix BPEnums
- return FJsonObjectConverterBPEnum::JsonObjectToUStruct(JsonObject.ToSharedRef(), Struct, StructPtr, 0, 0);
- }
- else
- {
- return FJsonObjectConverter::JsonObjectToUStruct(JsonObject.ToSharedRef(), Struct, StructPtr, 0, 0);
- }
- }
- bool USIOJConvert::JsonFileToUStruct(const FString& FilePath, UStruct* Struct, void* StructPtr, bool IsBlueprintStruct /*= false*/)
- {
- //Read bytes from file
- TArray<uint8> OutBytes;
- if (!FFileHelper::LoadFileToArray(OutBytes, *FilePath))
- {
- return false;
- }
- //Convert to json string
- FString JsonString;
- FFileHelper::BufferToString(JsonString, OutBytes.GetData(), OutBytes.Num());
- //Read into struct
- return JsonObjectToUStruct(ToJsonObject(JsonString), Struct, StructPtr, IsBlueprintStruct);
- }
- bool USIOJConvert::ToJsonFile(const FString& FilePath, UStruct* Struct, void* StructPtr, bool IsBlueprintStruct /*= false*/)
- {
- //Get json object with trimmed values
- TSharedPtr<FJsonObject> JsonObject = ToJsonObject(Struct, StructPtr, IsBlueprintStruct);
- TSharedPtr<FJsonValue> TrimmedValue = MakeShareable(new FJsonValueObject(JsonObject));
- TrimValueKeyNames(TrimmedValue);
- //Convert to string
- FString JsonString = ToJsonString(TrimmedValue);
- FTCHARToUTF8 Utf8String(*JsonString);
- TArray<uint8> Bytes;
- Bytes.Append((uint8*)Utf8String.Get(), Utf8String.Length());
- //flush to disk
- return FFileHelper::SaveArrayToFile(Bytes, *FilePath);
- }
- void USIOJConvert::TrimValueKeyNames(const TSharedPtr<FJsonValue>& JsonValue)
- {
- //Array?
- if (JsonValue->Type == EJson::Array)
- {
- auto Array = JsonValue->AsArray();
- for (auto SubValue : Array)
- {
- TrimValueKeyNames(SubValue);
- }
- }
- //Object?
- else if (JsonValue->Type == EJson::Object)
- {
- auto JsonObject = JsonValue->AsObject();
- for (auto Pair : JsonObject->Values)
- {
- const FString& Key = Pair.Key;
- FString TrimmedKey;
- bool DidNeedTrimming = TrimKey(Key, TrimmedKey);
- //keep attempting sub keys even if we have a valid string
- auto SubValue = Pair.Value;
- TrimValueKeyNames(SubValue);
- if (DidNeedTrimming)
- {
- //Replace field names with the trimmed key
- JsonObject->SetField(TrimmedKey, SubValue);
- JsonObject->RemoveField(Key);
- }
- }
- }
- else
- {
- //UE_LOG(LogTemp, Warning, TEXT("TrimValueKeyNames:: uncaught type is: %d"), (int)JsonValue->Type);
- }
- }
- bool USIOJConvert::TrimKey(const FString& InLongKey, FString& OutTrimmedKey)
- {
- //Look for the position of the 2nd '_'
- int32 LastIndex = InLongKey.Find(TEXT("_"), ESearchCase::IgnoreCase, ESearchDir::FromEnd);
- LastIndex = InLongKey.Find(TEXT("_"), ESearchCase::IgnoreCase, ESearchDir::FromEnd, LastIndex);
- if (LastIndex >= 0)
- {
- OutTrimmedKey = InLongKey.Mid(0, LastIndex);;
- return true;
- }
- else
- {
- return false;
- }
- }
- void USIOJConvert::SetTrimmedKeyMapForStruct(TSharedPtr<FTrimmedKeyMap>& InMap, UStruct* Struct)
- {
- //Get the child fields
- FField* FieldPtr = Struct->ChildProperties;
- //If it hasn't been set, the long key is the json standardized long name
- if (InMap->LongKey.IsEmpty())
- {
- InMap->LongKey = FJsonObjectConverter::StandardizeCase(Struct->GetName());
- }
- //For each child field...
- while (FieldPtr != nullptr)
- {
- //Map our trimmed name to our full name
- const FString& LowerKey = FJsonObjectConverter::StandardizeCase(FieldPtr->GetName());
- FString TrimmedKey;
- bool DidTrim = TrimKey(LowerKey, TrimmedKey);
- //Set the key map
- TSharedPtr<FTrimmedKeyMap> SubMap = MakeShareable(new FTrimmedKeyMap);
- SubMap->LongKey = LowerKey;
- //No-trim case, trim = long
- if (!DidTrim)
- {
- TrimmedKey = SubMap->LongKey;
- }
- //Did we get a substructure?
- FStructProperty* SubStruct = CastField<FStructProperty>(FieldPtr);
- FArrayProperty* ArrayProp = CastField<FArrayProperty>(FieldPtr);
- FMapProperty* MapProperty = CastField<FMapProperty>(FieldPtr);
- if (SubStruct != nullptr)
- {
- //We did, embed the sub-map
- SetTrimmedKeyMapForStruct(SubMap, SubStruct->Struct);
- }
- //Did we get a sub-array?
- else if (ArrayProp != nullptr)
- {
- //set the inner map for the inner property
- //UE_LOG(LogTemp, Log, TEXT("found array: %s"), *ArrayProp->GetName());
- SetTrimmedKeyMapForProp(SubMap, ArrayProp->Inner);
- }
- else if (MapProperty != nullptr)
- {
- //UE_LOG(LogTemp, Log, TEXT("I'm a tmap: %s"), *MapProperty->GetName());
- SetTrimmedKeyMapForProp(SubMap, MapProperty);
- }
- //Debug types
- /*
- UProperty* ObjectProp = Cast<UProperty>(FieldPtr);
- if (ObjectProp)
- {
- UE_LOG(LogTemp, Log, TEXT("found map: %s, %s, type: %s, %s"),
- *ObjectProp->GetName(),
- *ObjectProp->GetNameCPP(),
- *ObjectProp->GetClass()->GetFName().ToString(),
- *ObjectProp->GetCPPType());
- }*/
- InMap->SubMap.Add(TrimmedKey, SubMap);
- //UE_LOG(LogTemp, Log, TEXT("long: %s, trim: %s, is struct: %d"), *SubMap->LongKey, *TrimmedKey, SubStruct != NULL);
- FieldPtr = FieldPtr->Next;
- }
- //UE_LOG(LogTemp, Log, TEXT("Final map: %d"), InMap->SubMap.Num());
- }
- void USIOJConvert::SetTrimmedKeyMapForProp(TSharedPtr<FTrimmedKeyMap>& InMap, FProperty* InnerProperty)
- {
- //UE_LOG(LogTemp, Log, TEXT("got prop: %s"), *InnerProperty->GetName());
- FStructProperty* SubStruct = CastField<FStructProperty>(InnerProperty);
- FArrayProperty* ArrayProp = CastField<FArrayProperty>(InnerProperty);
- FMapProperty* MapProperty = CastField<FMapProperty>(InnerProperty);
- if (SubStruct != nullptr)
- {
- //We did, embed the sub-map
- SetTrimmedKeyMapForStruct(InMap, SubStruct->Struct);
- }
- //Did we get a sub-array?
- else if (ArrayProp != nullptr)
- {
- SetTrimmedKeyMapForProp(InMap, ArrayProp->Inner);
- }
- else if (MapProperty != nullptr)
- {
- //Make a special submap with special TMAP identifier key
- TSharedPtr<FTrimmedKeyMap> SubMap = MakeShareable(new FTrimmedKeyMap);
- SubMap->LongKey = TMAP_STRING;
- InMap->SubMap.Add(SubMap->LongKey, SubMap);
- //Take the value property and set it as it's unique child
- SetTrimmedKeyMapForProp(SubMap, MapProperty->ValueProp);
- //Each child in the JSON object map will use the same structure (it's a UE4 limitation of maps anyway
- }
- }
- void USIOJConvert::ReplaceJsonValueNamesWithMap(TSharedPtr<FJsonValue>& JsonValue, TSharedPtr<FTrimmedKeyMap> KeyMap)
- {
- if (JsonValue->Type == EJson::Object)
- {
- //Go through each key in the object
- auto Object = JsonValue->AsObject();
- auto SubMap = KeyMap->SubMap;
- auto AllValues = Object->Values;
- FString PreviewPreValue = USIOJConvert::ToJsonString(Object);
- //UE_LOG(LogTemp, Log, TEXT("Rep::PreObject: <%s>"), *PreviewPreValue);
- for (auto Pair : AllValues)
- {
- if (SubMap.Contains(TMAP_STRING))
- {
- FString TMapString = FString(TMAP_STRING);
- //If we found a tmap, replace each sub key with list of keys
- ReplaceJsonValueNamesWithMap(Pair.Value, SubMap[TMapString]);
- }
- else if (SubMap.Num() > 0 && SubMap.Contains(Pair.Key))
- {
- //Get the long key for entry
- const FString& LongKey = SubMap[Pair.Key]->LongKey;
- //loop nested structures
- ReplaceJsonValueNamesWithMap(Pair.Value, SubMap[Pair.Key]);
- if (Pair.Key != LongKey)
- {
- //finally set the field and remove the old field
- Object->SetField(LongKey, Pair.Value);
- Object->RemoveField(Pair.Key);
- }
- }
- }
- FString PreviewPostValue = USIOJConvert::ToJsonString(Object);
- //UE_LOG(LogTemp, Log, TEXT("Rep::PostObject: <%s>"), *PreviewPostValue);
- }
- else if (JsonValue->Type == EJson::Array)
- {
- auto Array = JsonValue->AsArray();
- for (auto Item : Array)
- {
- //UE_LOG(LogTemp, Log, TEXT("%s"), *Item->AsString());
- ReplaceJsonValueNamesWithMap(Item, KeyMap);
- }
- }
- }
|