2026-03-18 10:17:58 -04:00
|
|
|
#include "WingProperty.h"
|
|
|
|
|
#include "WingUtils.h"
|
2026-03-26 19:16:59 -04:00
|
|
|
#include "WingHandler.h"
|
2026-03-18 10:17:58 -04:00
|
|
|
#include "WingServer.h"
|
|
|
|
|
#include "WingTypes.h"
|
2026-03-15 22:24:10 -04:00
|
|
|
#include "Engine/Blueprint.h"
|
2026-03-19 16:26:00 -04:00
|
|
|
#include "Engine/SCS_Node.h"
|
2026-03-13 05:34:19 -04:00
|
|
|
#include "MaterialGraph/MaterialGraphNode.h"
|
2026-03-23 14:10:26 -04:00
|
|
|
#include "Components/Widget.h"
|
|
|
|
|
#include "Components/PanelSlot.h"
|
2026-03-15 22:24:10 -04:00
|
|
|
#include "EdGraph/EdGraphPin.h"
|
2026-03-16 19:22:59 -04:00
|
|
|
#include "UObject/EnumProperty.h"
|
2026-03-26 19:16:59 -04:00
|
|
|
#include "Dom/JsonValue.h"
|
|
|
|
|
|
2026-03-15 22:24:10 -04:00
|
|
|
|
|
|
|
|
static bool IsPinTypeProperty(FProperty* Prop)
|
|
|
|
|
{
|
|
|
|
|
FStructProperty* StructProp = CastField<FStructProperty>(Prop);
|
|
|
|
|
return StructProp && StructProp->Struct == FEdGraphPinType::StaticStruct();
|
|
|
|
|
}
|
2026-03-13 03:30:14 -04:00
|
|
|
|
2026-03-18 20:08:50 -04:00
|
|
|
FWingProperty::FWingProperty(FProperty* InProp, void* InContainer)
|
2026-03-13 05:34:19 -04:00
|
|
|
: Prop(InProp), Container(InContainer) {}
|
2026-03-13 03:30:14 -04:00
|
|
|
|
2026-03-18 20:08:50 -04:00
|
|
|
FWingProperty::FWingProperty(FProperty* InProp, UObject* InContainer)
|
|
|
|
|
: Prop(InProp), Container(static_cast<void*>(InContainer)) {}
|
|
|
|
|
|
2026-03-19 15:53:25 -04:00
|
|
|
FString FWingProperty::GetCategory()
|
|
|
|
|
{
|
|
|
|
|
FString Result = Prop->GetMetaData(TEXT("Category"));
|
|
|
|
|
if (Result.IsEmpty()) Result = "Unclassified";
|
|
|
|
|
return Result;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-18 20:08:50 -04:00
|
|
|
FString FWingProperty::GetText() const
|
2026-03-13 03:30:14 -04:00
|
|
|
{
|
2026-03-15 22:24:10 -04:00
|
|
|
if (IsPinTypeProperty(Prop))
|
2026-03-23 00:18:12 -04:00
|
|
|
{
|
|
|
|
|
FEdGraphPinType PinType;
|
|
|
|
|
Prop->GetValue_InContainer(Container, &PinType);
|
|
|
|
|
return UWingTypes::TypeToText(PinType);
|
|
|
|
|
}
|
2026-03-15 22:24:10 -04:00
|
|
|
FString Result;
|
2026-03-23 00:18:12 -04:00
|
|
|
Prop->ExportTextItem_InContainer(Result, Container, nullptr, nullptr, PPF_None);
|
2026-03-13 03:30:14 -04:00
|
|
|
return Result;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-19 15:53:25 -04:00
|
|
|
FString FWingProperty::GetTruncatedText(int32 MaxLen) const
|
|
|
|
|
{
|
|
|
|
|
FString Result = GetText();
|
|
|
|
|
for (int i = 0; i < Result.Len(); i++)
|
|
|
|
|
if (Result[i] == '\n') Result[i] = ' ';
|
|
|
|
|
if (Result.Len() > MaxLen)
|
|
|
|
|
Result = Result.Left(MaxLen) + TEXT("...");
|
|
|
|
|
return Result;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 19:16:59 -04:00
|
|
|
void FWingProperty::PrintExpectsReceived(const TCHAR *Type)
|
|
|
|
|
{
|
|
|
|
|
UWingServer::Printf(TEXT("ERROR: '%s' received a %s, but expects %s\n"),
|
|
|
|
|
*WingUtils::FormatName(Prop), Type, *Prop->GetCPPType());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FWingProperty::SetText(FString Value)
|
2026-03-13 03:30:14 -04:00
|
|
|
{
|
2026-03-26 19:16:59 -04:00
|
|
|
// Mostly, this is implemented by Unreal's ImportText_Incontainer.
|
|
|
|
|
// We override it for a few very specific types.
|
|
|
|
|
|
2026-03-18 20:08:50 -04:00
|
|
|
// Notify that we're modifying the containing object.
|
|
|
|
|
if (Prop->GetOwnerClass())
|
|
|
|
|
UWingServer::AddTouchedObject(static_cast<UObject*>(Container));
|
|
|
|
|
|
2026-03-18 10:17:58 -04:00
|
|
|
// Pin types get parsed by UWingTypes.
|
2026-03-15 22:24:10 -04:00
|
|
|
if (IsPinTypeProperty(Prop))
|
2026-03-23 00:18:12 -04:00
|
|
|
{
|
|
|
|
|
FEdGraphPinType PinType;
|
2026-03-25 02:32:26 -04:00
|
|
|
UWingTypes::Requirements Req;
|
|
|
|
|
Req.BlueprintType = true;
|
|
|
|
|
Req.Blueprintable = false;
|
|
|
|
|
Req.AllowContainer = true;
|
|
|
|
|
if (!UWingTypes::TextToType(Value, PinType, Req)) return false;
|
2026-03-23 00:18:12 -04:00
|
|
|
Prop->SetValue_InContainer(Container, &PinType);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2026-03-16 19:22:59 -04:00
|
|
|
|
2026-03-26 19:16:59 -04:00
|
|
|
// If it's an enum type, use our parsing routine which is smarter about
|
|
|
|
|
// prefixes than ImportText. We canonicalize the string, and then send
|
|
|
|
|
// it onward to ImportText.
|
|
|
|
|
UEnum *Enum = nullptr;
|
2026-03-16 19:22:59 -04:00
|
|
|
if (FByteProperty* ByteProp = CastField<FByteProperty>(Prop))
|
2026-03-26 19:16:59 -04:00
|
|
|
Enum = ByteProp->Enum;
|
2026-03-16 19:22:59 -04:00
|
|
|
if (FEnumProperty* EnumProp = CastField<FEnumProperty>(Prop))
|
2026-03-26 19:16:59 -04:00
|
|
|
Enum = EnumProp->GetEnum();
|
|
|
|
|
if (Enum != nullptr)
|
2026-03-16 19:22:59 -04:00
|
|
|
{
|
|
|
|
|
int64 EnumValue;
|
2026-03-26 19:16:59 -04:00
|
|
|
if (!WingUtils::StringToEnum(Enum, Value, EnumValue)) return false;
|
|
|
|
|
Value = Enum->GetNameStringByValue(EnumValue);
|
2026-03-16 19:22:59 -04:00
|
|
|
}
|
|
|
|
|
|
2026-03-26 19:16:59 -04:00
|
|
|
// Now Use ImportText
|
2026-03-23 00:18:12 -04:00
|
|
|
const TCHAR* Result = Prop->ImportText_InContainer(*Value, Container, nullptr, PPF_None);
|
2026-03-26 19:16:59 -04:00
|
|
|
if ((!Result) || (*Result != 0))
|
2026-03-13 03:30:14 -04:00
|
|
|
{
|
2026-03-18 10:17:58 -04:00
|
|
|
UWingServer::Printf(TEXT("ERROR: Failed to parse '%s' for property '%s' (type: %s)\n"),
|
|
|
|
|
*Value, *WingUtils::FormatName(Prop), *Prop->GetCPPType());
|
2026-03-13 03:30:14 -04:00
|
|
|
return false;
|
|
|
|
|
}
|
2026-03-16 19:22:59 -04:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 19:16:59 -04:00
|
|
|
bool FWingProperty::SetJson(const TSharedPtr<FJsonValue> &JsonValue)
|
|
|
|
|
{
|
|
|
|
|
if (JsonValue->Type == EJson::String)
|
|
|
|
|
{
|
|
|
|
|
return SetText(JsonValue->AsString());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (JsonValue->Type == EJson::Number)
|
|
|
|
|
{
|
|
|
|
|
// If the property is float or double, just store the value.
|
|
|
|
|
double D = JsonValue->AsNumber();
|
|
|
|
|
if (FFloatProperty* FloatProp = CastField<FFloatProperty>(Prop))
|
|
|
|
|
{
|
|
|
|
|
float Value = (float)D;
|
|
|
|
|
Prop->SetValue_InContainer(Container, &Value);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
else if (FDoubleProperty* DoubleProp = CastField<FDoubleProperty>(Prop))
|
|
|
|
|
{
|
|
|
|
|
double Value = (double)D;
|
|
|
|
|
Prop->SetValue_InContainer(Container, &Value);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// At this point, we've ruled out it being a float or double property.
|
|
|
|
|
// All that's left is integers. Verify that the number can be converted
|
|
|
|
|
// losslessly to an int64, and then do so.
|
|
|
|
|
if (FMath::Floor(D) != D)
|
|
|
|
|
{
|
|
|
|
|
PrintExpectsReceived(TEXT("float"));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (FMath::Abs(D) > (double)((1LL)<<53))
|
|
|
|
|
{
|
|
|
|
|
UWingServer::Printf(TEXT("ERROR: To store very large numbers in '%s', please pass a json string\n"),
|
|
|
|
|
*WingUtils::FormatName(Prop));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
int64 I = (int64)D;
|
|
|
|
|
|
|
|
|
|
// Now store the integer. Make sure it fits first.
|
|
|
|
|
if (FNumericProperty *NumericProperty = CastField<FNumericProperty>(Prop))
|
|
|
|
|
{
|
|
|
|
|
uint8 buffer[16];
|
|
|
|
|
NumericProperty->SetIntPropertyValue(buffer, I);
|
|
|
|
|
if (NumericProperty->GetSignedIntPropertyValue(buffer) != I)
|
|
|
|
|
{
|
|
|
|
|
UWingServer::Printf(TEXT("ERROR: Property '%s' of type %s is too small to hold %lld\n"),
|
|
|
|
|
*WingUtils::FormatName(Prop), *Prop->GetCPPType(), I);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
NumericProperty->SetValue_InContainer(Container, buffer);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
PrintExpectsReceived(TEXT("integer"));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (JsonValue->Type == EJson::Boolean)
|
|
|
|
|
{
|
|
|
|
|
if (FBoolProperty* BoolProp = CastField<FBoolProperty>(Prop))
|
|
|
|
|
{
|
|
|
|
|
bool Value = JsonValue->AsBool();
|
|
|
|
|
Prop->SetValue_InContainer(Container, &Value);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
PrintExpectsReceived(TEXT("boolean"));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (JsonValue->Type == EJson::Object)
|
|
|
|
|
{
|
|
|
|
|
void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(Container);
|
|
|
|
|
FStructProperty* StructProp = CastField<FStructProperty>(Prop);
|
|
|
|
|
if (StructProp && (StructProp->Struct == FWingJsonObject::StaticStruct()))
|
|
|
|
|
{
|
|
|
|
|
static_cast<FWingJsonObject*>(ValuePtr)->Json = JsonValue->AsObject();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
PrintExpectsReceived(TEXT("json object"));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (JsonValue->Type == EJson::Array)
|
|
|
|
|
{
|
|
|
|
|
void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(Container);
|
|
|
|
|
FStructProperty* StructProp = CastField<FStructProperty>(Prop);
|
|
|
|
|
if (StructProp && (StructProp->Struct == FWingJsonArray::StaticStruct()))
|
|
|
|
|
{
|
|
|
|
|
static_cast<FWingJsonArray*>(ValuePtr)->Array = JsonValue->AsArray();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
PrintExpectsReceived(TEXT("json array"));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PrintExpectsReceived(TEXT("Unrecognized Json Data"));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-18 20:08:50 -04:00
|
|
|
void FWingProperty::Collect(UStruct* StructType, void* Container, TArray<FWingProperty> &Props, EPropertyFlags Flags)
|
2026-03-16 19:22:59 -04:00
|
|
|
{
|
2026-03-19 15:53:25 -04:00
|
|
|
TMap<FString, TArray<FWingProperty>> Grouped;
|
|
|
|
|
|
2026-03-18 20:08:50 -04:00
|
|
|
for (TFieldIterator<FProperty> It(StructType); It; ++It)
|
2026-03-13 05:34:19 -04:00
|
|
|
{
|
2026-03-18 20:08:50 -04:00
|
|
|
if (Flags != 0 && !It->HasAnyPropertyFlags(Flags)) continue;
|
2026-03-19 15:53:25 -04:00
|
|
|
FString SortCat = *It->GetMetaData(TEXT("Category"));
|
|
|
|
|
Grouped.FindOrAdd(SortCat).Add(FWingProperty(*It, Container));
|
2026-03-13 05:34:19 -04:00
|
|
|
}
|
2026-03-19 15:53:25 -04:00
|
|
|
TArray<FString> Categories;
|
2026-03-13 05:34:19 -04:00
|
|
|
|
2026-03-19 15:53:25 -04:00
|
|
|
Grouped.GetKeys(Categories);
|
|
|
|
|
Categories.Sort([](const FString& A, const FString& B) {
|
|
|
|
|
if (A.IsEmpty()) return false;
|
|
|
|
|
if (B.IsEmpty()) return true;
|
|
|
|
|
return A < B;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
for (const FString& Category : Categories)
|
2026-03-13 05:34:19 -04:00
|
|
|
{
|
2026-03-19 15:53:25 -04:00
|
|
|
Props.Append(Grouped[Category]);
|
2026-03-14 01:31:06 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-18 20:08:50 -04:00
|
|
|
void FWingProperty::Remove(TArray<FWingProperty>& Props, const FString& Name)
|
2026-03-15 22:24:10 -04:00
|
|
|
{
|
2026-03-18 20:08:50 -04:00
|
|
|
Props.RemoveAll([&](const FWingProperty& P) { return P.Prop->GetName() == Name; });
|
2026-03-15 22:24:10 -04:00
|
|
|
}
|
|
|
|
|
|
2026-03-20 19:40:29 -04:00
|
|
|
void FWingProperty::Move(TArray<FWingProperty> &Out, TArray<FWingProperty> &In, const FString &Name)
|
|
|
|
|
{
|
|
|
|
|
int Dst = 0;
|
|
|
|
|
for (int i = 0; i < In.Num(); i++)
|
|
|
|
|
{
|
|
|
|
|
if (In[i]->GetName() == Name)
|
|
|
|
|
{
|
|
|
|
|
Out.Add(In[i]);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
In[Dst++] = In[i];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
In.SetNum(Dst);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-18 20:08:50 -04:00
|
|
|
TArray<FWingProperty> FWingProperty::GetAll(UObject* Obj, EPropertyFlags Flags)
|
2026-03-14 01:31:06 -04:00
|
|
|
{
|
|
|
|
|
if (!Obj) return {};
|
|
|
|
|
|
|
|
|
|
// Blueprints don't have editable properties. So
|
|
|
|
|
// instead, we fetch properties from the generated CDO,
|
|
|
|
|
// which is probably what the user intended.
|
|
|
|
|
//
|
|
|
|
|
if (UBlueprint *BP = ::Cast<UBlueprint>(Obj))
|
|
|
|
|
{
|
|
|
|
|
if (BP->GeneratedClass == nullptr)
|
|
|
|
|
{
|
2026-03-18 10:17:58 -04:00
|
|
|
UWingServer::Printf(TEXT("ERROR: Blueprint '%s' has no GeneratedClass\n"), *Obj->GetName());
|
2026-03-14 01:31:06 -04:00
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
Obj = BP->GeneratedClass->GetDefaultObject();
|
2026-03-13 05:34:19 -04:00
|
|
|
}
|
2026-03-14 01:31:06 -04:00
|
|
|
|
2026-03-19 16:26:00 -04:00
|
|
|
// SCS nodes don't have useful editable properties.
|
|
|
|
|
// Redirect to the component template instead.
|
|
|
|
|
//
|
|
|
|
|
if (USCS_Node* Node = ::Cast<USCS_Node>(Obj))
|
|
|
|
|
{
|
|
|
|
|
if (!Node->ComponentTemplate)
|
|
|
|
|
{
|
|
|
|
|
UWingServer::Printf(TEXT("ERROR: SCS node '%s' has no component template\n"), *Obj->GetName());
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
Obj = Node->ComponentTemplate;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-19 15:53:25 -04:00
|
|
|
TArray<FWingProperty> Result;
|
|
|
|
|
Collect(Obj->GetClass(), Obj, Result, Flags);
|
2026-03-14 01:31:06 -04:00
|
|
|
|
|
|
|
|
// If it's a Material Graph node, also collect properties from
|
|
|
|
|
// the associated material expression.
|
|
|
|
|
//
|
2026-03-13 05:34:19 -04:00
|
|
|
if (UMaterialGraphNode* MatNode = Cast<UMaterialGraphNode>(Obj))
|
|
|
|
|
{
|
|
|
|
|
if (UMaterialExpression* Expr = MatNode->MaterialExpression)
|
|
|
|
|
{
|
2026-03-19 15:53:25 -04:00
|
|
|
Collect(Expr->GetClass(), Expr, Result, Flags);
|
2026-03-13 05:34:19 -04:00
|
|
|
}
|
|
|
|
|
}
|
2026-03-19 15:53:25 -04:00
|
|
|
|
2026-03-23 14:10:26 -04:00
|
|
|
// If it's a Widget, hide the slot property, and add the
|
|
|
|
|
// slot properties.
|
|
|
|
|
if (UWidget* Widget = Cast<UWidget>(Obj))
|
|
|
|
|
{
|
|
|
|
|
FWingProperty::Remove(Result, TEXT("Slot"));
|
|
|
|
|
if (UPanelSlot* Slot = Widget->Slot)
|
|
|
|
|
{
|
|
|
|
|
Collect(Slot->GetClass(), Slot, Result, Flags);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-13 05:34:19 -04:00
|
|
|
return Result;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-18 20:08:50 -04:00
|
|
|
TArray<FWingProperty> FWingProperty::GetAll(UStruct* StructType, void* Container, EPropertyFlags Flags)
|
2026-03-15 22:24:10 -04:00
|
|
|
{
|
2026-03-18 20:08:50 -04:00
|
|
|
TArray<FWingProperty> Result;
|
2026-03-15 22:24:10 -04:00
|
|
|
Collect(StructType, Container, Result, Flags);
|
|
|
|
|
return Result;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-18 20:08:50 -04:00
|
|
|
TArray<FWingProperty> FWingProperty::FindAllSubstring(const TArray<FWingProperty>& Props, const FString& Substring)
|
2026-03-13 05:34:19 -04:00
|
|
|
{
|
2026-03-17 11:38:25 -04:00
|
|
|
if (Substring.IsEmpty()) return Props;
|
2026-03-18 20:08:50 -04:00
|
|
|
TArray<FWingProperty> Result;
|
|
|
|
|
for (const FWingProperty& P : Props)
|
2026-03-13 05:34:19 -04:00
|
|
|
{
|
2026-03-18 10:17:58 -04:00
|
|
|
if (WingUtils::FormatName(P.Prop).Contains(Substring, ESearchCase::IgnoreCase))
|
2026-03-13 05:34:19 -04:00
|
|
|
Result.Add(P);
|
|
|
|
|
}
|
|
|
|
|
return Result;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-18 20:08:50 -04:00
|
|
|
FWingProperty FWingProperty::FindOneExactMatch(const TArray<FWingProperty>& Props, const FString& Name)
|
2026-03-13 05:34:19 -04:00
|
|
|
{
|
2026-03-18 20:08:50 -04:00
|
|
|
TArray<FWingProperty> Matches;
|
|
|
|
|
for (const FWingProperty& P : Props)
|
2026-03-13 05:34:19 -04:00
|
|
|
{
|
2026-03-18 10:17:58 -04:00
|
|
|
if (WingUtils::Identifies(Name, P.Prop))
|
2026-03-17 11:38:25 -04:00
|
|
|
Matches.Add(P);
|
2026-03-13 05:34:19 -04:00
|
|
|
}
|
|
|
|
|
if (Matches.Num() == 0)
|
|
|
|
|
{
|
2026-03-18 10:17:58 -04:00
|
|
|
UWingServer::Printf(TEXT("ERROR: Property '%s' not found\n"), *Name);
|
2026-03-18 20:08:50 -04:00
|
|
|
return FWingProperty();
|
2026-03-13 05:34:19 -04:00
|
|
|
}
|
|
|
|
|
if (Matches.Num() > 1)
|
|
|
|
|
{
|
2026-03-18 10:17:58 -04:00
|
|
|
UWingServer::Printf(TEXT("ERROR: Ambiguous property '%s'\n"), *Name);
|
2026-03-18 20:08:50 -04:00
|
|
|
return FWingProperty();
|
2026-03-13 05:34:19 -04:00
|
|
|
}
|
|
|
|
|
return Matches[0];
|
|
|
|
|
}
|
2026-03-26 19:16:59 -04:00
|
|
|
|
|
|
|
|
bool FWingProperty::PopulateFromJson(FWingProperty& P, const FJsonObject* Json, bool AllOptional)
|
|
|
|
|
{
|
|
|
|
|
FString JsonKey = WingUtils::FormatName(P.Prop);
|
|
|
|
|
TSharedPtr<FJsonValue> Value = Json->TryGetField(JsonKey);
|
|
|
|
|
|
|
|
|
|
if (Value == nullptr)
|
|
|
|
|
{
|
|
|
|
|
bool Optional = AllOptional || P.Prop->HasMetaData(TEXT("Optional"));
|
|
|
|
|
if (Optional) return true;
|
|
|
|
|
UWingServer::Printf(TEXT("ERROR: Missing required parameter '%s'\n"), *JsonKey);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return P.SetJson(Value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FWingProperty::PopulateFromJson(
|
|
|
|
|
TArray<FWingProperty>& Props, const FJsonObject* Json, bool AllOptional)
|
|
|
|
|
{
|
|
|
|
|
bool Ok = true;
|
|
|
|
|
|
|
|
|
|
// Build a set of known property names for the unknown-field check.
|
|
|
|
|
TSet<FString> KnownKeys;
|
|
|
|
|
for (const FWingProperty& P : Props)
|
|
|
|
|
KnownKeys.Add(WingUtils::FormatName(P.Prop));
|
|
|
|
|
|
|
|
|
|
// Check for unknown fields in the JSON
|
|
|
|
|
for (const auto& KV : Json->Values)
|
|
|
|
|
{
|
|
|
|
|
if (!KnownKeys.Contains(KV.Key))
|
|
|
|
|
{
|
|
|
|
|
UWingServer::Printf(TEXT("ERROR: Unknown parameter '%s'\n"), *KV.Key);
|
|
|
|
|
Ok = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Populate each property from JSON
|
|
|
|
|
for (FWingProperty& P : Props)
|
|
|
|
|
{
|
|
|
|
|
if (!PopulateFromJson(P, Json, AllOptional)) Ok = false;
|
|
|
|
|
}
|
|
|
|
|
return Ok;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FWingProperty::PopulateFromJson(UStruct* StructType, void* Container, const FJsonObject* Object)
|
|
|
|
|
{
|
|
|
|
|
TArray<FWingProperty> Props = FWingProperty::GetAll(StructType, Container, (EPropertyFlags)0);
|
|
|
|
|
return PopulateFromJson(Props, Object);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FWingProperty::PopulateFromJson(UStruct* StructType, void* Container, const TSharedPtr<FJsonValue>& Object)
|
|
|
|
|
{
|
|
|
|
|
if (!Object.IsValid() || (Object->Type != EJson::Object))
|
|
|
|
|
{
|
|
|
|
|
UWingServer::Print(TEXT("ERROR: Expected a JSON object\n"));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return PopulateFromJson(StructType, Container, Object->AsObject().Get());
|
|
|
|
|
}
|