From 1ee2067505623db9d4a6075288332294d56b35af Mon Sep 17 00:00:00 2001 From: jyelon Date: Thu, 26 Mar 2026 19:16:59 -0400 Subject: [PATCH] Better handling of JSON property setters --- .../Handlers/BlueprintVariable_Create.h | 1 - .../Handlers/BlueprintVariable_Modify.h | 1 - .../Handlers/EventDispatcher_Create.h | 1 - .../UEWingman/Handlers/GraphNode_Create.h | 4 +- .../Handlers/GraphNode_SetDefaults.h | 3 +- .../Handlers/GraphNode_SetPositions.h | 3 +- .../UEWingman/Handlers/GraphPin_Connect.h | 4 +- .../UEWingman/Handlers/GraphPin_Disconnect.h | 4 +- .../Source/UEWingman/Handlers/Property_Set.h | 22 +- .../Source/UEWingman/Handlers/ShowCommands.h | 2 +- .../UEWingman/Private/WingBlueprintVar.cpp | 3 +- .../Source/UEWingman/Private/WingJson.cpp | 132 ----------- .../Source/UEWingman/Private/WingProperty.cpp | 207 ++++++++++++++++-- .../Source/UEWingman/Private/WingServer.cpp | 4 +- .../Source/UEWingman/Private/WingUtils.cpp | 2 +- .../Source/UEWingman/Public/WingJson.h | 17 -- .../Source/UEWingman/Public/WingProperty.h | 9 +- 17 files changed, 211 insertions(+), 208 deletions(-) delete mode 100644 Plugins/UEWingman/Source/UEWingman/Private/WingJson.cpp delete mode 100644 Plugins/UEWingman/Source/UEWingman/Public/WingJson.h diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintVariable_Create.h b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintVariable_Create.h index 73532a1c..2654a53b 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintVariable_Create.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintVariable_Create.h @@ -4,7 +4,6 @@ #include "WingServer.h" #include "WingHandler.h" #include "WingFetcher.h" -#include "WingJson.h" #include "WingProperty.h" #include "WingBlueprintVar.h" #include "WingUtils.h" diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintVariable_Modify.h b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintVariable_Modify.h index 38ed771a..7773f266 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintVariable_Modify.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintVariable_Modify.h @@ -4,7 +4,6 @@ #include "WingServer.h" #include "WingHandler.h" #include "WingFetcher.h" -#include "WingJson.h" #include "WingProperty.h" #include "WingBlueprintVar.h" #include "WingUtils.h" diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/EventDispatcher_Create.h b/Plugins/UEWingman/Source/UEWingman/Handlers/EventDispatcher_Create.h index ceb911e5..e70d585c 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/EventDispatcher_Create.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/EventDispatcher_Create.h @@ -4,7 +4,6 @@ #include "WingServer.h" #include "WingHandler.h" #include "WingFetcher.h" -#include "WingJson.h" #include "WingFunctionArgs.h" #include "WingUtils.h" #include "Engine/Blueprint.h" diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/GraphNode_Create.h b/Plugins/UEWingman/Source/UEWingman/Handlers/GraphNode_Create.h index 41857514..3c8dd0b3 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/GraphNode_Create.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/GraphNode_Create.h @@ -4,7 +4,7 @@ #include "WingServer.h" #include "WingHandler.h" #include "WingFetcher.h" -#include "WingJson.h" +#include "WingProperty.h" #include "WingUtils.h" #include "WingGraphActions.h" #include "EdGraph/EdGraph.h" @@ -64,7 +64,7 @@ public: for (const TSharedPtr& NodeVal : Nodes.Array) { FSpawnNodeEntry Entry; - if (!WingJson::PopulateFromJson(FSpawnNodeEntry::StaticStruct(), &Entry, NodeVal)) + if (!FWingProperty::PopulateFromJson(FSpawnNodeEntry::StaticStruct(), &Entry, NodeVal)) continue; // Find the action by exact full name diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/GraphNode_SetDefaults.h b/Plugins/UEWingman/Source/UEWingman/Handlers/GraphNode_SetDefaults.h index a85286c9..d34603dc 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/GraphNode_SetDefaults.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/GraphNode_SetDefaults.h @@ -5,7 +5,6 @@ #include "WingServer.h" #include "WingFetcher.h" #include "WingProperty.h" -#include "WingJson.h" #include "WingUtils.h" #include "EdGraph/EdGraphPin.h" #include "EdGraphSchema_K2.h" @@ -124,7 +123,7 @@ public: for (const TSharedPtr& PinVal : Pins.Array) { FSetNodeDefaultEntry Entry; - if (!WingJson::PopulateFromJson(FSetNodeDefaultEntry::StaticStruct(), &Entry, PinVal)) + if (!FWingProperty::PopulateFromJson(FSetNodeDefaultEntry::StaticStruct(), &Entry, PinVal)) continue; if (K2Schema) diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/GraphNode_SetPositions.h b/Plugins/UEWingman/Source/UEWingman/Handlers/GraphNode_SetPositions.h index bd191dd0..164a6ed6 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/GraphNode_SetPositions.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/GraphNode_SetPositions.h @@ -4,7 +4,6 @@ #include "WingServer.h" #include "WingHandler.h" #include "WingFetcher.h" -#include "WingJson.h" #include "WingUtils.h" #include "Engine/Blueprint.h" #include "EdGraph/EdGraphNode.h" @@ -59,7 +58,7 @@ public: for (const TSharedPtr& NodeVal : Nodes.Array) { FMoveNodeEntry Entry; - if (!WingJson::PopulateFromJson(FMoveNodeEntry::StaticStruct(), &Entry, NodeVal)) continue; + if (!FWingProperty::PopulateFromJson(FMoveNodeEntry::StaticStruct(), &Entry, NodeVal)) continue; WingFetcher FN(TargetGraph); UEdGraphNode* Node = FN.Node(Entry.Node).Cast(); diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/GraphPin_Connect.h b/Plugins/UEWingman/Source/UEWingman/Handlers/GraphPin_Connect.h index ea802708..89bc618c 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/GraphPin_Connect.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/GraphPin_Connect.h @@ -4,7 +4,7 @@ #include "WingServer.h" #include "WingHandler.h" #include "WingFetcher.h" -#include "WingJson.h" +#include "WingProperty.h" #include "WingUtils.h" #include "Engine/Blueprint.h" #include "EdGraph/EdGraph.h" @@ -59,7 +59,7 @@ public: for (const TSharedPtr& ConnVal : Connections.Array) { FConnectPinsEntry Entry; - if (!WingJson::PopulateFromJson(FConnectPinsEntry::StaticStruct(), &Entry, ConnVal)) + if (!FWingProperty::PopulateFromJson(FConnectPinsEntry::StaticStruct(), &Entry, ConnVal)) continue; WingFetcher FS(G); diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/GraphPin_Disconnect.h b/Plugins/UEWingman/Source/UEWingman/Handlers/GraphPin_Disconnect.h index 83baba64..db641d88 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/GraphPin_Disconnect.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/GraphPin_Disconnect.h @@ -4,7 +4,7 @@ #include "WingServer.h" #include "WingHandler.h" #include "WingFetcher.h" -#include "WingJson.h" +#include "WingProperty.h" #include "WingUtils.h" #include "Engine/Blueprint.h" #include "EdGraph/EdGraph.h" @@ -59,7 +59,7 @@ public: for (const TSharedPtr& DiscVal : Disconnections.Array) { FDisconnectPinEntry Entry; - if (!WingJson::PopulateFromJson(FDisconnectPinEntry::StaticStruct(), &Entry, DiscVal)) continue; + if (!FWingProperty::PopulateFromJson(FDisconnectPinEntry::StaticStruct(), &Entry, DiscVal)) continue; WingFetcher FP(G); UEdGraphPin* Pin = FP.Walk(Entry.Pin).Cast(); diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Set.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Set.h index e0ba1b59..799d6e3a 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Set.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Set.h @@ -42,31 +42,21 @@ public: UWingServer::Print(TEXT("Error: No properties specified\n")); return; } + TArray All = FWingProperty::GetAll(Obj, CPF_Edit); + int SuccessCount = 0; // Validation pass — resolve all properties and values before modifying anything. - TArray All = FWingProperty::GetAll(Obj, CPF_Edit); - TArray> Resolved; for (const auto& Pair : Properties.Json->Values) { FWingProperty P = FWingProperty::FindOneExactMatch(All, Pair.Key); if (!P) return; - - FString ValueStr; - if (!Pair.Value->TryGetString(ValueStr)) - { - UWingServer::Printf(TEXT("Error: Value for '%s' must be a string\n"), *Pair.Key); - return; - } - Resolved.Emplace(P, ValueStr); } - // Apply all changes. - int32 SuccessCount = 0; - for (auto& [P, ValueStr] : Resolved) + // Assignment Pass - store the values. + for (const auto& Pair : Properties.Json->Values) { - if (!P.SetText(ValueStr)) - continue; - SuccessCount++; + FWingProperty P = FWingProperty::FindOneExactMatch(All, Pair.Key); + if (P.SetJson(Pair.Value)) SuccessCount++; } UWingServer::Printf(TEXT("Set %d/%d properties.\n"), SuccessCount, Properties.Json->Values.Num()); diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/ShowCommands.h b/Plugins/UEWingman/Source/UEWingman/Handlers/ShowCommands.h index 3aff0e63..434b1fca 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/ShowCommands.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/ShowCommands.h @@ -5,7 +5,7 @@ #include "WingFetcher.h" #include "WingServer.h" #include "WingTypes.h" -#include "WingJson.h" +#include "WingProperty.h" #include "WingManual.h" #include "ShowCommands.generated.h" diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingBlueprintVar.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingBlueprintVar.cpp index 904865ba..f4b2ac5c 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingBlueprintVar.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingBlueprintVar.cpp @@ -1,5 +1,4 @@ #include "WingBlueprintVar.h" -#include "WingJson.h" #include "WingServer.h" #include "WingTypes.h" #include "WingUtils.h" @@ -57,7 +56,7 @@ bool FWingBlueprintVar::ApplyJson(const FJsonObject* Json) LoadFlags(); TArray Props = MergedProperties(); - if (!WingJson::PopulateFromJson(Props, Json, true)) + if (!FWingProperty::PopulateFromJson(Props, Json, true)) return false; SaveFlags(); diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingJson.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingJson.cpp deleted file mode 100644 index 5afe9faa..00000000 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingJson.cpp +++ /dev/null @@ -1,132 +0,0 @@ -#include "WingJson.h" -#include "WingTypes.h" -#include "WingServer.h" -#include "UObject/UnrealType.h" -#include "UObject/EnumProperty.h" -#include "Dom/JsonValue.h" - - -bool WingJson::PopulateFromJson(FWingProperty& P, const FJsonObject* Json, bool AllOptional) -{ - FString JsonKey = P.Prop->GetName(); - bool bOptional = AllOptional || P.Prop->HasMetaData(TEXT("Optional")); - - if (!Json->HasField(JsonKey)) - { - if (!bOptional) - { - UWingServer::Printf(TEXT("ERROR: Missing required parameter '%s'\n"), *JsonKey); - return false; - } - return true; - } - - void* ValuePtr = P.Prop->ContainerPtrToValuePtr(P.Container); - - // Special handling for FWingJsonObject and FWingJsonArray - if (FStructProperty* StructProp = CastField(P.Prop)) - { - if (StructProp->Struct == FWingJsonObject::StaticStruct()) - { - if (!Json->HasTypedField(JsonKey)) - { - UWingServer::Printf(TEXT("ERROR: '%s' must be an object\n"), *JsonKey); - return false; - } - static_cast(ValuePtr)->Json = Json->GetObjectField(JsonKey); - return true; - } - - if (StructProp->Struct == FWingJsonArray::StaticStruct()) - { - if (!Json->HasTypedField(JsonKey)) - { - UWingServer::Printf(TEXT("ERROR: '%s' must be an array\n"), *JsonKey); - return false; - } - static_cast(ValuePtr)->Array = Json->GetArrayField(JsonKey); - return true; - } - } - - // Handle based on JSON value type. - TSharedPtr JsonValue = Json->TryGetField(JsonKey); - - if (JsonValue->Type == EJson::Number) - { - double D = JsonValue->AsNumber(); - if (FIntProperty* IntProp = CastField(P.Prop)) - { IntProp->SetPropertyValue(ValuePtr, (int32)D); return true; } - if (FFloatProperty* FloatProp = CastField(P.Prop)) - { FloatProp->SetPropertyValue(ValuePtr, (float)D); return true; } - if (FDoubleProperty* DoubleProp = CastField(P.Prop)) - { DoubleProp->SetPropertyValue(ValuePtr, D); return true; } - if (FByteProperty* ByteProp = CastField(P.Prop)) - { ByteProp->SetPropertyValue(ValuePtr, (uint8)D); return true; } - UWingServer::Printf(TEXT("ERROR: '%s' received a number but expects %s\n"), *JsonKey, *P.Prop->GetCPPType()); - return false; - } - - if (JsonValue->Type == EJson::Boolean) - { - if (FBoolProperty* BoolProp = CastField(P.Prop)) - { BoolProp->SetPropertyValue(ValuePtr, JsonValue->AsBool()); return true; } - UWingServer::Printf(TEXT("ERROR: '%s' received a boolean but expects %s\n"), *JsonKey, *P.Prop->GetCPPType()); - return false; - } - - if (JsonValue->Type == EJson::String) - { - return P.SetText(JsonValue->AsString()); - } - - UWingServer::Printf(TEXT("ERROR: '%s' must be a string, number, or boolean\n"), *JsonKey); - return false; -} - -bool WingJson::PopulateFromJson( - TArray& Props, const FJsonObject* Json, bool AllOptional) -{ - bool Ok = true; - - // Build a set of known property names for the unknown-field check. - TSet KnownKeys; - for (const FWingProperty& P : Props) - KnownKeys.Add(P.Prop->GetName()); - - // 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 WingJson::PopulateFromJson( - UStruct* StructType, void* Container, const FJsonObject* Json) -{ - TArray Props = FWingProperty::GetAll(StructType, Container, (EPropertyFlags)0); - return PopulateFromJson(Props, Json); -} - -bool WingJson::PopulateFromJson( - UStruct* StructType, void* Container, - const TSharedPtr& JsonValue) -{ - if (!JsonValue.IsValid() || (JsonValue->Type != EJson::Object)) - { - UWingServer::Print(TEXT("ERROR: Expected a JSON object\n")); - return false; - } - return PopulateFromJson(StructType, Container, JsonValue->AsObject().Get()); -} diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingProperty.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingProperty.cpp index 85e012c0..3dfe12a9 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingProperty.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingProperty.cpp @@ -1,5 +1,6 @@ #include "WingProperty.h" #include "WingUtils.h" +#include "WingHandler.h" #include "WingServer.h" #include "WingTypes.h" #include "Engine/Blueprint.h" @@ -9,6 +10,8 @@ #include "Components/PanelSlot.h" #include "EdGraph/EdGraphPin.h" #include "UObject/EnumProperty.h" +#include "Dom/JsonValue.h" + static bool IsPinTypeProperty(FProperty* Prop) { @@ -52,8 +55,17 @@ FString FWingProperty::GetTruncatedText(int32 MaxLen) const return Result; } -bool FWingProperty::SetText(const FString &Value) +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) +{ + // Mostly, this is implemented by Unreal's ImportText_Incontainer. + // We override it for a few very specific types. + // Notify that we're modifying the containing object. if (Prop->GetOwnerClass()) UWingServer::AddTouchedObject(static_cast(Container)); @@ -71,35 +83,24 @@ bool FWingProperty::SetText(const FString &Value) return true; } - // Byte Enum types. + // 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; if (FByteProperty* ByteProp = CastField(Prop)) - { - if (UEnum* Enum = ByteProp->Enum) - { - int64 EnumValue; - if (!WingUtils::StringToEnum(Enum, Value, EnumValue)) return false; - uint8 ByteValue = (uint8)EnumValue; - Prop->SetValue_InContainer(Container, &ByteValue); - return true; - } - } - - // Regular Enum types. - // TODO: This doesn't use an incontainer setter, which means it - // doesn't call property setters. + Enum = ByteProp->Enum; if (FEnumProperty* EnumProp = CastField(Prop)) + Enum = EnumProp->GetEnum(); + if (Enum != nullptr) { int64 EnumValue; - if (!WingUtils::StringToEnum(EnumProp->GetEnum(), Value, EnumValue)) return false; - FNumericProperty* Underlying = EnumProp->GetUnderlyingProperty(); - void* ValuePtr = Underlying->ContainerPtrToValuePtr(Container); - Underlying->SetIntPropertyValue(ValuePtr, EnumValue); - return true; + if (!WingUtils::StringToEnum(Enum, Value, EnumValue)) return false; + Value = Enum->GetNameStringByValue(EnumValue); } - // Non-enum properties use ImportText + // Now Use ImportText const TCHAR* Result = Prop->ImportText_InContainer(*Value, Container, nullptr, PPF_None); - if (!Result) + if ((!Result) || (*Result != 0)) { UWingServer::Printf(TEXT("ERROR: Failed to parse '%s' for property '%s' (type: %s)\n"), *Value, *WingUtils::FormatName(Prop), *Prop->GetCPPType()); @@ -108,6 +109,106 @@ bool FWingProperty::SetText(const FString &Value) return true; } +bool FWingProperty::SetJson(const TSharedPtr &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(Prop)) + { + float Value = (float)D; + Prop->SetValue_InContainer(Container, &Value); + return true; + } + else if (FDoubleProperty* DoubleProp = CastField(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(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(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(Container); + FStructProperty* StructProp = CastField(Prop); + if (StructProp && (StructProp->Struct == FWingJsonObject::StaticStruct())) + { + static_cast(ValuePtr)->Json = JsonValue->AsObject(); + return true; + } + PrintExpectsReceived(TEXT("json object")); + return false; + } + + if (JsonValue->Type == EJson::Array) + { + void* ValuePtr = Prop->ContainerPtrToValuePtr(Container); + FStructProperty* StructProp = CastField(Prop); + if (StructProp && (StructProp->Struct == FWingJsonArray::StaticStruct())) + { + static_cast(ValuePtr)->Array = JsonValue->AsArray(); + return true; + } + PrintExpectsReceived(TEXT("json array")); + return false; + } + + PrintExpectsReceived(TEXT("Unrecognized Json Data")); + return false; +} + void FWingProperty::Collect(UStruct* StructType, void* Container, TArray &Props, EPropertyFlags Flags) { TMap> Grouped; @@ -253,3 +354,63 @@ FWingProperty FWingProperty::FindOneExactMatch(const TArray& Prop } return Matches[0]; } + +bool FWingProperty::PopulateFromJson(FWingProperty& P, const FJsonObject* Json, bool AllOptional) +{ + FString JsonKey = WingUtils::FormatName(P.Prop); + TSharedPtr 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& Props, const FJsonObject* Json, bool AllOptional) +{ + bool Ok = true; + + // Build a set of known property names for the unknown-field check. + TSet 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 Props = FWingProperty::GetAll(StructType, Container, (EPropertyFlags)0); + return PopulateFromJson(Props, Object); +} + +bool FWingProperty::PopulateFromJson(UStruct* StructType, void* Container, const TSharedPtr& 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()); +} diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingServer.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingServer.cpp index af6fca35..f0e0153c 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingServer.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingServer.cpp @@ -1,7 +1,7 @@ #include "WingServer.h" #include "WingHandler.h" +#include "WingProperty.h" #include "WingManual.h" -#include "WingJson.h" #include "WingLogCapture.h" #include "WingUtils.h" #include "UObject/StrongObjectPtr.h" @@ -327,7 +327,7 @@ void UWingServer::TryCallHandler(const FString &Line) IWingHandler* Handler = Cast(HandlerObj.Get()); // Populate the handler object with the request parameters. - if (!WingJson::PopulateFromJson(HandlerObj->GetClass(), HandlerObj.Get(), &*Request)) + if (!FWingProperty::PopulateFromJson(HandlerObj->GetClass(), HandlerObj.Get(), &*Request)) { UWingServer::SuggestManual(WingManual::Section::HandlerHelp); return; diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp index 39229024..ed1d44d2 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp @@ -1,6 +1,6 @@ #include "WingUtils.h" #include "WingActorComponent.h" -#include "WingJson.h" +#include "WingProperty.h" #include "WingTypes.h" #include "WingServer.h" #include "WingHandler.h" diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingJson.h b/Plugins/UEWingman/Source/UEWingman/Public/WingJson.h deleted file mode 100644 index bb5bee3b..00000000 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingJson.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include "CoreMinimal.h" -#include "WingHandler.h" -#include "WingProperty.h" -#include "Dom/JsonObject.h" - -// JSON utility functions used by MCP handlers. -// This is effectively a namespace — all methods are static. -class WingJson -{ -public: - static bool PopulateFromJson(FWingProperty& Prop, const FJsonObject* Json, bool AllOptional = false); - static bool PopulateFromJson(TArray& Props, const FJsonObject* Json, bool AllOptional = false); - static bool PopulateFromJson(UStruct* StructType, void* Container, const TSharedPtr& JsonValue); - static bool PopulateFromJson(UStruct* StructType, void* Container, const FJsonObject* Json); -}; diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingProperty.h b/Plugins/UEWingman/Source/UEWingman/Public/WingProperty.h index fea7bbe1..635d7315 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingProperty.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingProperty.h @@ -15,7 +15,8 @@ struct FWingProperty FWingProperty(FProperty* InProp, UObject* InContainer); FString GetText() const; - bool SetText(const FString& Value); + bool SetText(FString Value); + bool SetJson(const TSharedPtr &Value); // Get the Category metadata. FString GetCategory(); @@ -34,6 +35,12 @@ struct FWingProperty static void Remove(TArray& Props, const FString& Name); static void Move(TArray &Out, TArray &In, const FString &Name); + static bool PopulateFromJson(FWingProperty& Prop, const FJsonObject* Json, bool AllOptional = false); + static bool PopulateFromJson(TArray& Props, const FJsonObject* Json, bool AllOptional = false); + static bool PopulateFromJson(UStruct* StructType, void* Container, const FJsonObject* Object); + static bool PopulateFromJson(UStruct* StructType, void* Container, const TSharedPtr& Object); + private: + void PrintExpectsReceived(const TCHAR *Type); static void Collect(UStruct* Struct, void* Container, TArray &Props, EPropertyFlags Flags); };