// Copyright Epic Games, Inc. All Rights Reserved. #include "EditorConfig.h" #include "UObject/UnrealType.h" FEditorConfig::FEditorConfig() { JsonConfig = MakeShared(); } void FEditorConfig::SetParent(TSharedPtr InConfig) { ParentConfig = InConfig; if (ParentConfig.IsValid()) { JsonConfig->SetParent(ParentConfig->JsonConfig); } else { JsonConfig->SetParent(TSharedPtr()); } } bool FEditorConfig::LoadFromFile(FStringView FilePath) { JsonConfig = MakeShared(); if (!JsonConfig->LoadFromFile(FilePath)) { return false; } if (ParentConfig.IsValid()) { JsonConfig->SetParent(ParentConfig->JsonConfig); } return true; } bool FEditorConfig::LoadFromString(FStringView Content) { JsonConfig = MakeShared(); if (!JsonConfig->LoadFromString(Content)) { return false; } if (ParentConfig.IsValid()) { JsonConfig->SetParent(ParentConfig->JsonConfig); } return true; } bool FEditorConfig::SaveToString(FString& OutResult) const { if (!IsValid()) { return false; } return JsonConfig->SaveToString(OutResult); } bool FEditorConfig::SaveToFile(FStringView FilePath) const { if (!IsValid()) { return false; } return JsonConfig->SaveToFile(FilePath); } bool FEditorConfig::HasOverride(FStringView Key) const { return JsonConfig->HasOverride(UE::FJsonPath(Key)); } void FEditorConfig::ReadStruct(TSharedPtr JsonObject, const UStruct* Struct, void* Instance, UObject* Owner) { FString TypeName; JsonObject->TryGetStringField(TEXT("$type"), TypeName); if (!ensureAlways(Struct->GetName() != TypeName)) { return; } for (TFieldIterator It(Struct); It; ++It) { FProperty* Property = *It; if (Property != nullptr) { void* DataPtr = Property->ContainerPtrToValuePtr(Instance); TSharedPtr Value = JsonObject->TryGetField(Property->GetName()); if (Value.IsValid()) { ReadValue(Value, Property, DataPtr, Owner); } } } } void FEditorConfig::ReadUObject(TSharedPtr JsonObject, const UClass* Class, UObject* Instance) { FString TypeName; JsonObject->TryGetStringField(TEXT("$type"), TypeName); if (!ensureAlways(Class->GetName() != TypeName)) { return; } for (TFieldIterator It(Class); It; ++It) { FProperty* Property = *It; if (Property != nullptr) { void* DataPtr = Property->ContainerPtrToValuePtr(Instance); TSharedPtr Value = JsonObject->TryGetField(Property->GetName()); if (Value.IsValid()) { ReadValue(Value, Property, DataPtr, Instance); } } } } void FEditorConfig::ReadValue(TSharedPtr JsonValue, const FProperty* Property, void* DataPtr, UObject* Owner) { if (const FStrProperty* StrProperty = CastField(Property)) { FString* Value = (FString*) DataPtr; JsonValue->TryGetString(*Value); } else if (const FNameProperty* NameProperty = CastField(Property)) { FString TempValue; JsonValue->TryGetString(TempValue); *(FName*) DataPtr = *TempValue; } else if (const FTextProperty* TextProperty = CastField(Property)) { FString TempValue; JsonValue->TryGetString(TempValue); *(FText*) DataPtr = FText::FromString(TempValue); } else if (const FBoolProperty* BoolProperty = CastField(Property)) { bool Value = BoolProperty->GetDefaultPropertyValue(); if (JsonValue->TryGetBool(Value)) { BoolProperty->SetPropertyValue(DataPtr, Value); } } else if (const FFloatProperty* FloatProperty = CastField(Property)) { float* Value = (float*) DataPtr; JsonValue->TryGetNumber(*Value); } else if (const FDoubleProperty* DoubleProperty = CastField(Property)) { double* Value = (double*) DataPtr; JsonValue->TryGetNumber(*Value); } else if (const FInt8Property* Int8Property = CastField(Property)) { int8* Value = (int8*) DataPtr; JsonValue->TryGetNumber(*Value); } else if (const FInt16Property* Int16Property = CastField(Property)) { int16* Value = (int16*) DataPtr; JsonValue->TryGetNumber(*Value); } else if (const FIntProperty* Int32Property = CastField(Property)) { int32* Value = (int32*) DataPtr; JsonValue->TryGetNumber(*Value); } else if (const FInt64Property* Int64Property = CastField(Property)) { int64* Value = (int64*) DataPtr; JsonValue->TryGetNumber(*Value); } else if (const FByteProperty* ByteProperty = CastField(Property)) { uint8* Value = (uint8*) DataPtr; JsonValue->TryGetNumber(*Value); } else if (const FUInt16Property* Uint16Property = CastField(Property)) { uint16* Value = (uint16*) DataPtr; JsonValue->TryGetNumber(*Value); } else if (const FUInt32Property* Uint32Property = CastField(Property)) { uint32* Value = (uint32*) DataPtr; JsonValue->TryGetNumber(*Value); } else if (const FUInt64Property* Uint64Property = CastField(Property)) { uint64* Value = (uint64*) DataPtr; JsonValue->TryGetNumber(*Value); } else if (const FEnumProperty* EnumProperty = CastField(Property)) { int64* Value = (int64*) DataPtr; UEnum* Enum = EnumProperty->GetEnum(); if (Enum != nullptr) { FString ValueString; if (JsonValue->TryGetString(ValueString)) { int64 Index = Enum->GetIndexByNameString(ValueString); if (Index != INDEX_NONE) { *Value = Enum->GetValueByIndex(Index); } } } } else if (const FObjectPropertyBase* ObjectProperty = CastField(Property)) { FString PathString; if (JsonValue->TryGetString(PathString)) { Property->ImportText(*PathString, DataPtr, 0, Owner); } } else if (const FStructProperty* StructProperty = CastField(Property)) { const TSharedPtr* ObjectJsonValue; if (JsonValue->TryGetObject(ObjectJsonValue)) { ReadStruct(*ObjectJsonValue, StructProperty->Struct, DataPtr, Owner); } } else if (const FArrayProperty* ArrayProperty = CastField(Property)) { const TArray>* ArrayJsonValue; if (JsonValue->TryGetArray(ArrayJsonValue)) { FProperty* InnerProperty = ArrayProperty->Inner; FScriptArrayHelper ArrayHelper(ArrayProperty, DataPtr); ArrayHelper.EmptyAndAddValues(ArrayJsonValue->Num()); for (int32 Idx = 0; Idx < ArrayHelper.Num(); ++Idx) { TSharedPtr Value = (*ArrayJsonValue)[Idx]; ReadValue(Value, InnerProperty, ArrayHelper.GetRawPtr(Idx), Owner); } } } else if (const FSetProperty* SetProperty = CastField(Property)) { const TArray>* SetJsonValue; if (JsonValue->TryGetArray(SetJsonValue)) { const FProperty* InnerProperty = SetProperty->ElementProp; FScriptSetHelper SetHelper(SetProperty, DataPtr); SetHelper.EmptyElements(SetJsonValue->Num()); // temporary buffer to read elements into TArray TempBuffer; TempBuffer.AddUninitialized(InnerProperty->ElementSize); for (int32 Idx = 0; Idx < SetJsonValue->Num(); ++Idx) { InnerProperty->InitializeValue(TempBuffer.GetData()); TSharedPtr Value = (*SetJsonValue)[Idx]; ReadValue(Value, InnerProperty, TempBuffer.GetData(), Owner); SetHelper.AddElement(TempBuffer.GetData()); InnerProperty->DestroyValue(TempBuffer.GetData()); } } } else if (const FMapProperty* MapProperty = CastField(Property)) { const FProperty* KeyProperty = MapProperty->KeyProp; const FProperty* ValueProperty = MapProperty->ValueProp; FScriptMapHelper MapHelper(MapProperty, DataPtr); // maps can either be stored as a simple JSON object, or as an array of { $key, $value } pairs // first check for object storage - this will cover eg. numbers and strings as keys const TSharedPtr* JsonObjectValue; if (JsonValue->TryGetObject(JsonObjectValue)) { MapHelper.EmptyValues((*JsonObjectValue)->Values.Num()); // temporary buffers to read elements into TArray TempKey; TempKey.AddZeroed(KeyProperty->ElementSize); TArray TempValue; TempValue.AddZeroed(ValueProperty->ElementSize); for (const TPair>& JsonPair : (*JsonObjectValue)->Values) { KeyProperty->InitializeValue(TempKey.GetData()); KeyProperty->ImportText(*JsonPair.Key, TempKey.GetData(), 0, Owner); ValueProperty->InitializeValue(TempValue.GetData()); ReadValue(JsonPair.Value, ValueProperty, TempValue.GetData(), Owner); MapHelper.AddPair(TempKey.GetData(), TempValue.GetData()); KeyProperty->DestroyValue(TempKey.GetData()); ValueProperty->DestroyValue(TempValue.GetData()); } return; } // then check for array storage, this will cover complex keys eg. custom structs const TArray>* JsonArrayPtr = nullptr; if (JsonValue->TryGetArray(JsonArrayPtr)) { MapHelper.EmptyValues(JsonArrayPtr->Num()); // temporary buffers to read elements into TArray TempKey; TempKey.AddUninitialized(KeyProperty->ElementSize); TArray TempValue; TempValue.AddUninitialized(ValueProperty->ElementSize); for (const TSharedPtr& JsonElement : *JsonArrayPtr) { TSharedPtr* JsonObject = nullptr; if (JsonElement->TryGetObject(JsonObject)) { TSharedPtr JsonKeyField = (*JsonObject)->TryGetField(TEXT("$key")); TSharedPtr JsonValueField = (*JsonObject)->TryGetField(TEXT("$value")); if (JsonKeyField.IsValid() && JsonValueField.IsValid()) { KeyProperty->InitializeValue(TempKey.GetData()); ReadValue(JsonKeyField, KeyProperty, TempKey.GetData(), Owner); ValueProperty->InitializeValue(TempValue.GetData()); ReadValue(JsonValueField, ValueProperty, TempValue.GetData(), Owner); MapHelper.AddPair(TempKey.GetData(), TempValue.GetData()); KeyProperty->DestroyValue(TempKey.GetData()); ValueProperty->DestroyValue(TempValue.GetData()); } } } return; } } } TSharedPtr FEditorConfig::WriteValue(const FProperty* Property, const void* DataPtr) { TSharedPtr ResultValue; if (const FStrProperty* StrProperty = CastField(Property)) { FString* Value = (FString*) DataPtr; ResultValue = MakeShared(*Value); } else if (const FNameProperty* NameProperty = CastField(Property)) { FName* Value = (FName*) DataPtr; ResultValue = MakeShared(Value->ToString()); } else if (const FTextProperty* TextProperty = CastField(Property)) { FText* Value = (FText*) DataPtr; ResultValue = MakeShared(Value->ToString()); } else if (const FBoolProperty* BoolProperty = CastField(Property)) { bool Value = BoolProperty->GetPropertyValue(DataPtr); ResultValue = MakeShared(Value); } else if (const FFloatProperty* FloatProperty = CastField(Property)) { float* Value = (float*) DataPtr; ResultValue = MakeShared(*Value); } else if (const FDoubleProperty* DoubleProperty = CastField(Property)) { double* Value = (double*) DataPtr; ResultValue = MakeShared(*Value); } else if (const FInt8Property* Int8Property = CastField(Property)) { int8* Value = (int8*) DataPtr; ResultValue = MakeShared(*Value); } else if (const FInt16Property* Int16Property = CastField(Property)) { int16* Value = (int16*) DataPtr; ResultValue = MakeShared(*Value); } else if (const FIntProperty* Int32Property = CastField(Property)) { int32* Value = (int32*) DataPtr; ResultValue = MakeShared(*Value); } else if (const FInt64Property* Int64Property = CastField(Property)) { int64* Value = (int64*) DataPtr; ResultValue = MakeShared(*Value); } else if (const FByteProperty* ByteProperty = CastField(Property)) { uint8* Value = (uint8*) DataPtr; ResultValue = MakeShared(*Value); } else if (const FUInt16Property* Uint16Property = CastField(Property)) { uint16* Value = (uint16*) DataPtr; ResultValue = MakeShared(*Value); } else if (const FUInt32Property* Uint32Property = CastField(Property)) { uint32* Value = (uint32*) DataPtr; ResultValue = MakeShared(*Value); } else if (const FUInt64Property* Uint64Property = CastField(Property)) { uint64* Value = (uint64*) DataPtr; ResultValue = MakeShared(*Value); } else if (const FEnumProperty* EnumProperty = CastField(Property)) { int64* Value = (int64*) DataPtr; UEnum* Enum = EnumProperty->GetEnum(); FName ValueName = Enum->GetNameByValue(*Value); ResultValue = MakeShared(ValueName.ToString()); } else if (const FObjectPropertyBase* ObjectProperty = CastField(Property)) { FString ObjectPath; // TODO: No idea how we're meant to get object paths, might be this: //ObjectProperty->ExportTextItem(ObjectPath, DataPtr, ??? /*DefaultValue*/, ??? /*Parent*/, 0, ??? /*ExportRootScope*/); ResultValue = MakeShared(ObjectPath); } else if (const FStructProperty* StructProperty = CastField(Property)) { ResultValue = MakeShared(WriteStruct(StructProperty->Struct, DataPtr)); } else if (const FArrayProperty* ArrayProperty = CastField(Property)) { FProperty* InnerProperty = ArrayProperty->Inner; FScriptArrayHelper ArrayHelper(ArrayProperty, DataPtr); TArray> JsonValuesArray; JsonValuesArray.Reserve(ArrayHelper.Num()); for (int32 Idx = 0; Idx < ArrayHelper.Num(); ++Idx) { TSharedPtr ElementValue = WriteValue(InnerProperty, ArrayHelper.GetRawPtr(Idx)); JsonValuesArray.Add(ElementValue); } ResultValue = MakeShared(JsonValuesArray); } else if (const FSetProperty* SetProperty = CastField(Property)) { FProperty* InnerProperty = SetProperty->ElementProp; FScriptSetHelper SetHelper(SetProperty, DataPtr); TArray> JsonValuesArray; JsonValuesArray.Reserve(SetHelper.Num()); for (int32 Idx = 0; Idx < SetHelper.Num(); ++Idx) { if (SetHelper.IsValidIndex(Idx)) { TSharedPtr ElementValue = WriteValue(InnerProperty, SetHelper.GetElementPtr(Idx)); JsonValuesArray.Add(ElementValue); } } ResultValue = MakeShared(JsonValuesArray); } else if (const FMapProperty* MapProperty = CastField(Property)) { FProperty* KeyProperty = MapProperty->KeyProp; FProperty* ValueProperty = MapProperty->ValueProp; FScriptMapHelper MapHelper(MapProperty, DataPtr); if (MapHelper.Num() == 0) { ResultValue = MakeShared(MakeShared()); } else { TArray> JsonKeysArray; JsonKeysArray.Reserve(MapHelper.Num()); TArray> JsonValuesArray; JsonValuesArray.Reserve(MapHelper.Num()); for (int32 Idx = 0; Idx < MapHelper.Num(); ++Idx) { TSharedPtr JsonKey = WriteValue(KeyProperty, MapHelper.GetKeyPtr(Idx)); JsonKeysArray.Add(JsonKey); TSharedPtr JsonValue = WriteValue(ValueProperty, MapHelper.GetValuePtr(Idx)); JsonValuesArray.Add(JsonValue); } // maps can either be stored as $key, $value pairs or, if the keys can be stringified, as a JSON object // check which we should use based on the first element EJson KeyType = JsonKeysArray[0]->Type; if (KeyType == EJson::Object) { TArray> ResultArray; ResultArray.Reserve(MapHelper.Num()); for (int32 Idx = 0; Idx < MapHelper.Num(); ++Idx) { TSharedPtr ElementObject = MakeShared(); ElementObject->SetField(TEXT("$key"), JsonKeysArray[Idx]); ElementObject->SetField(TEXT("$value"), JsonValuesArray[Idx]); ResultArray.Add(MakeShared(ElementObject)); } ResultValue = MakeShared(ResultArray); } else if (KeyType == EJson::Boolean || KeyType == EJson::Number || KeyType == EJson::String) { TSharedPtr ResultObject = MakeShared(); for (int32 Idx = 0; Idx < MapHelper.Num(); ++Idx) { FString KeyString; check(JsonKeysArray[Idx]->TryGetString(KeyString)); ResultObject->SetField(KeyString, JsonValuesArray[Idx]); } ResultValue = MakeShared(ResultObject); } ensureMsgf(ResultValue.IsValid(), TEXT("Map key type is invalid.")); } } ensureMsgf(ResultValue.IsValid(), TEXT("Property type is unsupported.")); return ResultValue; } bool FEditorConfig::IsDefault(const FProperty* Property, TSharedPtr JsonValue, const void* NativeValue) { if (JsonValue->Type == EJson::Array) { return JsonValue->AsArray().Num() == 0; } else if (JsonValue->Type == EJson::Object) { return JsonValue->AsObject()->Values.Num() == 0; } else { // have the property initialize some temp storage, then check against that uint8 Temp[256]; Property->InitializeValue(Temp); return Property->Identical(NativeValue, Temp); } return false; } TSharedPtr FEditorConfig::WriteStruct(const UStruct* Struct, const void* Instance) { TSharedPtr JsonObject = MakeShared(); for (TFieldIterator It(Struct); It; ++It) { const FProperty* Property = *It; const void* ValuePtr = Property->ContainerPtrToValuePtr(Instance); TSharedPtr PropertyValue = WriteValue(Property, ValuePtr); if (!IsDefault(Property, PropertyValue, ValuePtr)) { JsonObject->SetField(Property->GetName(), PropertyValue); } } return JsonObject; } /** * This exists because of sparse class data that can exist for UObjects only, and which is handled in ContainerPtrToValuePtr. */ TSharedPtr FEditorConfig::WriteUObject(const UStruct* Struct, const UObject* Instance) { TSharedPtr JsonObject = MakeShared(); for (TFieldIterator It(Struct); It; ++It) { const FProperty* Property = *It; const void* ValuePtr = Property->ContainerPtrToValuePtr(Instance); TSharedPtr PropertyValue = WriteValue(Property, ValuePtr); if (!IsDefault(Property, PropertyValue, ValuePtr)) { JsonObject->SetField(Property->GetName(), PropertyValue); } } return JsonObject; }