// Copyright Epic Games, Inc. All Rights Reserved. #include "EditorConfig.h" #include "Containers/Array.h" #include "Containers/Map.h" #include "Containers/UnrealString.h" #include "Dom/JsonObject.h" #include "Dom/JsonValue.h" #include "HAL/PlatformCrt.h" #include "Internationalization/Text.h" #include "Misc/CoreMiscDefines.h" #include "Serialization/JsonTypes.h" #include "UObject/Class.h" #include "UObject/EnumProperty.h" #include "UObject/Field.h" #include "UObject/NameTypes.h" #include "UObject/PropertyPortFlags.h" #include "UObject/TextProperty.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)); } bool FEditorConfig::TryGetRootUObject(const UClass* Class, UObject* OutValue, EPropertyFilter Filter) const { if (!IsValid()) { return false; } TSharedPtr UObjectData = JsonConfig->GetRootObject(); ReadUObject(UObjectData, Class, OutValue, Filter); return true; } bool FEditorConfig::TryGetRootStruct(const UStruct* Struct, void* OutValue, EPropertyFilter Filter) const { if (!IsValid()) { return false; } TSharedPtr StructData = JsonConfig->GetRootObject(); ReadStruct(StructData, Struct, OutValue, nullptr, Filter); return true; } void FEditorConfig::SetRootUObject(const UClass* Class, const UObject* Instance, EPropertyFilter Filter) { if (!IsValid()) { return; } TSharedPtr JsonObject = WriteUObject(Class, Instance, Filter); JsonConfig->SetRootObject(JsonObject); SetDirty(); } void FEditorConfig::SetRootStruct(const UStruct* Struct, const void* Instance, EPropertyFilter Filter) { if (!IsValid()) { return; } TSharedPtr JsonObject = WriteStruct(Struct, Instance, nullptr, Filter); JsonConfig->SetRootObject(JsonObject); SetDirty(); } void FEditorConfig::ReadStruct(const TSharedPtr& JsonObject, const UStruct* Struct, void* Instance, UObject* Owner, EPropertyFilter Filter) { FString TypeName; JsonObject->TryGetStringField(TEXT("$type"), TypeName); if (!TypeName.IsEmpty() && !ensureAlwaysMsgf(Struct->GetName().Equals(TypeName), TEXT("Type name mismatch in FEditorConfig::ReadUObject. Expected: %s, Actual: %s"), *Struct->GetName(), *TypeName)) { return; } for (TFieldIterator It(Struct); It; ++It) { const FProperty* Property = *It; if (Filter == EPropertyFilter::MetadataOnly && !Property->HasMetaData("EditorConfig")) { continue; } void* DataPtr = Property->ContainerPtrToValuePtr(Instance); TSharedPtr Value = JsonObject->TryGetField(Property->GetName()); if (Value.IsValid()) { ReadValue(Value, Property, DataPtr, Owner); } } } void FEditorConfig::ReadUObject(const TSharedPtr& JsonObject, const UClass* Class, UObject* Instance, EPropertyFilter Filter) { FString TypeName; JsonObject->TryGetStringField(TEXT("$type"), TypeName); if (!TypeName.IsEmpty() && !ensureAlwaysMsgf(Class->GetName().Equals(TypeName), TEXT("Type name mismatch in FEditorConfig::ReadUObject. Expected: %s, Actual: %s"), *Class->GetName(), *TypeName)) { return; } for (TFieldIterator It(Class); It; ++It) { const FProperty* Property = *It; if (Filter == EPropertyFilter::MetadataOnly && !Property->HasMetaData("EditorConfig")) { continue; } void* DataPtr = Property->ContainerPtrToValuePtr(Instance); TSharedPtr Value = JsonObject->TryGetField(Property->GetName()); if (Value.IsValid()) { ReadValue(Value, Property, DataPtr, Instance); } } } void FEditorConfig::ReadValue(const TSharedPtr& JsonValue, const FProperty* Property, void* DataPtr, UObject* Owner) { if (const FStrProperty* StrProperty = CastField(Property)) { FString* Value = (FString*) DataPtr; JsonValue->TryGetString(*Value); return; } else if (const FNameProperty* NameProperty = CastField(Property)) { FString TempValue; JsonValue->TryGetString(TempValue); *(FName*) DataPtr = *TempValue; return; } else if (const FTextProperty* TextProperty = CastField(Property)) { FString TempValue; JsonValue->TryGetString(TempValue); *(FText*) DataPtr = FText::FromString(TempValue); return; } else if (const FBoolProperty* BoolProperty = CastField(Property)) { bool Value = BoolProperty->GetDefaultPropertyValue(); if (JsonValue->TryGetBool(Value)) { BoolProperty->SetPropertyValue(DataPtr, Value); } return; } else if (const FFloatProperty* FloatProperty = CastField(Property)) { float* Value = (float*) DataPtr; JsonValue->TryGetNumber(*Value); return; } else if (const FDoubleProperty* DoubleProperty = CastField(Property)) { double* Value = (double*) DataPtr; JsonValue->TryGetNumber(*Value); return; } else if (const FInt8Property* Int8Property = CastField(Property)) { int8* Value = (int8*) DataPtr; JsonValue->TryGetNumber(*Value); return; } else if (const FInt16Property* Int16Property = CastField(Property)) { int16* Value = (int16*) DataPtr; JsonValue->TryGetNumber(*Value); return; } else if (const FIntProperty* Int32Property = CastField(Property)) { int32* Value = (int32*) DataPtr; JsonValue->TryGetNumber(*Value); return; } else if (const FInt64Property* Int64Property = CastField(Property)) { int64* Value = (int64*) DataPtr; JsonValue->TryGetNumber(*Value); return; } else if (const FByteProperty* ByteProperty = CastField(Property)) { uint8* Value = (uint8*) DataPtr; JsonValue->TryGetNumber(*Value); return; } else if (const FUInt16Property* Uint16Property = CastField(Property)) { uint16* Value = (uint16*) DataPtr; JsonValue->TryGetNumber(*Value); return; } else if (const FUInt32Property* Uint32Property = CastField(Property)) { uint32* Value = (uint32*) DataPtr; JsonValue->TryGetNumber(*Value); return; } else if (const FUInt64Property* Uint64Property = CastField(Property)) { uint64* Value = (uint64*) DataPtr; JsonValue->TryGetNumber(*Value); return; } else if (const FEnumProperty* EnumProperty = CastField(Property)) { UEnum* Enum = EnumProperty->GetEnum(); FString ValueString; if (JsonValue->TryGetString(ValueString)) { int64 Index = Enum->GetIndexByNameString(ValueString); if (Index != INDEX_NONE) { int64 Value = Enum->GetValueByIndex(Index); EnumProperty->GetUnderlyingProperty()->SetIntPropertyValue(DataPtr, Value); } } return; } else if (const FObjectPropertyBase* ObjectProperty = CastField(Property)) { FString PathString; if (JsonValue->TryGetString(PathString)) { Property->ImportText_Direct(*PathString, DataPtr, Owner, 0); } return; } else if (const FStructProperty* StructProperty = CastField(Property)) { const TSharedPtr* ObjectJsonValue; if (JsonValue->TryGetObject(ObjectJsonValue)) { ReadStruct(*ObjectJsonValue, StructProperty->Struct, DataPtr, Owner, EPropertyFilter::All); } else if (JsonValue->Type == EJson::String) { FString ImportTextString; if (JsonValue->TryGetString(ImportTextString)) { Property->ImportText_Direct(*ImportTextString, DataPtr, Owner, 0); } } return; } 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); } } return; } 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()); } } return; } 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_Direct(*JsonPair.Key, TempKey.GetData(), Owner, 0); 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; } } ensureAlwaysMsgf(false, TEXT("Property type is unsupported: %s, type: %s"), *Property->GetPathName(), *Property->GetClass()->GetName()); } TSharedPtr FEditorConfig::WriteArray(const FArrayProperty* ArrayProperty, const void* DataPtr) { FProperty* InnerProperty = ArrayProperty->Inner; FScriptArrayHelper ArrayHelper(ArrayProperty, DataPtr); TArray> JsonValuesArray; JsonValuesArray.Reserve(ArrayHelper.Num()); for (int32 Idx = 0; Idx < ArrayHelper.Num(); ++Idx) { if (ArrayHelper.IsValidIndex(Idx)) { TSharedPtr ElementValue = WriteValue(InnerProperty, ArrayHelper.GetRawPtr(Idx), nullptr); check(ElementValue.IsValid()); JsonValuesArray.Add(ElementValue); } } return MakeShared(JsonValuesArray); } TSharedPtr FEditorConfig::WriteSet(const FSetProperty* SetProperty, const void* DataPtr) { FProperty* InnerProperty = SetProperty->ElementProp; FScriptSetHelper SetHelper(SetProperty, DataPtr); TArray> JsonValuesArray; JsonValuesArray.Reserve(SetHelper.Num()); for (FScriptSetHelper::FIterator It(SetHelper); It; ++It) { TSharedPtr ElementValue = WriteValue(InnerProperty, SetHelper.GetElementPtr(It), nullptr); check(ElementValue.IsValid()); JsonValuesArray.Add(ElementValue); } return MakeShared(JsonValuesArray); } TSharedPtr FEditorConfig::WriteMap(const FMapProperty* MapProperty, const void* DataPtr) { TSharedPtr ResultValue; 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 (FScriptMapHelper::FIterator It(MapHelper); It; ++It) { TSharedPtr JsonKey = WriteValue(KeyProperty, MapHelper.GetKeyPtr(It), nullptr); check(JsonKey.IsValid()); JsonKeysArray.Add(JsonKey); TSharedPtr JsonValue = WriteValue(ValueProperty, MapHelper.GetValuePtr(It), nullptr); check(JsonValue.IsValid()); JsonValuesArray.Add(JsonValue); } // maps can either be stored as $key, $value pairs or, if the keys can be stringified, as a JSON object // check Filter 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) { if (MapHelper.IsValidIndex(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) { if (MapHelper.IsValidIndex(Idx)) { FString KeyString; const bool bGetStringSuccess = JsonKeysArray[Idx]->TryGetString(KeyString); check(bGetStringSuccess); ResultObject->SetField(KeyString, JsonValuesArray[Idx]); } } ResultValue = MakeShared(ResultObject); } ensureMsgf(ResultValue.IsValid(), TEXT("Map key type is invalid.")); } return ResultValue; } TSharedPtr FEditorConfig::WriteValue(const FProperty* Property, const void* DataPtr, const void* DefaultPtr) { TSharedPtr ResultValue; if (DefaultPtr != nullptr && Property->Identical(DataPtr, DefaultPtr)) { return 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)) { UEnum* Enum = EnumProperty->GetEnum(); int64 Value = EnumProperty->GetUnderlyingProperty()->GetSignedIntPropertyValue(DataPtr); FName ValueName = Enum->GetNameByValue(Value); ResultValue = MakeShared(ValueName.ToString()); } else if (const FObjectPropertyBase* ObjectProperty = CastField(Property)) { FString ObjectPath; ObjectProperty->ExportTextItem_Direct(ObjectPath, DataPtr, nullptr, nullptr, PPF_None, nullptr); ResultValue = MakeShared(ObjectPath); } else if (const FStructProperty* StructProperty = CastField(Property)) { ResultValue = MakeShared(WriteStruct(StructProperty->Struct, DataPtr, DefaultPtr, EPropertyFilter::All)); } else if (const FArrayProperty* ArrayProperty = CastField(Property)) { ResultValue = WriteArray(ArrayProperty, DataPtr); } else if (const FSetProperty* SetProperty = CastField(Property)) { ResultValue = WriteSet(SetProperty, DataPtr); } else if (const FMapProperty* MapProperty = CastField(Property)) { ResultValue = WriteMap(MapProperty, DataPtr); } ensureAlwaysMsgf(ResultValue.IsValid(), TEXT("Property type is unsupported: %s, type: %s"), *Property->GetPathName(), *Property->GetClass()->GetName()); return ResultValue; } TSharedPtr FEditorConfig::WriteStruct(const UStruct* Struct, const void* Instance, const void* Defaults, EPropertyFilter Filter) { TSharedPtr JsonObject = MakeShared(); JsonObject->SetStringField(TEXT("$type"), Struct->GetName()); // Default initialize a struct here if we weren't passed one. // This is necessary because structs can contain default initializations, eg.: // struct Foo { float Bar = 5.0f; } // The Bar FProperty does not store this value itself, only the struct does. TArray TempDefaults; if (Defaults == nullptr) { TempDefaults.AddZeroed(Struct->GetStructureSize()); Struct->InitializeStruct(TempDefaults.GetData()); Defaults = TempDefaults.GetData(); } bool bAnyWritten = false; for (TFieldIterator It(Struct); It; ++It) { const FProperty* Property = *It; if (Filter == EPropertyFilter::MetadataOnly && !Property->HasMetaData("EditorConfig")) { continue; } bAnyWritten = true; const void* ValuePtr = Property->ContainerPtrToValuePtr(Instance); const void* PropertyDefaultPtr = Property->ContainerPtrToValuePtr(Defaults); TSharedPtr PropertyValue = WriteValue(Property, ValuePtr, PropertyDefaultPtr); if (PropertyValue.IsValid()) { JsonObject->SetField(Property->GetName(), PropertyValue); } } ensureAlwaysMsgf(bAnyWritten, TEXT("Struct type has no properties to serialize: %s"), *Struct->GetName()); return JsonObject; } /** * This exists because of sparse class data that can exist for UObjects only, which is handled in ContainerPtrToValuePtr. */ TSharedPtr FEditorConfig::WriteUObject(const UClass* Class, const UObject* Instance, EPropertyFilter Filter) { TSharedPtr JsonObject = MakeShared(); JsonObject->SetStringField(TEXT("$type"), Class->GetName()); const UObject* Defaults = Class->GetDefaultObject(); bool bAnyWritten = false; for (TFieldIterator It(Class); It; ++It) { const FProperty* Property = *It; if (Filter == EPropertyFilter::MetadataOnly && !Property->HasMetaData("EditorConfig")) { continue; } bAnyWritten = true; const void* ValuePtr = Property->ContainerPtrToValuePtr(Instance); const void* PropertyDefaultPtr = Property->ContainerPtrToValuePtr(Defaults); TSharedPtr PropertyValue = WriteValue(Property, ValuePtr, PropertyDefaultPtr); if (PropertyValue.IsValid()) { JsonObject->SetField(Property->GetName(), PropertyValue); } } ensureAlwaysMsgf(bAnyWritten, TEXT("UObject type has no properties to serialize: %s"), *Class->GetName()); return JsonObject; } void FEditorConfig::SetDirty() { if (!Dirty) { Dirty = true; EditorConfigDirtiedEvent.Broadcast(*this); } } void FEditorConfig::OnSaved() { Dirty = false; }