Better handling of JSON property setters
This commit is contained in:
@@ -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<UObject*>(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<FByteProperty>(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<FEnumProperty>(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<void>(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<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;
|
||||
}
|
||||
|
||||
void FWingProperty::Collect(UStruct* StructType, void* Container, TArray<FWingProperty> &Props, EPropertyFlags Flags)
|
||||
{
|
||||
TMap<FString, TArray<FWingProperty>> Grouped;
|
||||
@@ -253,3 +354,63 @@ FWingProperty FWingProperty::FindOneExactMatch(const TArray<FWingProperty>& Prop
|
||||
}
|
||||
return Matches[0];
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user