diff --git a/JSONQuery.uplugin b/JSONQuery.uplugin index 9f7add8..c584f6c 100644 --- a/JSONQuery.uplugin +++ b/JSONQuery.uplugin @@ -3,10 +3,9 @@ "FriendlyName" : "JSON Query", "Version" : 5, - "VersionName" : "0.91", + "VersionName" : "0.95", "CreatedBy" : "Stefander", "CreatedByURL" : "http://stefander.nl/", - "EngineVersion" : "4.8.0", "Description" : "Exposes nodes to Blueprint that allow you to easily post and request pages on webservers through the JSON protocol.", "Category" : "Web", "EnabledByDefault" : true, diff --git a/README.md b/README.md new file mode 100644 index 0000000..a5082b7 --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +# JSONQuery_UE4 + +Name | JSON Query +--- | --- +Category | Web +Author | [Stefan "Stefander" Wijnker](http://www.stefander.nl/) +Version | 0.95 +UE4 Build | 4.15.0 +Discuss | https://forums.unrealengine.com/showthread.php?7045-PLUGIN-JSON-Query + +## Overview +This plugin allows you to easily setup communication with webservers through the +common [JSON](http://en.wikipedia.org/wiki/JSON) protocol. Everything in this plugin is exposed to +Blueprints, which allows you to fully customize the post data and event dispatcher flow. + +## Downloads +Latest version (Binary): +[JSONQueryUE4.8.3.zip](https://github.com/marynate/JSONQuery_UE4/releases/download/0.91/JSONQuery-Bin-4.8.3.zip) + +Latest version (Source): +https://github.com/marynate/JSONQuery_UE4 + +## Blueprint Examples + +### Post data when the game starts + +https://wiki.unrealengine.com/File:JSONPostExample.png + +### Example output + +https://wiki.unrealengine.com/File:JSONPostOutput.png + +## Rebuilding + +You can rebuild the binaries by cloning this repository into a new UE4 C++ project. +When opening the project, the editor should ask you to rebuild the missing binary files +(e.g. with Visual Studio). diff --git a/Source/JSONQuery/Classes/JsonFieldData.h b/Source/JSONQuery/Classes/JsonFieldData.h index 1dc29b2..70306ac 100644 --- a/Source/JSONQuery/Classes/JsonFieldData.h +++ b/Source/JSONQuery/Classes/JsonFieldData.h @@ -2,99 +2,410 @@ #pragma once #include "JsonFieldData.generated.h" -// Generate a delegate for the OnGetResult event -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnGetResult); +/** +* The possible status of a JSON POST/GET call. +*/ +UENUM(BlueprintType, Category = "JSON") +enum class EJSONResult : uint8 +{ + Success = 0, + HttpFailed, + JSONParsingFailed +}; -// Generate a delegate for the OnFailed event -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnFailed); +// Generate a delegate for the OnGetResult event +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnGetResult, const bool, bSuccess, class UJsonFieldData*, JSON, const EJSONResult, Status); -UCLASS(BlueprintType, Blueprintable) +UCLASS(BlueprintType, Blueprintable, Category = "JSON") class UJsonFieldData : public UObject { - GENERATED_UCLASS_BODY() + GENERATED_BODY() private: - /* Internal bind method for the IHTTPRequest::OnProcessRequestCompleted() event */ + + /** + * Callback for IHttpRequest::OnProcessRequestComplete() + * + * @param Request HTTP request pointer + * @param Response Response pointer + * @param bWasSuccessful Whether the request was successful or not + */ void OnReady(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); - /* Resets the current post data */ + /** + * Resets the current page data + */ void Reset(); - /* Prefixes the input URL with http:// if needed */ + /** + * Prefixes the input URL with http:// if necessary + * + * @param inputURL The input URL + * + * @return The output URL + */ static FString CreateURL(FString inputURL); + /** + * This function will write the supplied key and value to the JsonWriter + * + * @param writer The JsonWriter to use + * @param key Object key + * @param value Object value + */ void WriteObject(TSharedRef> writer, FString key, FJsonValue* value); + public: UObject* contextObject; /* The actual field data */ TSharedPtr Data; - /* Contains the actual page content, as a string */ - UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "JSON") - FString Content; - - /* Event which triggers when the content has been retrieved */ + /** + * Event which triggers after the request returned something. Check bSuccess to know if it worked. + * Check Status to know what happened on error. + */ UPROPERTY(BlueprintAssignable, Category = "JSON") FOnGetResult OnGetResult; - /* Event which triggers when the request failed */ - UPROPERTY(BlueprintAssignable, Category = "JSON") - FOnFailed OnFailed; + /** + * Constructor + */ + UJsonFieldData(); - /* Creates a new post data object */ - UFUNCTION(BlueprintPure, meta = (DisplayName = "Create JSON Data", HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject"), Category = "JSON") + /** + * Get the JSON object as a string. + */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "JSON To String", CompactNodeTitle = "->", Keywords = "cast text convert serialize"), Category = "JSON") + FString ToString(); + + /** + * Checks if a field exists in the JSON object + * + * @param key The field name to check + * + * @return True if the field exists + */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "Check Field Exists"), Category = "JSON") + bool HasField(const FString& key); + + /** + * Create a new instance of the UJsonFieldData class, for use in Blueprint graphs. + * + * @param WorldContextObject The current context + * + * @return A pointer to the newly created post data + */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "Create JSON", HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject"), Category = "JSON") static UJsonFieldData* Create(UObject* WorldContextObject); - /* Adds string data to the post data */ - UFUNCTION(BlueprintPure, meta = (DisplayName = "Add String Field"), Category = "JSON") + /** + * Adds the supplied string to the post data, under the given key + * + * @param key Key + * @param value Object value + * + * @return The object itself + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add String Field"), Category = "JSON") UJsonFieldData* SetString(const FString& key, const FString& value); - /* Adds a string array to the post data */ - UFUNCTION(BlueprintPure, meta = (DisplayName = "Add String Array Field"), Category = "JSON") - UJsonFieldData* SetStringArray(const FString& key, const TArray arrayData); + /** + * Adds the supplied bool to the post data, under the given key + * + * @param key Key + * @param value Object value + * + * @return The object itself + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Boolean Field"), Category = "JSON") + UJsonFieldData* SetBoolean(const FString& key, const bool value); + + /** + * Adds the supplied float to the post data, under the given key + * + * @param key Key + * @param value Object value + * + * @return The object itself + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Float Field"), Category = "JSON") + UJsonFieldData* SetFloat(const FString& key, const float value); + + /** + * Adds the supplied integer to the post data, under the given key + * + * @param key Key + * @param value Object value + * + * @return The object itself + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Integer Field"), Category = "JSON") + UJsonFieldData* SetInt(const FString& key, const int32 value); + + /** + * Adds a null value to the post data, under the given key + * + * @param key Key + * + * @return The object itself + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Null Field"), Category = "JSON") + UJsonFieldData* SetNull(const FString& key); + + /** + * Adds the supplied string array to the post data, under the given key + * + * @param key Key + * @param data Array + * + * @return The object itself + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add String Array Field"), Category = "JSON") + UJsonFieldData* SetStringArray(const FString& key, const TArray data); - /* Sets nested object data to the post array */ - UFUNCTION(BlueprintPure, meta = (DisplayName = "Add Data Field"), Category = "JSON") + /** + * Adds the supplied boolean array to the post data, under the given key + * + * @param key Key + * @param data Array + * + * @return The object itself + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Boolean Array Field"), Category = "JSON") + UJsonFieldData* SetBoolArray(const FString& key, const TArray data); + + /** + * Adds the supplied float array to the post data, under the given key + * + * @param key Key + * @param data Array + * + * @return The object itself + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Float Array Field"), Category = "JSON") + UJsonFieldData* SetFloatArray(const FString& key, const TArray data); + + /** + * Adds the supplied integer array to the post data, under the given key + * + * @param key Key + * @param data Array + * + * @return The object itself + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Integer Array Field"), Category = "JSON") + UJsonFieldData* SetIntArray(const FString& key, const TArray data); + + /** + * Adds null array to the post data, under the given key, with the given number of nulls + * + * @param key Key + * @param data Array + * + * @return The object itself + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Null Array Field"), Category = "JSON") + UJsonFieldData* SetNullArray(const FString& key, const int32& length); + + /** + * Adds the supplied object to the post data, under the given key + * + * @param key Key + * @param objectData Object data + * + * @return The object itself + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Object Field"), Category = "JSON") UJsonFieldData* SetObject(const FString& key, const UJsonFieldData* objectData); - /* Adds a new post data field to the specified data */ - UFUNCTION(BlueprintPure, meta = (DisplayName = "Add Object Array Field"), Category = "JSON") + /** + * Adds the supplied object array to the post data, under the given key + * + * @param key Key + * @param objectData Array of object data + * + * @return The object itself + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Object Array Field"), Category = "JSON") UJsonFieldData* SetObjectArray(const FString& key, const TArray arrayData); - /* Gets string data from the post data */ + /** + * Tries to get a string from the field data by key, returns the string when successful + * + * @param key Key + * @param success Was the string field found? + * + * @return The requested string, empty if failed + */ UFUNCTION(BlueprintPure, meta = (DisplayName = "Get String Field"), Category = "JSON") - FString GetString(const FString& key) const; + FString GetString(const FString& key, bool& success) const; + + /** + * Tries to get a boolean from the field data by key, returns the boolean when successful + * + * @param key Key + * @param success Was the boolean field found? + * + * @return The requested boolean, always false when failed + */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Boolean Field"), Category = "JSON") + bool GetBool(const FString& key, bool& success) const; + + /** + * Tries to get an integer from the field data by key, returns the integer when successful + * + * @param key Key + * @param success Was the integer field found? + * + * @return The requested integer, always 0 when failed + */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Integer Field"), Category = "JSON") + int32 GetInt(const FString& key, bool& success) const; - /* Gets a string array with the specified key */ - UFUNCTION(BlueprintPure, meta = (DisplayName = "Get String Array Field", HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject"), Category = "JSON") - TArray GetStringArray(const FString& key); + /** + * Tries to get a float from the field data by key, returns the float when successful + * + * @param key Key + * @param success Was the float field found? + * + * @return The requested float, always 0.0 when failed + */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Float Field"), Category = "JSON") + float GetFloat(const FString& key, bool& success) const; - /* Fetches nested post data from the post data */ - UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Data Field", HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject"), Category = "JSON") - UJsonFieldData* GetObject(const FString& key); + /** + * Checks if a field is null + * + * @param key Key + * @param fieldExists Was the field found? + * + * @return If the field is null. False if it's not or it was not found. + */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Field Is Null"), Category = "JSON") + bool GetIsNull(const FString& key, bool& fieldExists) const; - /* Gets an array with post data with the specified key */ + /** + * Gets a string array from the post data with the given key + * + * @param key Key + * @param success Was the field found? + * + * @return The requested array of strings + */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get String Array Field"), Category = "JSON") + TArray GetStringArray(const FString& key, bool& success); + + /** + * Gets a boolean array from the post data with the given key + * + * @param key Key + * @param success Was the field found? + * + * @return The requested array of booleans + */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Boolean Array Field"), Category = "JSON") + TArray GetBoolArray(const FString& key, bool& success); + + /** + * Gets an integer array from the post data with the given key + * + * @param key Key + * @param success Was the field found? + * + * @return The requested array of integers + */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Integer Array Field"), Category = "JSON") + TArray GetIntArray(const FString& key, bool& success); + + /** + * Gets a float array from the post data with the given key + * + * @param key Key + * @param success Was the field found? + * + * @return The requested array of floats + */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Float Array Field"), Category = "JSON") + TArray GetFloatArray(const FString& key, bool& success); + + /** + * Gets the post data object from the post data with the given key + * + * @param WorldContextObject Array of strings + * @param key Key + * @param success Was the object field found? + * + * @return The object itself + */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Object Field", HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject"), Category = "JSON") + UJsonFieldData* GetObject(const FString& key, bool& success); + + /** + * Gets an object array from the post data with the given key + * + * @param key Key + * @param success Was the field found? + * + * @return The requested post data objects + */ UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Object Array Field", HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject"), Category = "JSON") - TArray GetObjectArray(UObject* WorldContextObject, const FString& key); + TArray GetObjectArray(UObject* WorldContextObject, const FString& key, bool& success); - /* Get all keys from the object */ + /** + * Gets the keys from the supplied object + * + * @param key Key + * + * @return Array of keys + */ UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Object Keys", HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject"), Category = "JSON") TArray GetObjectKeys(UObject* WorldContextObject); - /* Creates new data from the input string */ + /** + * Sets the fields from the supplied JSON string + * + * @param dataString The JSON string + */ UFUNCTION(BlueprintCallable, meta = (DisplayName = "From String"), Category = "JSON") - void FromString(const FString& dataString); + bool FromString(const FString& dataString); - /* Creates new data from text json file */ + /** + * Creates new data from the + * + * @param FilePath Text Json File in game content folder + * + * @return JsonFieldData Object + */ UFUNCTION(BlueprintCallable, meta = (DisplayName = "From File"), Category = "JSON") - void FromFile(const FString& FilePath); + bool FromFile(const FString& FilePath); - /* Posts a request with the supplied post data to the specified page */ + /** + * Posts the current request data to the internet + * + * @param WorldContextObject The current context + * @param url The URL to post to + */ UFUNCTION(BlueprintCallable, meta = (DisplayName = "Post JSON Request", HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject"), Category = "JSON") void PostRequest(UObject* WorldContextObject, const FString& url); - /* Requests a page from the internet with a JSON response */ + /** + * Posts the current request data to the internet, together with a file + * + * @param FilePath The absolute path for a file + * @param Url The URL to post to + */ + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Post JSON Request and File"), Category = "JSON") + void PostRequestWithFile(FString FilePath, const FString& Url); + + /** + * Grabs a page from the internet + * + * @param WorldContextObject The current context + * @param url The URL to request + * + * @return The newly created post data that will be filled with the url response. + */ UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get JSON Request", HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject"), Category = "JSON") static UJsonFieldData* GetRequest(UObject* WorldContextObject, const FString& url); }; \ No newline at end of file diff --git a/Source/JSONQuery/Private/JsonFieldData.cpp b/Source/JSONQuery/Private/JsonFieldData.cpp index afc488c..8faf146 100644 --- a/Source/JSONQuery/Private/JsonFieldData.cpp +++ b/Source/JSONQuery/Private/JsonFieldData.cpp @@ -7,20 +7,13 @@ /** * Constructor */ -UJsonFieldData::UJsonFieldData(const class FObjectInitializer& PCIP) - : Super(PCIP) { +UJsonFieldData::UJsonFieldData() +{ Reset(); } -/** -* Grabs a page from the internet -* -* @param WorldContextObject The current context -* @param url The URL to request -* -* @return A pointer to the newly created post data -*/ -UJsonFieldData* UJsonFieldData::GetRequest(UObject* WorldContextObject, const FString &url) { +UJsonFieldData* UJsonFieldData::GetRequest(UObject* WorldContextObject, const FString &url) +{ // Create new page data for the response UJsonFieldData* dataObj = Create(WorldContextObject); @@ -30,6 +23,8 @@ UJsonFieldData* UJsonFieldData::GetRequest(UObject* WorldContextObject, const FS HttpRequest->SetURL(CreateURL(url)); HttpRequest->OnProcessRequestComplete().BindUObject(dataObj, &UJsonFieldData::OnReady); + dataObj->AddToRoot(); + // Execute the request HttpRequest->ProcessRequest(); @@ -37,75 +32,107 @@ UJsonFieldData* UJsonFieldData::GetRequest(UObject* WorldContextObject, const FS return dataObj; } -/** -* Create a new instance of the UJsonFieldData class, for use in Blueprint graphs. -* -* @param WorldContextObject The current context -* -* @return A pointer to the newly created post data -*/ -UJsonFieldData* UJsonFieldData::Create(UObject* WorldContextObject) { +UJsonFieldData* UJsonFieldData::Create(UObject* WorldContextObject) +{ // Get the world object from the context UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject); // Construct the object and return it - UJsonFieldData* fieldData = (UJsonFieldData*)StaticConstructObject(UJsonFieldData::StaticClass()); + UJsonFieldData* fieldData = NewObject(); fieldData->contextObject = WorldContextObject; return fieldData; } -/** -* Prefixes the input URL with http:// if necessary -* -* @param inputURL The input URL -* -* @return The output URL -*/ -FString UJsonFieldData::CreateURL(FString inputURL) { - if (!inputURL.StartsWith("http")) { +FString UJsonFieldData::CreateURL(FString inputURL) +{ + if (!inputURL.StartsWith("http")) + { return "http://" + inputURL; } return inputURL; } -/** -* This function will write the supplied key and value to the JsonWriter -* -* @param writer The JsonWriter to use -* @param key Object key -* @param value Object value -* -*/ -void UJsonFieldData::WriteObject(TSharedRef> writer, FString key, FJsonValue* value) { - if (value->Type == EJson::String) { +void UJsonFieldData::WriteObject(TSharedRef> writer, FString key, FJsonValue* value) +{ + if (value->Type == EJson::Null) + { + // Write simple entry, don't a key when it isn't set + if (key.Len() > 0) + { + writer->WriteNull(key); + } + else + { + writer->WriteNull(); + } + } + if (value->Type == EJson::String) + { // Write simple string entry, don't a key when it isn't set - if (key.Len() > 0) { + if (key.Len() > 0) + { writer->WriteValue(key, value->AsString()); - } else { + } + else + { writer->WriteValue(value->AsString()); } - } else if (value->Type == EJson::Object) { + } + if (value->Type == EJson::Boolean) + { + // Write simple entry, don't a key when it isn't set + if (key.Len() > 0) + { + writer->WriteValue(key, value->AsBool()); + } + else + { + writer->WriteValue(value->AsBool()); + } + } + if (value->Type == EJson::Number) + { + // Write simple entry, don't a key when it isn't set + if (key.Len() > 0) + { + writer->WriteValue(key, value->AsNumber()); + } + else + { + writer->WriteValue(value->AsNumber()); + } + } + else if (value->Type == EJson::Object) + { // Write object entry - if (key.Len() > 0) { - writer->WriteObjectStart(key); } - else { - writer->WriteObjectStart(); } + if (key.Len() > 0) + { + writer->WriteObjectStart(key); + } + else + { + writer->WriteObjectStart(); + } // Loop through all the values in the object data TSharedPtr objectData = value->AsObject(); - for (auto objectValue = objectData->Values.CreateIterator(); objectValue; ++objectValue) { + for (auto objectValue = objectData->Values.CreateIterator(); objectValue; ++objectValue) + { // Using recursion to write the key and value to the writer WriteObject(writer, objectValue.Key(), objectValue.Value().Get()); } writer->WriteObjectEnd(); - } else if (value->Type == EJson::Array) { + } + else if (value->Type == EJson::Array) + { // Process array entry writer->WriteArrayStart(key); TArray> objectArray = value->AsArray(); - for (int32 i = 0; i < objectArray.Num(); i++) { + for (int32 i = 0; i < objectArray.Num(); i++) + { // Use recursion with an empty key to process all the values in the array WriteObject(writer, "", objectArray[i].Get()); } @@ -114,20 +141,9 @@ void UJsonFieldData::WriteObject(TSharedRef> writer, FString } } -/** -* Posts the current request data to the internet -* -* @param WorldContextObject The current context -* @param url The URL to post to -* -*/ -void UJsonFieldData::PostRequest(UObject* WorldContextObject, const FString &url) { - FString outStr; - TSharedRef> JsonWriter = TJsonWriterFactory::Create(&outStr); - - // Start writing the response - WriteObject(JsonWriter, "", new FJsonValueObject(Data)); - JsonWriter->Close(); +void UJsonFieldData::PostRequest(UObject* WorldContextObject, const FString &url) +{ + FString outStr = ToString(); // Log the post data for the user (OPTIONAL) UE_LOG(LogTemp, Warning, TEXT("Post data: %s"), *outStr); @@ -135,50 +151,109 @@ void UJsonFieldData::PostRequest(UObject* WorldContextObject, const FString &url // Create the post request with the generated data TSharedRef< IHttpRequest > HttpRequest = FHttpModule::Get().CreateRequest(); HttpRequest->SetVerb("POST"); + HttpRequest->SetHeader("User-Agent", "UnrealEngine4Client/1.0"); HttpRequest->SetURL(CreateURL(url)); HttpRequest->SetHeader("Content-Type", "application/json"); HttpRequest->SetContentAsString(outStr); HttpRequest->OnProcessRequestComplete().BindUObject(this, &UJsonFieldData::OnReady); + AddToRoot(); // Execute the request HttpRequest->ProcessRequest(); } -/** -* Adds the supplied string to the post data, under the given key -* -* @param key Key -* @param value Object value -* -* @return The object itself -*/ -UJsonFieldData* UJsonFieldData::SetString(const FString& key, const FString& value) { +void UJsonFieldData::PostRequestWithFile(FString FilePath, const FString &Url) +{ + FPaths::NormalizeFilename(FilePath); + TArray RawFileData; + if (!FFileHelper::LoadFileToArray(RawFileData, *FilePath)) + { + OnGetResult.Broadcast(false, this, EJSONResult::JSONParsingFailed); + return; + } + + // The post content + static const FString Boundary = "UnrealEngine4FormDataBoundary"; + TArray Buffer; + + // Add the JSON as a POST var named 'json' + FString JSONStr = ToString(); + FString Text = FString("\r\n--") + Boundary + "\r\n" + + "Content-Type: text/plain; charset=\"utf-8\"\r\n" + + "Content-disposition: form-data; name=\"json\"\r\n\r\n" + + JSONStr; + + // add the file + FString PathPart, Filename, Extension; + FPaths::Split(FilePath, PathPart, Filename, Extension); + Filename += FString(".") + Extension; + Text += FString("\r\n--") + Boundary + "\r\n" + + "Content-Type: application/octet-stream\r\n" + + "Content-disposition: form-data; name=\"file\"; filename=\"" + Filename + "\"\r\n\r\n"; + FTCHARToUTF8 Converter1(*Text); + Buffer.Append((uint8*)(ANSICHAR*)Converter1.Get(), Converter1.Length()); + Buffer.Append(RawFileData); + + // end POST variables + Text = FString("\r\n--") + Boundary + "--\r\n"; + FTCHARToUTF8 Converter2(*Text); + Buffer.Append((uint8*)(ANSICHAR*)Converter2.Get(), Converter2.Length()); + + // Create the post request with the generated data + TSharedRef< IHttpRequest > HttpRequest = FHttpModule::Get().CreateRequest(); + HttpRequest->SetVerb("POST"); + HttpRequest->SetHeader("User-Agent", "UnrealEngine4Client/1.0"); + HttpRequest->SetHeader("Content-Type", FString("multipart/form-data; boundary=") + Boundary); + HttpRequest->SetURL(CreateURL(Url)); + HttpRequest->SetContent(Buffer); + HttpRequest->OnProcessRequestComplete().BindUObject(this, &UJsonFieldData::OnReady); + + // Log the post data for the user (OPTIONAL) + UE_LOG(LogTemp, Warning, TEXT("Post data: %s"), *JSONStr); + + AddToRoot(); + // Execute the request + HttpRequest->ProcessRequest(); +} + +UJsonFieldData* UJsonFieldData::SetString(const FString& key, const FString& value) +{ Data->SetStringField(*key,*value); return this; } -/** -* Adds the supplied object to the post data, under the given key -* -* @param key Key -* @param objectData Object data -* -* @return The object itself -*/ -UJsonFieldData* UJsonFieldData::SetObject(const FString& key, const UJsonFieldData* objectData) { +UJsonFieldData* UJsonFieldData::SetBoolean(const FString& key, const bool value) +{ + Data->SetBoolField(*key, value); + return this; +} + +UJsonFieldData* UJsonFieldData::SetFloat(const FString& key, const float value) +{ + Data->SetNumberField(*key, static_cast(value)); + return this; +} + +UJsonFieldData* UJsonFieldData::SetInt(const FString& key, const int32 value) +{ + Data->SetNumberField(*key, static_cast(value)); + return this; +} + +UJsonFieldData* UJsonFieldData::SetNull(const FString& key) +{ + Data->SetObjectField(*key, NULL); + return this; +} + +UJsonFieldData* UJsonFieldData::SetObject(const FString& key, const UJsonFieldData* objectData) +{ Data->SetObjectField(*key, objectData->Data); return this; } -/** -* Adds the supplied object array to the post data, under the given key -* -* @param key Key -* @param objectData Array of object data -* -* @return The object itself -*/ -UJsonFieldData* UJsonFieldData::SetObjectArray(const FString& key, const TArray objectData) { +UJsonFieldData* UJsonFieldData::SetObjectArray(const FString& key, const TArray objectData) +{ TArray> *dataArray = new TArray>(); // Loop through the array and create new shared FJsonValueObject instances for every FJsonObject @@ -190,19 +265,13 @@ UJsonFieldData* UJsonFieldData::SetObjectArray(const FString& key, const TArray< return this; } -/** -* Adds the supplied string array to the post data, under the given key -* -* @param key Key -* @param objectData Array of strings -* -* @return The object itself -*/ -UJsonFieldData* UJsonFieldData::SetStringArray(const FString& key, const TArray stringData) { +UJsonFieldData* UJsonFieldData::SetStringArray(const FString& key, const TArray stringData) +{ TArray> *dataArray = new TArray>(); // Loop through the input array and add new shareable FJsonValueString instances to the data array - for (int32 i=0; i < stringData.Num(); i++) { + for (int32 i=0; i < stringData.Num(); i++) + { dataArray->Add(MakeShareable(new FJsonValueString(stringData[i]))); } @@ -210,22 +279,73 @@ UJsonFieldData* UJsonFieldData::SetStringArray(const FString& key, const TArray< return this; } -/** -* Gets the post data object from the post data with the given key -* -* @param WorldContextObject Array of strings -* @param key Key -* -* @return The object itself -*/ -UJsonFieldData* UJsonFieldData::GetObject(const FString& key) { +UJsonFieldData* UJsonFieldData::SetBoolArray(const FString& key, const TArray data) +{ + TArray> *dataArray = new TArray>(); + + // Loop through the input array and add new shareable FJsonValueString instances to the data array + for (int32 i = 0; i < data.Num(); i++) + { + dataArray->Add(MakeShareable(new FJsonValueBoolean(data[i]))); + } + + Data->SetArrayField(*key, *dataArray); + return this; +} + +UJsonFieldData* UJsonFieldData::SetFloatArray(const FString& key, const TArray data) +{ + TArray> *dataArray = new TArray>(); + + // Loop through the input array and add new shareable FJsonValueString instances to the data array + for (int32 i = 0; i < data.Num(); i++) + { + dataArray->Add(MakeShareable(new FJsonValueNumber(static_cast(data[i])))); + } + + Data->SetArrayField(*key, *dataArray); + return this; +} + +UJsonFieldData* UJsonFieldData::SetIntArray(const FString& key, const TArray data) +{ + TArray> *dataArray = new TArray>(); + + // Loop through the input array and add new shareable FJsonValueString instances to the data array + for (int32 i = 0; i < data.Num(); i++) + { + dataArray->Add(MakeShareable(new FJsonValueNumber(static_cast(data[i])))); + } + + Data->SetArrayField(*key, *dataArray); + return this; +} + +UJsonFieldData* UJsonFieldData::SetNullArray(const FString& key, const int32& length) +{ + TArray> *dataArray = new TArray>(); + + // Loop through the input array and add new shareable FJsonValueString instances to the data array + for (int32 i = 0; i < length; i++) + { + dataArray->Add(MakeShareable(new FJsonValueNull())); + } + + Data->SetArrayField(*key, *dataArray); + return this; +} + +UJsonFieldData* UJsonFieldData::GetObject(const FString& key, bool& success) +{ UJsonFieldData* fieldObj = NULL; // Try to get the object field from the data const TSharedPtr *outPtr; - if (!Data->TryGetObjectField(*key, outPtr)) { + if (!Data->TryGetObjectField(*key, outPtr)) + { // Throw an error and return NULL when the key could not be found UE_LOG(LogJson, Error, TEXT("Entry '%s' not found in the field data!"), *key); + success = false; return NULL; } @@ -234,75 +354,148 @@ UJsonFieldData* UJsonFieldData::GetObject(const FString& key) { fieldObj->Data = *outPtr; // Return the newly created object + success = true; return fieldObj; } -/** -* Gets a string array from the post data with the given key -* -* @param key Key -* -* @return The requested array of strings -*/ -TArray UJsonFieldData::GetStringArray(const FString& key) { +TArray UJsonFieldData::GetStringArray(const FString& key, bool& success) +{ TArray stringArray; // Try to get the array field from the post data const TArray> *arrayPtr; - if (Data->TryGetArrayField(*key, arrayPtr)) { + if (Data->TryGetArrayField(*key, arrayPtr)) + { // Iterate through the array and use the string value from all the entries - for (int32 i=0; i < arrayPtr->Num(); i++) { + for (int32 i=0; i < arrayPtr->Num(); i++) + { stringArray.Add((*arrayPtr)[i]->AsString()); } - } else { + success = true; + } + else + { // Throw an error when the entry could not be found in the field data - UE_LOG(LogJson, Error, TEXT("Array entry '%s' not found in the field data!"), *key); + UE_LOG(LogJson, Error, TEXT("Array entry '%s' not found in the field data!"), *key); + success = false; } // Return the array, if unsuccessful the array will be empty return stringArray; } -/** -* Gets an object array from the post data with the given key -* -* @param key Key -* -* @return The requested post data objects -*/ -TArray UJsonFieldData::GetObjectArray(UObject* WorldContextObject, const FString& key) { +TArray UJsonFieldData::GetBoolArray(const FString& key, bool& success) +{ + TArray array; + + // Try to get the array field from the post data + const TArray> *arrayPtr; + if (Data->TryGetArrayField(*key, arrayPtr)) + { + // Iterate through the array and use the bool value from all the entries + for (int32 i = 0; i < arrayPtr->Num(); i++) + { + array.Add((*arrayPtr)[i]->AsBool()); + } + success = true; + } + else + { + // Throw an error when the entry could not be found in the field data + UE_LOG(LogJson, Error, TEXT("Array entry '%s' not found in the field data!"), *key); + success = false; + } + + // Return the array, if unsuccessful the array will be empty + return array; +} + +TArray UJsonFieldData::GetIntArray(const FString& key, bool& success) +{ + TArray array; + + // Try to get the array field from the post data + const TArray> *arrayPtr; + if (Data->TryGetArrayField(*key, arrayPtr)) + { + // Iterate through the array and use the bool value from all the entries + for (int32 i = 0; i < arrayPtr->Num(); i++) + { + array.Add(static_cast((*arrayPtr)[i]->AsNumber())); + } + success = true; + } + else + { + // Throw an error when the entry could not be found in the field data + UE_LOG(LogJson, Error, TEXT("Array entry '%s' not found in the field data!"), *key); + success = false; + } + + // Return the array, if unsuccessful the array will be empty + return array; +} + +TArray UJsonFieldData::GetFloatArray(const FString& key, bool& success) +{ + TArray array; + + // Try to get the array field from the post data + const TArray> *arrayPtr; + if (Data->TryGetArrayField(*key, arrayPtr)) + { + // Iterate through the array and use the bool value from all the entries + for (int32 i = 0; i < arrayPtr->Num(); i++) + { + array.Add(static_cast((*arrayPtr)[i]->AsNumber())); + } + success = true; + } + else + { + // Throw an error when the entry could not be found in the field data + UE_LOG(LogJson, Error, TEXT("Array entry '%s' not found in the field data!"), *key); + success = false; + } + + // Return the array, if unsuccessful the array will be empty + return array; +} + +TArray UJsonFieldData::GetObjectArray(UObject* WorldContextObject, const FString& key, bool& success) +{ TArray objectArray; // Try to fetch and assign the array to the array pointer const TArray> *arrayPtr; - if (Data->TryGetArrayField(*key, arrayPtr)) { + if (Data->TryGetArrayField(*key, arrayPtr)) + { // Iterate through the input array and create new post data objects for every entry and add them to the objectArray - for (int32 i = 0; i < arrayPtr->Num(); i++) { + for (int32 i = 0; i < arrayPtr->Num(); i++) + { UJsonFieldData* pageData = Create(WorldContextObject); pageData->Data = (*arrayPtr)[i]->AsObject(); objectArray.Add(pageData); } + success = true; } - else { + else + { // Throw an error, since the value with the supplied key could not be found UE_LOG(LogJson, Error, TEXT("Array entry '%s' not found in the field data!"), *key); + success = false; } // Return the array, will be empty if unsuccessful return objectArray; } -/** -* Gets the keys from the supplied object -* -* @param key Key -* -* @return Array of keys -*/ -TArray UJsonFieldData::GetObjectKeys(UObject* WorldContextObject) { +TArray UJsonFieldData::GetObjectKeys(UObject* WorldContextObject) +{ TArray stringArray; - for (auto currJsonValue = Data->Values.CreateConstIterator(); currJsonValue; ++currJsonValue) { + for (auto currJsonValue = Data->Values.CreateConstIterator(); currJsonValue; ++currJsonValue) + { stringArray.Add((*currJsonValue).Key); } @@ -310,32 +503,89 @@ TArray UJsonFieldData::GetObjectKeys(UObject* WorldContextObject) { return stringArray; } -/** -* Tries to get a string from the field data by key, returns the string when successful -* -* @param key Key -* -* @return The requested string, empty if failed -*/ -FString UJsonFieldData::GetString(const FString& key) const { +FString UJsonFieldData::GetString(const FString& key, bool& success) const +{ FString outString; // If the current post data isn't valid, return an empty string - if (!Data->TryGetStringField(*key, outString)) { - UE_LOG(LogJson, Error, TEXT("Entry '%s' not found in the field data!"), *key); + if (!Data->TryGetStringField(*key, outString)) + { + UE_LOG(LogJson, Error, TEXT("String entry '%s' not found in the field data!"), *key); + success = false; return ""; } + success = true; return outString; } -/** -* Resets the current page data -* -*/ -void UJsonFieldData::Reset() { +bool UJsonFieldData::GetBool(const FString& key, bool& success) const +{ + bool value; + + // If the current post data isn't valid, return an empty string + if (!Data->TryGetBoolField(*key, value)) + { + UE_LOG(LogJson, Error, TEXT("Boolean entry '%s' not found in the field data!"), *key); + success = false; + return false; + } + + success = true; + return value; +} + +int32 UJsonFieldData::GetInt(const FString& key, bool& success) const +{ + int32 value; + + // If the current post data isn't valid, return an empty string + if (!Data->TryGetNumberField(*key, value)) + { + UE_LOG(LogJson, Error, TEXT("Number entry '%s' not found in the field data!"), *key); + success = false; + return 0; + } + + success = true; + return value; +} + +float UJsonFieldData::GetFloat(const FString& key, bool& success) const +{ + double value; + + // If the current post data isn't valid, return an empty string + if (!Data->TryGetNumberField(*key, value)) + { + UE_LOG(LogJson, Error, TEXT("Number entry '%s' not found in the field data!"), *key); + success = false; + return 0.0f; + } + + success = true; + return static_cast(value); +} + +bool UJsonFieldData::GetIsNull(const FString& key, bool& success) const +{ + // If the current post data isn't valid, return an empty string + if (!Data->HasField(key)) + { + UE_LOG(LogJson, Error, TEXT("Number entry '%s' not found in the field data!"), *key); + success = false; + return false; + } + + success = true; + return Data->HasTypedField(key); +} + +void UJsonFieldData::Reset() +{ // If the post data is valid - if (Data.IsValid()) { + if (Data.IsValid()) + { // Clear the current post data Data.Reset(); } @@ -344,64 +594,69 @@ void UJsonFieldData::Reset() { Data = MakeShareable(new FJsonObject()); } -/** -* Creates new data from the -* -* @param key Key -* -* @return The requested string, empty if failed -*/ -void UJsonFieldData::FromString(const FString& dataString) { +bool UJsonFieldData::FromString(const FString& dataString) +{ TSharedRef> JsonReader = TJsonReaderFactory::Create(dataString); // Deserialize the JSON data bool isDeserialized = FJsonSerializer::Deserialize(JsonReader, Data); - if (!isDeserialized || !Data.IsValid()) { + if (!isDeserialized || !Data.IsValid()) + { UE_LOG(LogJson, Error, TEXT("JSON data is invalid! Input:\n'%s'"), *dataString); + return false; } - // Assign the request content - Content = dataString; + return true; } -/** -* Creates new data from the -* -* @param FilePath Text Json File in game content folder -* -* @return JsonFieldData Object -*/ -void UJsonFieldData::FromFile(const FString& FilePath) { +bool UJsonFieldData::FromFile(const FString& FilePath) { FString Result; FString FullJsonPath = FPaths::ConvertRelativePathToFull(FPaths::GameContentDir() / FilePath); - if (!FFileHelper::LoadFileToString(Result, *FullJsonPath)) { + if (!FFileHelper::LoadFileToString(Result, *FullJsonPath)) + { UE_LOG(LogJson, Error, TEXT("Can't load json data from %s"), *FilePath); + return false; } - FromString(Result); + return FromString(Result); } -/** -* Callback for IHttpRequest::OnProcessRequestComplete() -* -* @param Request HTTP request pointer -* @param Response Response pointer -* @param bWasSuccessful Whether the request was successful or not -* -*/ -void UJsonFieldData::OnReady(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) { - if (!bWasSuccessful) { +void UJsonFieldData::OnReady(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) +{ + RemoveFromRoot(); + if (!bWasSuccessful) + { UE_LOG(LogJson, Error, TEXT("Response was invalid! Please check the URL.")); // Broadcast the failed event - OnFailed.Broadcast(); + OnGetResult.Broadcast(false, this, EJSONResult::HttpFailed); return; } // Process the string - FromString(Response->GetContentAsString()); + if (!FromString(Response->GetContentAsString())) + { + OnGetResult.Broadcast(false, this, EJSONResult::JSONParsingFailed); + } // Broadcast the result event - OnGetResult.Broadcast(); + OnGetResult.Broadcast(true, this, EJSONResult::Success); +} + +FString UJsonFieldData::ToString() +{ + FString outStr; + TSharedRef> JsonWriter = TJsonWriterFactory::Create(&outStr); + + // Start writing the response + WriteObject(JsonWriter, "", new FJsonValueObject(Data)); + JsonWriter->Close(); + + return outStr; +} + +bool UJsonFieldData::HasField(const FString& key) +{ + return Data->HasField(key); } \ No newline at end of file