├── .gitignore ├── SCREENSHOT.jpg ├── Resources └── Icon128.png ├── Source ├── VaRestPlugin │ ├── Private │ │ ├── VaRestSettings.cpp │ │ ├── VaRestPluginPrivatePCH.h │ │ ├── VaRestPlugin.cpp │ │ ├── VaRestLibrary.cpp │ │ ├── VaRestJsonParser.h │ │ ├── VaRestJsonValue.cpp │ │ ├── VaRestRequestJSON.cpp │ │ ├── VaRestJsonObject.cpp │ │ └── VaRestJsonParser.cpp │ ├── Classes │ │ ├── VaRestSettings.h │ │ ├── VaRestTypes.h │ │ ├── VaRestLibrary.h │ │ ├── VaRestJsonValue.h │ │ ├── VaRestJsonObject.h │ │ └── VaRestRequestJSON.h │ ├── VaRestPlugin.Build.cs │ └── Public │ │ └── VaRestPlugin.h └── VaRestEditorPlugin │ ├── Public │ └── VaRestEditorPlugin.h │ ├── Private │ ├── VaRestEditorPlugin.cpp │ └── VaRest_BreakJson.cpp │ ├── VaRestEditorPlugin.Build.cs │ └── Classes │ └── VaRest_BreakJson.h ├── VaRestPlugin.uplugin ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /Intermediate/ 2 | /Binaries/ 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /SCREENSHOT.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chozabu/VaRest/develop/SCREENSHOT.jpg -------------------------------------------------------------------------------- /Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chozabu/VaRest/develop/Resources/Icon128.png -------------------------------------------------------------------------------- /Source/VaRestPlugin/Private/VaRestSettings.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Vladimir Alyamkin. All Rights Reserved. 2 | 3 | #include "VaRestSettings.h" 4 | 5 | UVaRestSettings::UVaRestSettings(const FObjectInitializer& ObjectInitializer) 6 | : Super(ObjectInitializer) 7 | { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /Source/VaRestEditorPlugin/Public/VaRestEditorPlugin.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Vladimir Alyamkin. All Rights Reserved. 2 | // Original code by https://github.com/unktomi 3 | 4 | #pragma once 5 | 6 | #include "Modules/ModuleManager.h" 7 | 8 | class FVaRestEditorPluginModule : public IModuleInterface 9 | { 10 | 11 | public: 12 | /** IModuleInterface implementation */ 13 | virtual void StartupModule() override; 14 | virtual void ShutdownModule() override; 15 | 16 | }; 17 | -------------------------------------------------------------------------------- /Source/VaRestPlugin/Classes/VaRestSettings.h: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Vladimir Alyamkin. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "VaRestSettings.generated.h" 6 | 7 | UCLASS(config = Engine, defaultconfig) 8 | class VARESTPLUGIN_API UVaRestSettings : public UObject 9 | { 10 | GENERATED_UCLASS_BODY() 11 | 12 | public: 13 | /** You can disable request content logging to avoid security vulnerability */ 14 | UPROPERTY(Config, EditAnywhere, Category = "VaRest") 15 | bool bExtendedLog; 16 | 17 | }; 18 | -------------------------------------------------------------------------------- /VaRestPlugin.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion" : 3, 3 | 4 | "FriendlyName" : "VaRest", 5 | "Version" : 22, 6 | "VersionName" : "1.1-r22", 7 | "CreatedBy" : "Vladimir Alyamkin", 8 | "CreatedByURL" : "http://alyamkin.com", 9 | "EngineVersion" : "4.19.0", 10 | "Description" : "Plugin that makes REST (JSON) server communication easy to use", 11 | "Category" : "Network", 12 | "MarketplaceURL" : "com.epicgames.launcher://ue/marketplace/content/e47be161e7a24e928560290abd5dcc4f", 13 | 14 | "Modules" : 15 | [ 16 | { 17 | "Name" : "VaRestPlugin", 18 | "Type" : "Runtime", 19 | "LoadingPhase": "PreDefault" 20 | }, 21 | { 22 | "Name": "VaRestEditorPlugin", 23 | "Type": "Developer" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /Source/VaRestEditorPlugin/Private/VaRestEditorPlugin.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Vladimir Alyamkin. All Rights Reserved. 2 | // Original code by https://github.com/unktomi 3 | 4 | #include "VaRestEditorPlugin.h" 5 | 6 | #define LOCTEXT_NAMESPACE "FVaRestEditorPluginModule" 7 | 8 | void FVaRestEditorPluginModule::StartupModule() 9 | { 10 | // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module 11 | } 12 | 13 | void FVaRestEditorPluginModule::ShutdownModule() 14 | { 15 | // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, 16 | // we call this function before unloading the module. 17 | } 18 | 19 | #undef LOCTEXT_NAMESPACE 20 | 21 | IMPLEMENT_MODULE(FVaRestEditorPluginModule, VaRestEditorPlugin) 22 | -------------------------------------------------------------------------------- /Source/VaRestPlugin/VaRestPlugin.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Vladimir Alyamkin. All Rights Reserved. 2 | 3 | using System.IO; 4 | 5 | namespace UnrealBuildTool.Rules 6 | { 7 | public class VaRestPlugin : ModuleRules 8 | { 9 | public VaRestPlugin(ReadOnlyTargetRules Target) : base(Target) 10 | { 11 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; 12 | 13 | PrivateIncludePaths.AddRange( 14 | new string[] { 15 | "VaRestPlugin/Private", 16 | // ... add other private include paths required here ... 17 | }); 18 | 19 | PublicDependencyModuleNames.AddRange( 20 | new string[] 21 | { 22 | "Core", 23 | "CoreUObject", 24 | "Engine", 25 | "HTTP", 26 | "Json" 27 | // ... add other public dependencies that you statically link with here ... 28 | }); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Vladimir Alyamkin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Overview 2 | ======== 3 | 4 | VaRest is the plugin for [Unreal Engine 4](https://www.unrealengine.com/) that makes REST server communications easier to use. 5 | 6 | Key features: 7 | 8 | * Flexible Http/Https request management with support of different Verbs and Content Types 9 | * **No C++ coding required**, everything can be managed via blueprints 10 | * Blueprintable FJsonObject wrapper with almost full support of Json features: different types of values, **arrays**, **binary data** content, both ways serializarion to FString, etc. 11 | * Blueprintable FJsonValue wrapper - **full Json features made for blueprints!** 12 | * Both bindable events and **latent functions** are provided to control the asynchronous requests 13 | 14 | Check the [Wiki](https://hiazma.atlassian.net/wiki/display/VAR) for plugin usage examples and installation notes. 15 | 16 | Current version: **1.1 R 22** (UE 4.18-4.19) 17 | 18 | ![SCREENSHOT](SCREENSHOT.jpg) 19 | 20 | 21 | Legal info 22 | ---------- 23 | 24 | Unreal® is a trademark or registered trademark of Epic Games, Inc. in the United States of America and elsewhere. 25 | 26 | Unreal® Engine, Copyright 1998 – 2018, Epic Games, Inc. All rights reserved. 27 | 28 | -------------------------------------------------------------------------------- /Source/VaRestPlugin/Public/VaRestPlugin.h: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Vladimir Alyamkin. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "Modules/ModuleManager.h" 6 | 7 | 8 | /** 9 | * The public interface to this module. In most cases, this interface is only public to sibling modules 10 | * within this plugin. 11 | */ 12 | class IVaRestPlugin : public IModuleInterface 13 | { 14 | 15 | public: 16 | 17 | /** 18 | * Singleton-like access to this module's interface. This is just for convenience! 19 | * Beware of calling this during the shutdown phase, though. Your module might have been unloaded already. 20 | * 21 | * @return Returns singleton instance, loading the module on demand if needed 22 | */ 23 | static inline IVaRestPlugin& Get() 24 | { 25 | return FModuleManager::LoadModuleChecked< IVaRestPlugin >( "VaRestPlugin" ); 26 | } 27 | 28 | /** 29 | * Checks to see if this module is loaded and ready. It is only valid to call Get() if IsAvailable() returns true. 30 | * 31 | * @return True if the module is loaded and ready to use 32 | */ 33 | static inline bool IsAvailable() 34 | { 35 | return FModuleManager::Get().IsModuleLoaded( "VaRestPlugin" ); 36 | } 37 | }; 38 | 39 | -------------------------------------------------------------------------------- /Source/VaRestPlugin/Classes/VaRestTypes.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Vladimir Alyamkin. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | /** Verb (GET, PUT, POST) used by the request */ 6 | UENUM(BlueprintType) 7 | enum class ERequestVerb : uint8 8 | { 9 | GET, 10 | POST, 11 | PUT, 12 | DEL UMETA(DisplayName = "DELETE"), 13 | /** Set CUSTOM verb by SetCustomVerb() function */ 14 | CUSTOM 15 | }; 16 | 17 | /** Content type (json, urlencoded, etc.) used by the request */ 18 | UENUM(BlueprintType) 19 | enum class ERequestContentType : uint8 20 | { 21 | x_www_form_urlencoded_url UMETA(DisplayName = "x-www-form-urlencoded (URL)"), 22 | x_www_form_urlencoded_body UMETA(DisplayName = "x-www-form-urlencoded (Request Body)"), 23 | json, 24 | binary 25 | }; 26 | 27 | /** Enumerates the current state of an Http request */ 28 | UENUM(BlueprintType) 29 | enum class ERequestStatus : uint8 30 | { 31 | /** Has not been started via ProcessRequest() */ 32 | NotStarted, 33 | /** Currently being ticked and processed */ 34 | Processing, 35 | /** Finished but failed */ 36 | Failed, 37 | /** Failed because it was unable to connect (safe to retry) */ 38 | Failed_ConnectionError, 39 | /** Finished and was successful */ 40 | Succeeded 41 | }; 42 | -------------------------------------------------------------------------------- /Source/VaRestPlugin/Private/VaRestPluginPrivatePCH.h: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Vladimir Alyamkin. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "Runtime/Launch/Resources/Version.h" 6 | 7 | #if ENGINE_MINOR_VERSION >= 15 8 | #include "CoreMinimal.h" 9 | #include "EngineDefines.h" 10 | #include "Engine/Engine.h" 11 | #include "UObject/Object.h" 12 | #include "UObject/ScriptMacros.h" 13 | #else 14 | #include "CoreUObject.h" 15 | #include "Engine.h" 16 | #endif 17 | 18 | #include "Http.h" 19 | #include "Containers/Map.h" 20 | #include "Json.h" 21 | 22 | #include "LatentActions.h" 23 | #include "Templates/SharedPointer.h" 24 | 25 | // You should place include statements to your module's private header files here. You only need to 26 | // add includes for headers that are used in most of your module's source files though. 27 | #include "Modules/ModuleManager.h" 28 | 29 | DECLARE_LOG_CATEGORY_EXTERN(LogVaRest, Log, All); 30 | 31 | #define VA_FUNC (FString(__FUNCTION__)) // Current Class Name + Function Name where this is called 32 | #define VA_LINE (FString::FromInt(__LINE__)) // Current Line Number in the code where this is called 33 | #define VA_FUNC_LINE (VA_FUNC + "(" + VA_LINE + ")") // Current Class and Line Number where this is called! 34 | -------------------------------------------------------------------------------- /Source/VaRestPlugin/Private/VaRestPlugin.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Vladimir Alyamkin. All Rights Reserved. 2 | 3 | #include "VaRestPlugin.h" 4 | #include "VaRestSettings.h" 5 | #include "VaRestJsonObject.h" 6 | #include "VaRestJsonValue.h" 7 | #include "VaRestRequestJSON.h" 8 | #include "VaRestPluginPrivatePCH.h" 9 | 10 | //#include "UObject/Package.h" 11 | //#include "Misc/ConfigCacheIni.h" 12 | 13 | #include "Developer/Settings/Public/ISettingsModule.h" 14 | 15 | #define LOCTEXT_NAMESPACE "VaRest" 16 | 17 | class FVaRestPlugin : public IVaRestPlugin 18 | { 19 | /** IModuleInterface implementation */ 20 | virtual void StartupModule() override 21 | { 22 | // @HACK Force classes to be compiled on shipping build 23 | UVaRestJsonObject::StaticClass(); 24 | UVaRestJsonValue::StaticClass(); 25 | UVaRestRequestJSON::StaticClass(); 26 | 27 | // Register settings 28 | if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings")) 29 | { 30 | SettingsModule->RegisterSettings("Project", "Plugins", "VaRest", 31 | LOCTEXT("RuntimeSettingsName", "VaRest Kit"), 32 | LOCTEXT("RuntimeSettingsDescription", "Configure API keys for VaRest"), 33 | GetMutableDefault() 34 | ); 35 | } 36 | } 37 | 38 | virtual void ShutdownModule() override 39 | { 40 | if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings")) 41 | { 42 | SettingsModule->UnregisterSettings("Project", "Plugins", "VaRest"); 43 | } 44 | } 45 | }; 46 | 47 | IMPLEMENT_MODULE( FVaRestPlugin, VaRestPlugin ) 48 | 49 | DEFINE_LOG_CATEGORY(LogVaRest); 50 | 51 | #undef LOCTEXT_NAMESPACE 52 | -------------------------------------------------------------------------------- /Source/VaRestEditorPlugin/VaRestEditorPlugin.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Vladimir Alyamkin. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class VaRestEditorPlugin : ModuleRules 6 | { 7 | public VaRestEditorPlugin(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; 10 | 11 | PublicIncludePaths.AddRange( 12 | new string[] { 13 | "VaRestPlugin", 14 | "VaRestPlugin/Public" 15 | 16 | // ... add public include paths required here ... 17 | }); 18 | 19 | 20 | PrivateIncludePaths.AddRange( 21 | new string[] { 22 | "VaRestEditorPlugin/Private", 23 | 24 | // ... add other private include paths required here ... 25 | }); 26 | 27 | 28 | PublicDependencyModuleNames.AddRange( 29 | new string[] 30 | { 31 | "Core", 32 | "VaRestPlugin" 33 | 34 | // ... add other public dependencies that you statically link with here ... 35 | }); 36 | 37 | 38 | PrivateDependencyModuleNames.AddRange( 39 | new string[] 40 | { 41 | "CoreUObject", 42 | "Engine", 43 | "Slate", 44 | "SlateCore", 45 | "InputCore", 46 | "AssetTools", 47 | "UnrealEd", // for FAssetEditorManager 48 | "KismetWidgets", 49 | "KismetCompiler", 50 | "BlueprintGraph", 51 | "GraphEditor", 52 | "Kismet", // for FWorkflowCentricApplication 53 | "PropertyEditor", 54 | "EditorStyle", 55 | "Sequencer", 56 | "DetailCustomizations", 57 | "Settings", 58 | "RenderCore" 59 | }); 60 | 61 | 62 | DynamicallyLoadedModuleNames.AddRange( 63 | new string[] 64 | { 65 | // ... add any modules that your module loads dynamically here ... 66 | }); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Source/VaRestEditorPlugin/Classes/VaRest_BreakJson.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Vladimir Alyamkin. All Rights Reserved. 2 | // Original code by https://github.com/unktomi 3 | 4 | #pragma once 5 | 6 | #include "Runtime/Launch/Resources/Version.h" 7 | 8 | #if ENGINE_MINOR_VERSION >= 15 9 | #include "CoreMinimal.h" 10 | #else 11 | #include "Engine.h" 12 | #endif 13 | 14 | #include "K2Node.h" 15 | 16 | #include "VaRest_BreakJson.generated.h" 17 | 18 | UENUM(BlueprintType) 19 | enum class EVaRest_JsonType : uint8 20 | { 21 | //JSON_Null UMETA(DisplayName = "Null"), 22 | JSON_Bool UMETA(DisplayName = "Boolean"), 23 | JSON_Number UMETA(DisplayName = "Number"), 24 | JSON_String UMETA(DisplayName = "String"), 25 | JSON_Object UMETA(DisplayName = "Object") 26 | }; 27 | 28 | USTRUCT(BlueprintType) 29 | struct FVaRest_NamedType 30 | { 31 | GENERATED_USTRUCT_BODY(); 32 | 33 | UPROPERTY(EditAnywhere, Category = NamedType) 34 | FString Name; 35 | 36 | UPROPERTY(EditAnywhere, Category = NamedType) 37 | EVaRest_JsonType Type; 38 | 39 | UPROPERTY(EditAnywhere, Category = NamedType) 40 | bool bIsArray; 41 | }; 42 | 43 | UCLASS(BlueprintType, Blueprintable) 44 | class VARESTEDITORPLUGIN_API UVaRest_BreakJson : public UK2Node 45 | { 46 | GENERATED_UCLASS_BODY() 47 | 48 | public: 49 | // Begin UEdGraphNode interface. 50 | virtual void AllocateDefaultPins() override; 51 | virtual FLinearColor GetNodeTitleColor() const override; 52 | virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override; 53 | // End UEdGraphNode interface. 54 | 55 | // Begin UK2Node interface 56 | virtual bool IsNodePure() const { return true; } 57 | virtual bool ShouldShowNodeProperties() const { return true; } 58 | void GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const override; 59 | virtual FText GetMenuCategory() const override; 60 | virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override; 61 | virtual class FNodeHandlingFunctor* CreateNodeHandler(class FKismetCompilerContext& CompilerContext) const override; 62 | // End UK2Node interface. 63 | 64 | protected: 65 | virtual void CreateProjectionPins(UEdGraphPin *Source); 66 | 67 | public: 68 | UPROPERTY(EditAnywhere, Category = PinOptions) 69 | TArray Outputs; 70 | 71 | }; 72 | -------------------------------------------------------------------------------- /Source/VaRestPlugin/Private/VaRestLibrary.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Vladimir Alyamkin. All Rights Reserved. 2 | 3 | #include "VaRestLibrary.h" 4 | #include "VaRestRequestJSON.h" 5 | #include "VaRestJsonObject.h" 6 | #include "VaRestPluginPrivatePCH.h" 7 | #include "Misc/Base64.h" 8 | 9 | ////////////////////////////////////////////////////////////////////////// 10 | // Helpers 11 | 12 | FString UVaRestLibrary::PercentEncode(const FString& Source) 13 | { 14 | return FGenericPlatformHttp::UrlEncode(Source); 15 | } 16 | 17 | FString UVaRestLibrary::Base64Encode(const FString& Source) 18 | { 19 | return FBase64::Encode(Source); 20 | } 21 | 22 | bool UVaRestLibrary::Base64Decode(const FString& Source, FString& Dest) 23 | { 24 | return FBase64::Decode(Source, Dest); 25 | } 26 | 27 | bool UVaRestLibrary::Base64EncodeData(const TArray& Data, FString& Dest) 28 | { 29 | if (Data.Num() > 0) 30 | { 31 | Dest = FBase64::Encode(Data); 32 | return true; 33 | } 34 | 35 | return false; 36 | } 37 | 38 | bool UVaRestLibrary::Base64DecodeData(const FString& Source, TArray& Dest) 39 | { 40 | return FBase64::Decode(Source, Dest); 41 | } 42 | 43 | 44 | ////////////////////////////////////////////////////////////////////////// 45 | // File system integration 46 | 47 | class UVaRestJsonObject* UVaRestLibrary::LoadJsonFromFile(UObject* WorldContextObject, const FString& Path) 48 | { 49 | UVaRestJsonObject* Json = UVaRestJsonObject::ConstructJsonObject(WorldContextObject); 50 | 51 | FString JSONString; 52 | if (FFileHelper::LoadFileToString(JSONString, *(FPaths::ProjectContentDir() + Path))) 53 | { 54 | if (Json->DecodeJson(JSONString)) 55 | { 56 | return Json; 57 | } 58 | else 59 | { 60 | UE_LOG(LogVaRest, Error, TEXT("%s: Can't decode json from file %s"), *VA_FUNC_LINE, *Path); 61 | } 62 | } 63 | else 64 | { 65 | UE_LOG(LogVaRest, Error, TEXT("%s: Can't open file %s"), *VA_FUNC_LINE, *Path); 66 | } 67 | 68 | return nullptr; 69 | } 70 | 71 | 72 | ////////////////////////////////////////////////////////////////////////// 73 | // Easy URL processing 74 | 75 | TMap UVaRestLibrary::RequestMap; 76 | 77 | void UVaRestLibrary::CallURL(UObject* WorldContextObject, const FString& URL, ERequestVerb Verb, ERequestContentType ContentType, UVaRestJsonObject* VaRestJson, const FVaRestCallDelegate& Callback) 78 | { 79 | UWorld* World = GEngine->GetWorldFromContextObjectChecked(WorldContextObject); 80 | if (World == nullptr) 81 | { 82 | UE_LOG(LogVaRest, Error, TEXT("UVaRestLibrary: Wrong world context")) 83 | return; 84 | } 85 | 86 | // Check we have valid data json 87 | if (VaRestJson == nullptr) 88 | { 89 | VaRestJson = UVaRestJsonObject::ConstructJsonObject(WorldContextObject); 90 | } 91 | 92 | UVaRestRequestJSON* Request = NewObject(); 93 | 94 | Request->SetVerb(Verb); 95 | Request->SetContentType(ContentType); 96 | Request->SetRequestObject(VaRestJson); 97 | 98 | FVaRestCallResponse Response; 99 | Response.Request = Request; 100 | Response.WorldContextObject = WorldContextObject; 101 | Response.Callback = Callback; 102 | 103 | Response.CompleteDelegateHandle = Request->OnStaticRequestComplete.AddStatic(&UVaRestLibrary::OnCallComplete); 104 | Response.FailDelegateHandle = Request->OnStaticRequestFail.AddStatic(&UVaRestLibrary::OnCallComplete); 105 | 106 | RequestMap.Add(Request, Response); 107 | 108 | Request->ResetResponseData(); 109 | Request->ProcessURL(URL); 110 | } 111 | 112 | void UVaRestLibrary::OnCallComplete(UVaRestRequestJSON* Request) 113 | { 114 | if (!RequestMap.Contains(Request)) 115 | { 116 | return; 117 | } 118 | 119 | FVaRestCallResponse* Response = RequestMap.Find(Request); 120 | 121 | Request->OnStaticRequestComplete.Remove(Response->CompleteDelegateHandle); 122 | Request->OnStaticRequestFail.Remove(Response->FailDelegateHandle); 123 | 124 | Response->Callback.ExecuteIfBound(Request); 125 | 126 | Response->WorldContextObject = nullptr; 127 | Response->Request = nullptr; 128 | RequestMap.Remove(Request); 129 | } 130 | -------------------------------------------------------------------------------- /Source/VaRestPlugin/Classes/VaRestLibrary.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Vladimir Alyamkin. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "Kismet/BlueprintFunctionLibrary.h" 6 | 7 | #include "VaRestTypes.h" 8 | #include "VaRestLibrary.generated.h" 9 | 10 | class UVaRestRequestJSON; 11 | class UVaRestJsonObject; 12 | 13 | DECLARE_DYNAMIC_DELEGATE_OneParam(FVaRestCallDelegate, UVaRestRequestJSON*, Request); 14 | 15 | USTRUCT() 16 | struct FVaRestCallResponse 17 | { 18 | GENERATED_USTRUCT_BODY() 19 | 20 | UPROPERTY() 21 | UVaRestRequestJSON* Request; 22 | 23 | UPROPERTY() 24 | UObject* WorldContextObject; 25 | 26 | UPROPERTY() 27 | FVaRestCallDelegate Callback; 28 | 29 | FDelegateHandle CompleteDelegateHandle; 30 | FDelegateHandle FailDelegateHandle; 31 | 32 | FVaRestCallResponse() 33 | : Request(nullptr) 34 | , WorldContextObject(nullptr) 35 | { 36 | } 37 | 38 | }; 39 | 40 | /** 41 | * Usefull tools for REST communications 42 | */ 43 | UCLASS() 44 | class UVaRestLibrary : public UBlueprintFunctionLibrary 45 | { 46 | GENERATED_BODY() 47 | 48 | 49 | ////////////////////////////////////////////////////////////////////////// 50 | // Helpers 51 | 52 | public: 53 | /** Applies percent-encoding to text */ 54 | UFUNCTION(BlueprintCallable, Category = "VaRest|Utility") 55 | static FString PercentEncode(const FString& Source); 56 | 57 | /** 58 | * Encodes a FString into a Base64 string 59 | * 60 | * @param Source The string data to convert 61 | * @return A string that encodes the binary data in a way that can be safely transmitted via various Internet protocols 62 | */ 63 | UFUNCTION(BlueprintCallable, Category = "VaRest|Utility", meta = (DisplayName = "Base64 Encode")) 64 | static FString Base64Encode(const FString& Source); 65 | 66 | /** 67 | * Decodes a Base64 string into a FString 68 | * 69 | * @param Source The stringified data to convert 70 | * @param Dest The out buffer that will be filled with the decoded data 71 | * @return True if the buffer was decoded, false if it failed to decode 72 | */ 73 | UFUNCTION(BlueprintCallable, Category = "VaRest|Utility", meta = (DisplayName = "Base64 Decode")) 74 | static bool Base64Decode(const FString& Source, FString& Dest); 75 | 76 | /** 77 | * Encodes a byte array into a Base64 string 78 | * 79 | * @param Dara The data to convert 80 | * @return A string that encodes the binary data in a way that can be safely transmitted via various Internet protocols 81 | */ 82 | UFUNCTION(BlueprintCallable, Category = "VaRest|Utility", meta = (DisplayName = "Base64 Encode Data")) 83 | static bool Base64EncodeData(const TArray& Data, FString& Dest); 84 | 85 | /** 86 | * Decodes a Base64 string into a byte array 87 | * 88 | * @param Source The stringified data to convert 89 | * @param Dest The out buffer that will be filled with the decoded data 90 | * @return True if the buffer was decoded, false if it failed to decode 91 | */ 92 | UFUNCTION(BlueprintCallable, Category = "VaRest|Utility", meta = (DisplayName = "Base64 Decode Data")) 93 | static bool Base64DecodeData(const FString& Source, TArray& Dest); 94 | 95 | 96 | ////////////////////////////////////////////////////////////////////////// 97 | // File system integration 98 | 99 | public: 100 | /** 101 | * Load JSON from formatted text file 102 | * @param Path File name relative to the Content folder 103 | */ 104 | UFUNCTION(BlueprintCallable, Category = "VaRest|Utility", meta = (WorldContext = "WorldContextObject")) 105 | static class UVaRestJsonObject* LoadJsonFromFile(UObject* WorldContextObject, const FString& Path); 106 | 107 | 108 | ////////////////////////////////////////////////////////////////////////// 109 | // Easy URL processing 110 | 111 | public: 112 | /** Easy way to process http requests */ 113 | UFUNCTION(BlueprintCallable, Category = "VaRest|Utility", meta = (WorldContext = "WorldContextObject")) 114 | static void CallURL(UObject* WorldContextObject, const FString& URL, ERequestVerb Verb, ERequestContentType ContentType, UVaRestJsonObject* VaRestJson, const FVaRestCallDelegate& Callback); 115 | 116 | /** Called when URL is processed (one for both success/unsuccess events)*/ 117 | static void OnCallComplete(UVaRestRequestJSON* Request); 118 | 119 | private: 120 | static TMap RequestMap; 121 | 122 | }; 123 | -------------------------------------------------------------------------------- /Source/VaRestPlugin/Private/VaRestJsonParser.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2017 Mail.Ru Group. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "Json.h" 6 | 7 | class FJsonValueNonConstArray : public FJsonValueArray 8 | { 9 | public: 10 | FJsonValueNonConstArray(const TArray>& InArray) : FJsonValueArray(InArray) {} 11 | 12 | /** return non const array */ 13 | TArray>& AsNonConstArray() { return Value; } 14 | }; 15 | 16 | class FJsonValueNonConstBoolean : public FJsonValueBoolean 17 | { 18 | public: 19 | FJsonValueNonConstBoolean(bool InBool) : FJsonValueBoolean(InBool) {} 20 | 21 | /** return non const bool */ 22 | bool& AsNonConstBool() { return Value; } 23 | }; 24 | 25 | class FJsonValueNonConstString : public FJsonValueString 26 | { 27 | public: 28 | FJsonValueNonConstString(const FString& InString) : FJsonValueString(InString) {} 29 | 30 | /** return non const string */ 31 | FString& AsNonConstString() { return Value; } 32 | }; 33 | 34 | class FJsonValueNonConstNumber : public FJsonValueNumber 35 | { 36 | public: 37 | FJsonValueNonConstNumber(double InNumber) : FJsonValueNumber(InNumber) {} 38 | 39 | /** return non const number */ 40 | double& AsNonConstNumber() { return Value; } 41 | }; 42 | 43 | enum class EJSONNotation 44 | { 45 | NONE, 46 | STRING, 47 | STRING_SPECIAL, 48 | SKIP, 49 | NUMBER, 50 | ARRAY, 51 | OBJECT, 52 | }; 53 | 54 | enum class EJSONToken 55 | { 56 | CURLY_BEGIN, 57 | CURLY_END, 58 | SQUARE_BEGIN, 59 | SQUARE_END, 60 | COMMA, 61 | COLON, 62 | ROOT, 63 | ERROR, 64 | }; 65 | 66 | struct FJSONState 67 | { 68 | /** Key */ 69 | FString Key; 70 | 71 | /** Data */ 72 | FString Data; 73 | 74 | /** Root object */ 75 | TSharedPtr Root; 76 | 77 | /** Object list */ 78 | TArray> Objects; 79 | 80 | /** Tokens */ 81 | TArray Tokens; 82 | 83 | /** Notation */ 84 | EJSONNotation Notation; 85 | 86 | /** Current char has escape */ 87 | bool bEscape; 88 | 89 | /** Has error */ 90 | int32 bError; 91 | 92 | /** Las quote for string */ 93 | TCHAR Quote; 94 | 95 | /** Size */ 96 | int32 Size; 97 | 98 | /** Default constructor */ 99 | FJSONState(); 100 | 101 | EJSONToken GetToken(int32 Index = 0); 102 | 103 | FORCEINLINE bool CheckTokens(EJSONToken T1); 104 | 105 | FORCEINLINE bool CheckTokens(EJSONToken T1, EJSONToken T2); 106 | 107 | FORCEINLINE bool CheckTokens(EJSONToken T1, EJSONToken T2, EJSONToken T3); 108 | 109 | FORCEINLINE void PopToken(int32 Num); 110 | 111 | FORCEINLINE void PopObject(); 112 | 113 | FORCEINLINE void PopArray(); 114 | 115 | FORCEINLINE void PopValue(bool bCheckType = true); 116 | 117 | FORCEINLINE FJsonValue* GetLast(); 118 | 119 | FORCEINLINE FJsonValueObject* GetObject(); 120 | 121 | FORCEINLINE FJsonValueNonConstArray* GetArray(); 122 | 123 | FORCEINLINE TSharedPtr PushObject(); 124 | 125 | FORCEINLINE TSharedPtr PushObject(TSharedPtr Object); 126 | 127 | FORCEINLINE TSharedPtr PushArray(); 128 | 129 | FORCEINLINE TSharedPtr PushBoolean(); 130 | 131 | FORCEINLINE TSharedPtr PushNull(); 132 | 133 | FORCEINLINE TSharedPtr PushNumber(); 134 | 135 | FORCEINLINE TSharedPtr PushString(); 136 | 137 | FORCEINLINE void ClearData(); 138 | 139 | FORCEINLINE void ClearKey(); 140 | 141 | FORCEINLINE void DataToKey(); 142 | 143 | FORCEINLINE void Error(); 144 | 145 | }; 146 | 147 | struct FJSONReader 148 | { 149 | /** State */ 150 | FJSONState State; 151 | 152 | /** Default constructor */ 153 | FJSONReader(); 154 | 155 | private: 156 | 157 | FORCEINLINE bool IsNewLine(const TCHAR& Char); 158 | 159 | FORCEINLINE bool IsSpace(const TCHAR& Char); 160 | 161 | FORCEINLINE bool FindToken(const TCHAR& Char); 162 | 163 | FORCEINLINE void UpdateNotation(); 164 | 165 | FORCEINLINE void ReadAsString(const TCHAR& Char); 166 | 167 | FORCEINLINE void ReadAsStringSpecial(const TCHAR& Char); 168 | 169 | FORCEINLINE void ReadAsNumber(const TCHAR& Char); 170 | 171 | FORCEINLINE void ReadBasicValue(const TCHAR& Char); 172 | 173 | FORCEINLINE void ReadAsArray(const TCHAR& Char); 174 | 175 | FORCEINLINE void ReadAsObject(const TCHAR& Char); 176 | 177 | FORCEINLINE void Skip(const TCHAR& Char); 178 | 179 | public: 180 | 181 | bool Read(const TCHAR Char); // @Pushkin 182 | 183 | }; 184 | 185 | struct FJSONWriter 186 | { 187 | FJSONWriter(); 188 | 189 | FORCEINLINE bool GetStartChar(const TSharedPtr& JsonValue, FString& Char); 190 | 191 | FORCEINLINE bool GetEndChar(const TSharedPtr& JsonValue, FString& Char); 192 | 193 | public: 194 | void Write(TSharedPtr JsonValue, FArchive* Writer, bool IsLastElement); // @Pushkin 195 | }; 196 | -------------------------------------------------------------------------------- /Source/VaRestPlugin/Classes/VaRestJsonValue.h: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2014 Epic Games, Inc. All Rights Reserved. 2 | // Copyright 2014 Vladimir Alyamkin. All Rights Reserved. 3 | 4 | #pragma once 5 | 6 | #include "VaRestJsonValue.generated.h" 7 | 8 | class UVaRestJsonObject; 9 | class FJsonValue; 10 | 11 | /** 12 | * Represents all the types a Json Value can be. 13 | */ 14 | UENUM(BlueprintType) 15 | namespace EVaJson 16 | { 17 | enum Type 18 | { 19 | None, 20 | Null, 21 | String, 22 | Number, 23 | Boolean, 24 | Array, 25 | Object, 26 | }; 27 | } 28 | 29 | /** 30 | * Blueprintable FJsonValue wrapper 31 | */ 32 | UCLASS(BlueprintType, Blueprintable) 33 | class VARESTPLUGIN_API UVaRestJsonValue : public UObject 34 | { 35 | GENERATED_UCLASS_BODY() 36 | 37 | /** Create new Json Number value 38 | * Attn.!! float used instead of double to make the function blueprintable! */ 39 | UFUNCTION(BlueprintPure, meta = (DisplayName = "Construct Json Number Value", HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject"), Category = "VaRest|Json") 40 | static UVaRestJsonValue* ConstructJsonValueNumber(UObject* WorldContextObject, float Number); 41 | 42 | /** Create new Json String value */ 43 | UFUNCTION(BlueprintPure, meta = (DisplayName = "Construct Json String Value", HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject"), Category = "VaRest|Json") 44 | static UVaRestJsonValue* ConstructJsonValueString(UObject* WorldContextObject, const FString& StringValue); 45 | 46 | /** Create new Json Bool value */ 47 | UFUNCTION(BlueprintPure, meta = (DisplayName = "Construct Json Bool Value", HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject"), Category = "VaRest|Json") 48 | static UVaRestJsonValue* ConstructJsonValueBool(UObject* WorldContextObject, bool InValue); 49 | 50 | /** Create new Json Array value */ 51 | UFUNCTION(BlueprintPure, meta = (DisplayName = "Construct Json Array Value", HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject"), Category = "VaRest|Json") 52 | static UVaRestJsonValue* ConstructJsonValueArray(UObject* WorldContextObject, const TArray& InArray); 53 | 54 | /** Create new Json Object value */ 55 | UFUNCTION(BlueprintPure, meta = (DisplayName = "Construct Json Object Value", HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject"), Category = "VaRest|Json") 56 | static UVaRestJsonValue* ConstructJsonValueObject(UObject* WorldContextObject, UVaRestJsonObject *JsonObject); 57 | 58 | /** Create new Json value from FJsonValue (to be used from VaRestJsonObject) */ 59 | static UVaRestJsonValue* ConstructJsonValue(UObject* WorldContextObject, const TSharedPtr& InValue); 60 | 61 | /** Get the root Json value */ 62 | TSharedPtr& GetRootValue(); 63 | 64 | /** Set the root Json value */ 65 | void SetRootValue(TSharedPtr& JsonValue); 66 | 67 | 68 | ////////////////////////////////////////////////////////////////////////// 69 | // FJsonValue API 70 | 71 | /** Get type of Json value (Enum) */ 72 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 73 | EVaJson::Type GetType() const; 74 | 75 | /** Get type of Json value (String) */ 76 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 77 | FString GetTypeString() const; 78 | 79 | /** Returns true if this value is a 'null' */ 80 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 81 | bool IsNull() const; 82 | 83 | /** Returns this value as a double, throwing an error if this is not an Json Number 84 | * Attn.!! float used instead of double to make the function blueprintable! */ 85 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 86 | float AsNumber() const; 87 | 88 | /** Returns this value as a number, throwing an error if this is not an Json String */ 89 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 90 | FString AsString() const; 91 | 92 | /** Returns this value as a boolean, throwing an error if this is not an Json Bool */ 93 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 94 | bool AsBool() const; 95 | 96 | /** Returns this value as an array, throwing an error if this is not an Json Array */ 97 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 98 | TArray AsArray() const; 99 | 100 | /** Returns this value as an object, throwing an error if this is not an Json Object */ 101 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 102 | UVaRestJsonObject* AsObject(); 103 | 104 | 105 | ////////////////////////////////////////////////////////////////////////// 106 | // Data 107 | 108 | private: 109 | /** Internal JSON data */ 110 | TSharedPtr JsonVal; 111 | 112 | 113 | ////////////////////////////////////////////////////////////////////////// 114 | // Helpers 115 | 116 | protected: 117 | /** Simple error logger */ 118 | void ErrorMessage(const FString& InType) const; 119 | 120 | }; 121 | -------------------------------------------------------------------------------- /Source/VaRestPlugin/Private/VaRestJsonValue.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Vladimir Alyamkin. All Rights Reserved. 2 | 3 | #include "VaRestJsonValue.h" 4 | #include "VaRestJsonObject.h" 5 | #include "VaRestPluginPrivatePCH.h" 6 | 7 | UVaRestJsonValue::UVaRestJsonValue(const class FObjectInitializer& PCIP) 8 | : Super(PCIP) 9 | { 10 | 11 | } 12 | 13 | UVaRestJsonValue* UVaRestJsonValue::ConstructJsonValueNumber(UObject* WorldContextObject, float Number) 14 | { 15 | TSharedPtr NewVal = MakeShareable(new FJsonValueNumber(Number)); 16 | 17 | UVaRestJsonValue* NewValue = NewObject(); 18 | NewValue->SetRootValue(NewVal); 19 | 20 | return NewValue; 21 | } 22 | 23 | UVaRestJsonValue* UVaRestJsonValue::ConstructJsonValueString(UObject* WorldContextObject, const FString& StringValue) 24 | { 25 | TSharedPtr NewVal = MakeShareable(new FJsonValueString(StringValue)); 26 | 27 | UVaRestJsonValue* NewValue = NewObject(); 28 | NewValue->SetRootValue(NewVal); 29 | 30 | return NewValue; 31 | } 32 | 33 | UVaRestJsonValue* UVaRestJsonValue::ConstructJsonValueBool(UObject* WorldContextObject, bool InValue) 34 | { 35 | TSharedPtr NewVal = MakeShareable(new FJsonValueBoolean(InValue)); 36 | 37 | UVaRestJsonValue* NewValue = NewObject(); 38 | NewValue->SetRootValue(NewVal); 39 | 40 | return NewValue; 41 | } 42 | 43 | UVaRestJsonValue* UVaRestJsonValue::ConstructJsonValueArray(UObject* WorldContextObject, const TArray& InArray) 44 | { 45 | // Prepare data array to create new value 46 | TArray< TSharedPtr > ValueArray; 47 | for (auto InVal : InArray) 48 | { 49 | ValueArray.Add(InVal->GetRootValue()); 50 | } 51 | 52 | TSharedPtr NewVal = MakeShareable(new FJsonValueArray(ValueArray)); 53 | 54 | UVaRestJsonValue* NewValue = NewObject(); 55 | NewValue->SetRootValue(NewVal); 56 | 57 | return NewValue; 58 | } 59 | 60 | UVaRestJsonValue* UVaRestJsonValue::ConstructJsonValueObject(UObject* WorldContextObject, UVaRestJsonObject *JsonObject) 61 | { 62 | TSharedPtr NewVal = MakeShareable(new FJsonValueObject(JsonObject->GetRootObject())); 63 | 64 | UVaRestJsonValue* NewValue = NewObject(); 65 | NewValue->SetRootValue(NewVal); 66 | 67 | return NewValue; 68 | } 69 | 70 | UVaRestJsonValue* ConstructJsonValue(UObject* WorldContextObject, const TSharedPtr& InValue) 71 | { 72 | TSharedPtr NewVal = InValue; 73 | 74 | UVaRestJsonValue* NewValue = NewObject(); 75 | NewValue->SetRootValue(NewVal); 76 | 77 | return NewValue; 78 | } 79 | 80 | TSharedPtr& UVaRestJsonValue::GetRootValue() 81 | { 82 | return JsonVal; 83 | } 84 | 85 | void UVaRestJsonValue::SetRootValue(TSharedPtr& JsonValue) 86 | { 87 | JsonVal = JsonValue; 88 | } 89 | 90 | 91 | ////////////////////////////////////////////////////////////////////////// 92 | // FJsonValue API 93 | 94 | EVaJson::Type UVaRestJsonValue::GetType() const 95 | { 96 | if (!JsonVal.IsValid()) 97 | { 98 | return EVaJson::None; 99 | } 100 | 101 | switch (JsonVal->Type) 102 | { 103 | case EJson::None: 104 | return EVaJson::None; 105 | 106 | case EJson::Null: 107 | return EVaJson::Null; 108 | 109 | case EJson::String: 110 | return EVaJson::String; 111 | 112 | case EJson::Number: 113 | return EVaJson::Number; 114 | 115 | case EJson::Boolean: 116 | return EVaJson::Boolean; 117 | 118 | case EJson::Array: 119 | return EVaJson::Array; 120 | 121 | case EJson::Object: 122 | return EVaJson::Object; 123 | 124 | default: 125 | return EVaJson::None; 126 | } 127 | } 128 | 129 | FString UVaRestJsonValue::GetTypeString() const 130 | { 131 | if (!JsonVal.IsValid()) 132 | { 133 | return "None"; 134 | } 135 | 136 | switch (JsonVal->Type) 137 | { 138 | case EJson::None: 139 | return TEXT("None"); 140 | 141 | case EJson::Null: 142 | return TEXT("Null"); 143 | 144 | case EJson::String: 145 | return TEXT("String"); 146 | 147 | case EJson::Number: 148 | return TEXT("Number"); 149 | 150 | case EJson::Boolean: 151 | return TEXT("Boolean"); 152 | 153 | case EJson::Array: 154 | return TEXT("Array"); 155 | 156 | case EJson::Object: 157 | return TEXT("Object"); 158 | 159 | default: 160 | return TEXT("None"); 161 | } 162 | } 163 | 164 | bool UVaRestJsonValue::IsNull() const 165 | { 166 | if (!JsonVal.IsValid()) 167 | { 168 | return true; 169 | } 170 | 171 | return JsonVal->IsNull(); 172 | } 173 | 174 | float UVaRestJsonValue::AsNumber() const 175 | { 176 | if (!JsonVal.IsValid()) 177 | { 178 | ErrorMessage(TEXT("Number")); 179 | return 0.f; 180 | } 181 | 182 | return JsonVal->AsNumber(); 183 | } 184 | 185 | FString UVaRestJsonValue::AsString() const 186 | { 187 | if (!JsonVal.IsValid()) 188 | { 189 | ErrorMessage(TEXT("String")); 190 | return FString(); 191 | } 192 | 193 | return JsonVal->AsString(); 194 | } 195 | 196 | bool UVaRestJsonValue::AsBool() const 197 | { 198 | if (!JsonVal.IsValid()) 199 | { 200 | ErrorMessage(TEXT("Boolean")); 201 | return false; 202 | } 203 | 204 | return JsonVal->AsBool(); 205 | } 206 | 207 | TArray UVaRestJsonValue::AsArray() const 208 | { 209 | TArray OutArray; 210 | 211 | if (!JsonVal.IsValid()) 212 | { 213 | ErrorMessage(TEXT("Array")); 214 | return OutArray; 215 | } 216 | 217 | TArray< TSharedPtr > ValArray = JsonVal->AsArray(); 218 | for (auto Value : ValArray) 219 | { 220 | UVaRestJsonValue* NewValue = NewObject(); 221 | NewValue->SetRootValue(Value); 222 | 223 | OutArray.Add(NewValue); 224 | } 225 | 226 | return OutArray; 227 | } 228 | 229 | UVaRestJsonObject* UVaRestJsonValue::AsObject() 230 | { 231 | if (!JsonVal.IsValid()) 232 | { 233 | ErrorMessage(TEXT("Object")); 234 | return nullptr; 235 | } 236 | 237 | TSharedPtr NewObj = JsonVal->AsObject(); 238 | 239 | UVaRestJsonObject* JsonObj = NewObject(); 240 | JsonObj->SetRootObject(NewObj); 241 | 242 | return JsonObj; 243 | } 244 | 245 | 246 | ////////////////////////////////////////////////////////////////////////// 247 | // Helpers 248 | 249 | void UVaRestJsonValue::ErrorMessage(const FString& InType) const 250 | { 251 | UE_LOG(LogVaRest, Error, TEXT("Json Value of type '%s' used as a '%s'."), *GetTypeString(), *InType); 252 | } 253 | -------------------------------------------------------------------------------- /Source/VaRestPlugin/Classes/VaRestJsonObject.h: -------------------------------------------------------------------------------- 1 | // Copyright 1998-2014 Epic Games, Inc. All Rights Reserved. 2 | // Copyright 2014 Vladimir Alyamkin. All Rights Reserved. 3 | 4 | #pragma once 5 | 6 | #include "VaRestJsonObject.generated.h" 7 | 8 | class UVaRestJsonValue; 9 | class FJsonObject; 10 | 11 | /** 12 | * Blueprintable FJsonObject wrapper 13 | */ 14 | UCLASS(BlueprintType, Blueprintable) 15 | class VARESTPLUGIN_API UVaRestJsonObject : public UObject 16 | { 17 | GENERATED_UCLASS_BODY() 18 | 19 | /** Create new Json object */ 20 | UFUNCTION(BlueprintPure, meta = (DisplayName = "Construct Json Object", HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject"), Category = "VaRest|Json") 21 | static UVaRestJsonObject* ConstructJsonObject(UObject* WorldContextObject); 22 | 23 | /** Reset all internal data */ 24 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 25 | void Reset(); 26 | 27 | /** Get the root Json object */ 28 | TSharedPtr& GetRootObject(); 29 | 30 | /** Set the root Json object */ 31 | void SetRootObject(TSharedPtr& JsonObject); 32 | 33 | 34 | ////////////////////////////////////////////////////////////////////////// 35 | // Serialization 36 | 37 | /** Serialize Json to string (formatted with line breaks) */ 38 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 39 | FString EncodeJson() const; 40 | 41 | /** Serialize Json to string (single string without line breaks) */ 42 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 43 | FString EncodeJsonToSingleString() const; 44 | 45 | /** Construct Json object from string */ 46 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 47 | bool DecodeJson(const FString& JsonString); 48 | 49 | 50 | ////////////////////////////////////////////////////////////////////////// 51 | // FJsonObject API 52 | 53 | /** Returns a list of field names that exist in the object */ 54 | UFUNCTION(BlueprintPure, Category = "VaRest|Json") 55 | TArray GetFieldNames() const; 56 | 57 | /** Checks to see if the FieldName exists in the object */ 58 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 59 | bool HasField(const FString& FieldName) const; 60 | 61 | /** Remove field named FieldName */ 62 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 63 | void RemoveField(const FString& FieldName); 64 | 65 | /** Get the field named FieldName as a JsonValue */ 66 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 67 | UVaRestJsonValue* GetField(const FString& FieldName) const; 68 | 69 | /** Add a field named FieldName with a Value */ 70 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 71 | void SetField(const FString& FieldName, UVaRestJsonValue* JsonValue); 72 | 73 | /** Get the field named FieldName as a Json Array */ 74 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 75 | TArray GetArrayField(const FString& FieldName) const; 76 | 77 | /** Set an ObjectField named FieldName and value of Json Array */ 78 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 79 | void SetArrayField(const FString& FieldName, const TArray& InArray); 80 | 81 | /** Adds all of the fields from one json object to this one */ 82 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 83 | void MergeJsonObject(UVaRestJsonObject* InJsonObject, bool Overwrite); 84 | 85 | 86 | ////////////////////////////////////////////////////////////////////////// 87 | // FJsonObject API Helpers (easy to use with simple Json objects) 88 | 89 | /** Get the field named FieldName as a number. Ensures that the field is present and is of type Json number. 90 | * Attn.!! float used instead of double to make the function blueprintable! */ 91 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 92 | float GetNumberField(const FString& FieldName) const; 93 | 94 | /** Add a field named FieldName with Number as value 95 | * Attn.!! float used instead of double to make the function blueprintable! */ 96 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 97 | void SetNumberField(const FString& FieldName, float Number); 98 | 99 | /** Get the field named FieldName as an Integer. Ensures that the field is present and is of type Json number. */ 100 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 101 | int32 GetIntegerField(const FString& FieldName) const; 102 | 103 | /** Add a field named FieldName with Integer as value. */ 104 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 105 | void SetIntegerField(const FString& FieldName, int32 Number); 106 | 107 | /** Get the field named FieldName as a string. */ 108 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 109 | FString GetStringField(const FString& FieldName) const; 110 | 111 | /** Add a field named FieldName with value of StringValue */ 112 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 113 | void SetStringField(const FString& FieldName, const FString& StringValue); 114 | 115 | /** Get the field named FieldName as a boolean. */ 116 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 117 | bool GetBoolField(const FString& FieldName) const; 118 | 119 | /** Set a boolean field named FieldName and value of InValue */ 120 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 121 | void SetBoolField(const FString& FieldName, bool InValue); 122 | 123 | /** Get the field named FieldName as a Json object. */ 124 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 125 | UVaRestJsonObject* GetObjectField(const FString& FieldName) const; 126 | 127 | /** Set an ObjectField named FieldName and value of JsonObject */ 128 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 129 | void SetObjectField(const FString& FieldName, UVaRestJsonObject* JsonObject); 130 | 131 | 132 | ////////////////////////////////////////////////////////////////////////// 133 | // Array fields helpers (uniform arrays) 134 | 135 | /** Get the field named FieldName as a Number Array. Use it only if you're sure that array is uniform! 136 | * Attn.!! float used instead of double to make the function blueprintable! */ 137 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 138 | TArray GetNumberArrayField(const FString& FieldName) const; 139 | 140 | /** Set an ObjectField named FieldName and value of Number Array 141 | * Attn.!! float used instead of double to make the function blueprintable! */ 142 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 143 | void SetNumberArrayField(const FString& FieldName, const TArray& NumberArray); 144 | 145 | /** Get the field named FieldName as a String Array. Use it only if you're sure that array is uniform! */ 146 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 147 | TArray GetStringArrayField(const FString& FieldName) const; 148 | 149 | /** Set an ObjectField named FieldName and value of String Array */ 150 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 151 | void SetStringArrayField(const FString& FieldName, const TArray& StringArray); 152 | 153 | /** Get the field named FieldName as a Bool Array. Use it only if you're sure that array is uniform! */ 154 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 155 | TArray GetBoolArrayField(const FString& FieldName) const; 156 | 157 | /** Set an ObjectField named FieldName and value of Bool Array */ 158 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 159 | void SetBoolArrayField(const FString& FieldName, const TArray& BoolArray); 160 | 161 | /** Get the field named FieldName as an Object Array. Use it only if you're sure that array is uniform! */ 162 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 163 | TArray GetObjectArrayField(const FString& FieldName) const; 164 | 165 | /** Set an ObjectField named FieldName and value of Ob Array */ 166 | UFUNCTION(BlueprintCallable, Category = "VaRest|Json") 167 | void SetObjectArrayField(const FString& FieldName, const TArray& ObjectArray); 168 | 169 | 170 | ////////////////////////////////////////////////////////////////////////// 171 | // Deserialize 172 | 173 | public: 174 | /** Deserialize byte content to json */ 175 | int32 DeserializeFromUTF8Bytes(const ANSICHAR* Bytes, int32 Size); 176 | 177 | /** Deserialize byte content to json */ 178 | int32 DeserializeFromTCHARBytes(const TCHAR* Bytes, int32 Size); 179 | 180 | /** Deserialize byte stream from reader */ 181 | void DecodeFromArchive(TUniquePtr& Reader); 182 | 183 | /** Save json to file */ 184 | bool WriteToFile(const FString& Path); 185 | 186 | static bool WriteStringToArchive(FArchive& Ar, const TCHAR* StrPtr, int64 Len); 187 | 188 | 189 | ////////////////////////////////////////////////////////////////////////// 190 | // Data 191 | 192 | private: 193 | /** Internal JSON data */ 194 | TSharedPtr JsonObj; 195 | 196 | }; 197 | -------------------------------------------------------------------------------- /Source/VaRestEditorPlugin/Private/VaRest_BreakJson.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Vladimir Alyamkin. All Rights Reserved. 2 | // Original code by https://github.com/unktomi 3 | 4 | #include "VaRest_BreakJson.h" 5 | 6 | #include "KismetCompiler.h" 7 | #include "EditorCategoryUtils.h" 8 | #include "EdGraphUtilities.h" 9 | #include "EdGraph/EdGraph.h" 10 | #include "EdGraph/EdGraphNodeUtils.h" // for FNodeTextCache 11 | #include "BlueprintNodeSpawner.h" 12 | #include "BlueprintActionDatabaseRegistrar.h" 13 | 14 | #include "Runtime/Launch/Resources/Version.h" 15 | 16 | #define LOCTEXT_NAMESPACE "VaRest_BreakJson" 17 | 18 | class FKCHandler_BreakJson : public FNodeHandlingFunctor 19 | { 20 | 21 | public: 22 | FKCHandler_BreakJson(FKismetCompilerContext& InCompilerContext) 23 | : FNodeHandlingFunctor(InCompilerContext) 24 | { 25 | } 26 | 27 | virtual void Compile(FKismetFunctionContext& Context, UEdGraphNode* Node) override 28 | { 29 | UEdGraphPin* InputPin = NULL; 30 | 31 | for (int32 PinIndex = 0; PinIndex < Node->Pins.Num(); ++PinIndex) 32 | { 33 | UEdGraphPin* Pin = Node->Pins[PinIndex]; 34 | if (Pin && (EGPD_Input == Pin->Direction)) 35 | { 36 | InputPin = Pin; 37 | break; 38 | } 39 | } 40 | 41 | UEdGraphPin *InNet = FEdGraphUtilities::GetNetFromPin(InputPin); 42 | UClass *Class = Cast(StaticLoadObject(UClass::StaticClass(), NULL, TEXT("class'VaRestPlugin.VaRestJsonObject'"))); 43 | 44 | FBPTerminal **SourceTerm = Context.NetMap.Find(InNet); 45 | if (SourceTerm == nullptr) 46 | { 47 | return; 48 | } 49 | 50 | for (int32 PinIndex = 0; PinIndex < Node->Pins.Num(); ++PinIndex) 51 | { 52 | UEdGraphPin* Pin = Node->Pins[PinIndex]; 53 | if (Pin && (EGPD_Output == Pin->Direction)) 54 | { 55 | if (Pin->LinkedTo.Num() < 1) 56 | { 57 | continue; 58 | } 59 | 60 | FBPTerminal **Target = Context.NetMap.Find(Pin); 61 | 62 | #if ENGINE_MINOR_VERSION >= 19 63 | const FName &FieldName = Pin->PinName; 64 | const FName &FieldType = Pin->PinType.PinCategory; 65 | #else 66 | const FString &FieldName = Pin->PinName; 67 | const FString &FieldType = Pin->PinType.PinCategory; 68 | #endif 69 | 70 | FBPTerminal* FieldNameTerm = Context.CreateLocalTerminal(ETerminalSpecification::TS_Literal); 71 | FieldNameTerm->Type.PinCategory = CompilerContext.GetSchema()->PC_String; 72 | #if ENGINE_MINOR_VERSION >= 13 73 | FieldNameTerm->SourcePin = Pin; 74 | #else 75 | FieldNameTerm->Source = Pin; 76 | #endif 77 | 78 | #if ENGINE_MINOR_VERSION >= 19 79 | FieldNameTerm->Name = FieldName.ToString(); 80 | FieldNameTerm->TextLiteral = FText::FromName(FieldName); 81 | #else 82 | FieldNameTerm->Name = FieldName; 83 | FieldNameTerm->TextLiteral = FText::FromString(FieldName); 84 | #endif 85 | 86 | FBlueprintCompiledStatement& Statement = Context.AppendStatementForNode(Node); 87 | FName FunctionName; 88 | 89 | #if ENGINE_MINOR_VERSION >= 17 90 | bool bIsArray = Pin->PinType.IsArray(); 91 | #else 92 | bool bIsArray = Pin->PinType.bIsArray; 93 | #endif 94 | 95 | if (FieldType == CompilerContext.GetSchema()->PC_Boolean) 96 | { 97 | FunctionName = bIsArray ? TEXT("GetBoolArrayField") : TEXT("GetBoolField"); 98 | } 99 | else if (FieldType == CompilerContext.GetSchema()->PC_Float) 100 | { 101 | FunctionName = bIsArray ? TEXT("GetNumberArrayField") : TEXT("GetNumberField"); 102 | } 103 | else if (FieldType == CompilerContext.GetSchema()->PC_String) 104 | { 105 | FunctionName = bIsArray ? TEXT("GetStringArrayField") : TEXT("GetStringField"); 106 | } 107 | else if (FieldType == CompilerContext.GetSchema()->PC_Object) 108 | { 109 | FunctionName = bIsArray ? TEXT("GetObjectArrayField") : TEXT("GetObjectField"); 110 | } 111 | else 112 | { 113 | continue; 114 | } 115 | 116 | UFunction *FunctionPtr = Class->FindFunctionByName(FunctionName); 117 | Statement.Type = KCST_CallFunction; 118 | Statement.FunctionToCall = FunctionPtr; 119 | Statement.FunctionContext = *SourceTerm; 120 | Statement.bIsParentContext = false; 121 | Statement.LHS = *Target; 122 | Statement.RHS.Add(FieldNameTerm); 123 | } 124 | } 125 | } 126 | 127 | FBPTerminal* RegisterInputTerm(FKismetFunctionContext& Context, UVaRest_BreakJson* Node) 128 | { 129 | // Find input pin 130 | UEdGraphPin* InputPin = NULL; 131 | for (int32 PinIndex = 0; PinIndex < Node->Pins.Num(); ++PinIndex) 132 | { 133 | UEdGraphPin* Pin = Node->Pins[PinIndex]; 134 | if (Pin && (EGPD_Input == Pin->Direction)) 135 | { 136 | InputPin = Pin; 137 | break; 138 | } 139 | } 140 | check(NULL != InputPin); 141 | 142 | // Find structure source net 143 | UEdGraphPin* Net = FEdGraphUtilities::GetNetFromPin(InputPin); 144 | FBPTerminal **TermPtr = Context.NetMap.Find(Net); 145 | 146 | if (!TermPtr) 147 | { 148 | FBPTerminal *Term = Context.CreateLocalTerminalFromPinAutoChooseScope(Net, Context.NetNameMap->MakeValidName(Net)); 149 | 150 | Context.NetMap.Add(Net, Term); 151 | 152 | return Term; 153 | } 154 | 155 | return *TermPtr; 156 | } 157 | 158 | void RegisterOutputTerm(FKismetFunctionContext& Context, UEdGraphPin* OutputPin, FBPTerminal* ContextTerm) 159 | { 160 | FBPTerminal *Term = Context.CreateLocalTerminalFromPinAutoChooseScope(OutputPin, Context.NetNameMap->MakeValidName(OutputPin)); 161 | Context.NetMap.Add(OutputPin, Term); 162 | } 163 | 164 | virtual void RegisterNets(FKismetFunctionContext& Context, UEdGraphNode* InNode) override 165 | { 166 | UVaRest_BreakJson* Node = Cast(InNode); 167 | FNodeHandlingFunctor::RegisterNets(Context, Node); 168 | 169 | check(NULL != Node); 170 | 171 | if (FBPTerminal* StructContextTerm = RegisterInputTerm(Context, Node)) 172 | { 173 | for (int32 PinIndex = 0; PinIndex < Node->Pins.Num(); ++PinIndex) 174 | { 175 | UEdGraphPin* Pin = Node->Pins[PinIndex]; 176 | if (NULL != Pin && EGPD_Output == Pin->Direction) 177 | { 178 | RegisterOutputTerm(Context, Pin, StructContextTerm); 179 | } 180 | } 181 | } 182 | } 183 | }; 184 | 185 | /** 186 | * Main node class 187 | */ 188 | UVaRest_BreakJson::UVaRest_BreakJson(const FObjectInitializer &ObjectInitializer) 189 | : Super(ObjectInitializer) 190 | { 191 | } 192 | 193 | FNodeHandlingFunctor* UVaRest_BreakJson::CreateNodeHandler(class FKismetCompilerContext& CompilerContext) const 194 | { 195 | return new FKCHandler_BreakJson(CompilerContext); 196 | } 197 | 198 | void UVaRest_BreakJson::AllocateDefaultPins() 199 | { 200 | const UEdGraphSchema_K2* K2Schema = GetDefault(); 201 | 202 | UClass *Class = Cast(StaticLoadObject(UClass::StaticClass(), NULL, TEXT("class'VaRestPlugin.VaRestJsonObject'"))); 203 | UEdGraphPin* Pin = CreatePin(EGPD_Input, K2Schema->PC_Object, TEXT(""), Class, false, false, TEXT("Target")); 204 | 205 | #if ENGINE_MINOR_VERSION >= 17 206 | K2Schema->SetPinAutogeneratedDefaultValueBasedOnType(Pin); 207 | #else 208 | K2Schema->SetPinDefaultValueBasedOnType(Pin); 209 | #endif 210 | 211 | CreateProjectionPins(Pin); 212 | } 213 | 214 | FLinearColor UVaRest_BreakJson::GetNodeTitleColor() const 215 | { 216 | return FLinearColor(255.0f, 255.0f, 0.0f); 217 | } 218 | 219 | void UVaRest_BreakJson::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) 220 | { 221 | bool bIsDirty = false; 222 | 223 | FName PropertyName = (PropertyChangedEvent.Property != NULL) ? PropertyChangedEvent.Property->GetFName() : NAME_None; 224 | if (true || PropertyName == TEXT("Outputs")) 225 | { 226 | bIsDirty = true; 227 | } 228 | 229 | if (bIsDirty) 230 | { 231 | ReconstructNode(); 232 | GetGraph()->NotifyGraphChanged(); 233 | } 234 | 235 | Super::PostEditChangeProperty(PropertyChangedEvent); 236 | } 237 | 238 | void UVaRest_BreakJson::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const 239 | { 240 | // actions get registered under specific object-keys; the idea is that 241 | // actions might have to be updated (or deleted) if their object-key is 242 | // mutated (or removed)... here we use the node's class (so if the node 243 | // type disappears, then the action should go with it) 244 | UClass* ActionKey = GetClass(); 245 | 246 | // to keep from needlessly instantiating a UBlueprintNodeSpawner, first 247 | // check to make sure that the registrar is looking for actions of this type 248 | // (could be regenerating actions for a specific asset, and therefore the 249 | // registrar would only accept actions corresponding to that asset) 250 | if (ActionRegistrar.IsOpenForRegistration(ActionKey)) 251 | { 252 | UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass()); 253 | check(NodeSpawner != nullptr); 254 | 255 | ActionRegistrar.AddBlueprintAction(ActionKey, NodeSpawner); 256 | } 257 | } 258 | 259 | FText UVaRest_BreakJson::GetMenuCategory() const 260 | { 261 | static FNodeTextCache CachedCategory; 262 | 263 | if (CachedCategory.IsOutOfDate(this)) 264 | { 265 | // FText::Format() is slow, so we cache this to save on performance 266 | CachedCategory.SetCachedText(FEditorCategoryUtils::BuildCategoryString(FCommonEditorCategory::Utilities, LOCTEXT("ActionMenuCategory", "Va Rest")), this); 267 | } 268 | return CachedCategory; 269 | } 270 | 271 | void UVaRest_BreakJson::CreateProjectionPins(UEdGraphPin *Source) 272 | { 273 | const UEdGraphSchema_K2* K2Schema = GetDefault(); 274 | UClass *Class = Cast(StaticLoadObject(UClass::StaticClass(), NULL, TEXT("class'VaRestPlugin.VaRestJsonObject'"))); 275 | 276 | for (TArray::TIterator it(Outputs); it; ++it) 277 | { 278 | #if ENGINE_MINOR_VERSION >= 19 279 | FName Type; 280 | #else 281 | FString Type; 282 | #endif 283 | 284 | UObject *Subtype = nullptr; 285 | FString FieldName = (*it).Name; 286 | 287 | switch ((*it).Type) { 288 | case EVaRest_JsonType::JSON_Bool: 289 | Type = K2Schema->PC_Boolean; 290 | break; 291 | 292 | case EVaRest_JsonType::JSON_Number: 293 | Type = K2Schema->PC_Float; 294 | break; 295 | 296 | case EVaRest_JsonType::JSON_String: 297 | Type = K2Schema->PC_String; 298 | break; 299 | 300 | case EVaRest_JsonType::JSON_Object: 301 | Type = K2Schema->PC_Object; 302 | Subtype = Class; 303 | break; 304 | } 305 | 306 | UEdGraphPin *OutputPin = CreatePin(EGPD_Output, Type, TEXT(""), Subtype, (*it).bIsArray, false, (*it).Name); 307 | } 308 | } 309 | 310 | FText UVaRest_BreakJson::GetNodeTitle(ENodeTitleType::Type TitleType) const 311 | { 312 | return LOCTEXT("VaRest_Break_Json.NodeTitle", "Break Json"); 313 | } 314 | 315 | #undef LOCTEXT_NAMESPACE 316 | -------------------------------------------------------------------------------- /Source/VaRestPlugin/Classes/VaRestRequestJSON.h: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Vladimir Alyamkin. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "Engine/LatentActionManager.h" 6 | #include "LatentActions.h" 7 | #include "Http.h" 8 | 9 | #include "VaRestTypes.h" 10 | #include "VaRestRequestJSON.generated.h" 11 | 12 | /** 13 | * @author Original latent action class by https://github.com/unktomi 14 | */ 15 | template class FVaRestLatentAction : public FPendingLatentAction 16 | { 17 | public: 18 | virtual void Call(const T &Value) 19 | { 20 | Result = Value; 21 | Called = true; 22 | } 23 | 24 | void operator()(const T &Value) 25 | { 26 | Call(Value); 27 | } 28 | 29 | void Cancel(); 30 | 31 | FVaRestLatentAction(FWeakObjectPtr RequestObj, T& ResultParam, const FLatentActionInfo& LatentInfo) : 32 | Called(false), 33 | Request(RequestObj), 34 | ExecutionFunction(LatentInfo.ExecutionFunction), 35 | OutputLink(LatentInfo.Linkage), 36 | CallbackTarget(LatentInfo.CallbackTarget), 37 | Result(ResultParam) 38 | { 39 | } 40 | 41 | virtual void UpdateOperation(FLatentResponse& Response) override 42 | { 43 | Response.FinishAndTriggerIf(Called, ExecutionFunction, OutputLink, CallbackTarget); 44 | } 45 | 46 | virtual void NotifyObjectDestroyed() 47 | { 48 | Cancel(); 49 | } 50 | 51 | virtual void NotifyActionAborted() 52 | { 53 | Cancel(); 54 | } 55 | 56 | private: 57 | bool Called; 58 | FWeakObjectPtr Request; 59 | 60 | public: 61 | const FName ExecutionFunction; 62 | const int32 OutputLink; 63 | const FWeakObjectPtr CallbackTarget; 64 | T &Result; 65 | }; 66 | 67 | 68 | /** Generate a delegates for callback events */ 69 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnRequestComplete, class UVaRestRequestJSON*, Request); 70 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnRequestFail, class UVaRestRequestJSON*, Request); 71 | 72 | DECLARE_MULTICAST_DELEGATE_OneParam(FOnStaticRequestComplete, class UVaRestRequestJSON*); 73 | DECLARE_MULTICAST_DELEGATE_OneParam(FOnStaticRequestFail, class UVaRestRequestJSON*); 74 | 75 | 76 | /** 77 | * General helper class http requests via blueprints 78 | */ 79 | UCLASS(BlueprintType, Blueprintable) 80 | class VARESTPLUGIN_API UVaRestRequestJSON : public UObject 81 | { 82 | GENERATED_UCLASS_BODY() 83 | 84 | public: 85 | ////////////////////////////////////////////////////////////////////////// 86 | // Construction 87 | 88 | /** Creates new request (totally empty) */ 89 | UFUNCTION(BlueprintPure, meta = (DisplayName = "Construct Json Request (Empty)", HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject"), Category = "VaRest|Request") 90 | static UVaRestRequestJSON* ConstructRequest(UObject* WorldContextObject); 91 | 92 | /** Creates new request with defined verb and content type */ 93 | UFUNCTION(BlueprintPure, meta = (DisplayName = "Construct Json Request", HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject"), Category = "VaRest|Request") 94 | static UVaRestRequestJSON* ConstructRequestExt(UObject* WorldContextObject, ERequestVerb Verb, ERequestContentType ContentType); 95 | 96 | /** Set verb to the request */ 97 | UFUNCTION(BlueprintCallable, Category = "VaRest|Request") 98 | void SetVerb(ERequestVerb Verb); 99 | 100 | /** Set custom verb to the request */ 101 | UFUNCTION(BlueprintCallable, Category = "VaRest|Request") 102 | void SetCustomVerb(FString Verb); 103 | 104 | /** Set content type to the request. If you're using the x-www-form-urlencoded, 105 | * params/constaints should be defined as key=ValueString pairs from Json data */ 106 | UFUNCTION(BlueprintCallable, Category = "VaRest|Request") 107 | void SetContentType(ERequestContentType ContentType); 108 | 109 | /** Set content type of the request for binary post data */ 110 | UFUNCTION(BlueprintCallable, Category = "VaRest|Request") 111 | void SetBinaryContentType(const FString &ContentType); 112 | 113 | /** Set content of the request for binary post data */ 114 | UFUNCTION(BlueprintCallable, Category = "VaRest|Request") 115 | void SetBinaryRequestContent(const TArray &Content); 116 | 117 | /** Set content of the request as a plain string */ 118 | UFUNCTION(BlueprintCallable, Category = "VaRest|Request") 119 | void SetStringRequestContent(const FString &Content); 120 | 121 | /** Sets optional header info */ 122 | UFUNCTION(BlueprintCallable, Category = "VaRest|Request") 123 | void SetHeader(const FString& HeaderName, const FString& HeaderValue); 124 | 125 | 126 | ////////////////////////////////////////////////////////////////////////// 127 | // Destruction and reset 128 | 129 | /** Reset all internal saved data */ 130 | UFUNCTION(BlueprintCallable, Category = "VaRest|Utility") 131 | void ResetData(); 132 | 133 | /** Reset saved request data */ 134 | UFUNCTION(BlueprintCallable, Category = "VaRest|Request") 135 | void ResetRequestData(); 136 | 137 | /** Reset saved response data */ 138 | UFUNCTION(BlueprintCallable, Category = "VaRest|Response") 139 | void ResetResponseData(); 140 | 141 | /** Cancel latent response waiting */ 142 | UFUNCTION(BlueprintCallable, Category = "VaRest|Response") 143 | void Cancel(); 144 | 145 | 146 | ////////////////////////////////////////////////////////////////////////// 147 | // JSON data accessors 148 | 149 | /** Get the Request Json object */ 150 | UFUNCTION(BlueprintCallable, Category = "VaRest|Request") 151 | UVaRestJsonObject* GetRequestObject(); 152 | 153 | /** Set the Request Json object */ 154 | UFUNCTION(BlueprintCallable, Category = "VaRest|Request") 155 | void SetRequestObject(UVaRestJsonObject* JsonObject); 156 | 157 | /** Get the Response Json object */ 158 | UFUNCTION(BlueprintCallable, Category = "VaRest|Response") 159 | UVaRestJsonObject* GetResponseObject(); 160 | 161 | /** Set the Response Json object */ 162 | UFUNCTION(BlueprintCallable, Category = "VaRest|Response") 163 | void SetResponseObject(UVaRestJsonObject* JsonObject); 164 | 165 | 166 | /////////////////////////////////////////////////////////////////////////// 167 | // Request/response data access 168 | 169 | /** Get url of http request */ 170 | UFUNCTION(BlueprintCallable, Category = "VaRest|Request") 171 | FString GetURL(); 172 | 173 | /** Get status of http request */ 174 | UFUNCTION(BlueprintCallable, Category = "VaRest|Request") 175 | ERequestStatus GetStatus(); 176 | 177 | /** Get the response code of the last query */ 178 | UFUNCTION(BlueprintCallable, Category = "VaRest|Response") 179 | int32 GetResponseCode(); 180 | 181 | /** Get value of desired response header */ 182 | UFUNCTION(BlueprintCallable, Category = "VaRest|Response") 183 | FString GetResponseHeader(const FString HeaderName); 184 | 185 | /** Get list of all response headers */ 186 | UFUNCTION(BlueprintCallable, Category = "VaRest|Response") 187 | TArray GetAllResponseHeaders(); 188 | 189 | 190 | ////////////////////////////////////////////////////////////////////////// 191 | // URL processing 192 | 193 | public: 194 | /** Setting request URL */ 195 | UFUNCTION(BlueprintCallable, Category = "VaRest|Request") 196 | void SetURL(const FString& Url = TEXT("http://alyamkin.com")); 197 | 198 | /** Open URL with current setup */ 199 | UFUNCTION(BlueprintCallable, Category = "VaRest|Request") 200 | virtual void ProcessURL(const FString& Url = TEXT("http://alyamkin.com")); 201 | 202 | /** Open URL in latent mode */ 203 | UFUNCTION(BlueprintCallable, Category = "VaRest|Request", meta = (Latent, LatentInfo = "LatentInfo", HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject")) 204 | virtual void ApplyURL(const FString& Url, UVaRestJsonObject *&Result, UObject* WorldContextObject, struct FLatentActionInfo LatentInfo); 205 | 206 | /** Check URL and execute process request */ 207 | UFUNCTION(BlueprintCallable, Category = "VaRest|Request") 208 | virtual void ExecuteProcessRequest(); 209 | 210 | protected: 211 | /** Apply current internal setup to request and process it */ 212 | void ProcessRequest(); 213 | 214 | 215 | ////////////////////////////////////////////////////////////////////////// 216 | // Request callbacks 217 | 218 | private: 219 | /** Internal bind function for the IHTTPRequest::OnProcessRequestCompleted() event */ 220 | void OnProcessRequestComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); 221 | 222 | public: 223 | /** Event occured when the request has been completed */ 224 | UPROPERTY(BlueprintAssignable, Category = "VaRest|Event") 225 | FOnRequestComplete OnRequestComplete; 226 | 227 | /** Event occured when the request wasn't successfull */ 228 | UPROPERTY(BlueprintAssignable, Category = "VaRest|Event") 229 | FOnRequestFail OnRequestFail; 230 | 231 | /** Event occured when the request has been completed */ 232 | FOnStaticRequestComplete OnStaticRequestComplete; 233 | 234 | /** Event occured when the request wasn't successfull */ 235 | FOnStaticRequestFail OnStaticRequestFail; 236 | 237 | 238 | ////////////////////////////////////////////////////////////////////////// 239 | // Tags 240 | 241 | public: 242 | /** Add tag to this request */ 243 | UFUNCTION(BlueprintCallable, Category = "VaRest|Utility") 244 | void AddTag(FName Tag); 245 | 246 | /** 247 | * Remove tag from this request 248 | * 249 | * @return Number of removed elements 250 | */ 251 | UFUNCTION(BlueprintCallable, Category = "VaRest|Utility") 252 | int32 RemoveTag(FName Tag); 253 | 254 | /** See if this request contains the supplied tag */ 255 | UFUNCTION(BlueprintCallable, Category = "VaRest|Utility") 256 | bool HasTag(FName Tag) const; 257 | 258 | protected: 259 | /** Array of tags that can be used for grouping and categorizing */ 260 | TArray Tags; 261 | 262 | 263 | ////////////////////////////////////////////////////////////////////////// 264 | // Data 265 | 266 | public: 267 | /** 268 | * Get request response stored as a string 269 | * @param bCacheResponseContent - Set true if you plan to use it few times to prevent deserialization each time 270 | */ 271 | UFUNCTION(BlueprintCallable, Category = "VaRest|Response") 272 | FString GetResponseContentAsString(bool bCacheResponseContent = true); 273 | 274 | public: 275 | /** Response size */ 276 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "VaRest|Response") 277 | int32 ResponseSize; 278 | 279 | /** DEPRECATED: Please use GetResponseContentAsString() instead */ 280 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "VaRest|Response") 281 | FString ResponseContent; 282 | 283 | /** Is the response valid JSON? */ 284 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "VaRest|Response") 285 | bool bIsValidJsonResponse; 286 | 287 | protected: 288 | /** Default value for deprecated ResponseContent variable */ 289 | static FString DeprecatedResponseString; 290 | 291 | protected: 292 | /** Latent action helper */ 293 | FVaRestLatentAction* ContinueAction; 294 | 295 | /** Internal request data stored as JSON */ 296 | UPROPERTY() 297 | UVaRestJsonObject* RequestJsonObj; 298 | 299 | TArray RequestBytes; 300 | FString BinaryContentType; 301 | 302 | /** Used for special cases when used wants to have plain string data in request. 303 | * Attn.! Content-type x-www-form-urlencoded only. */ 304 | FString StringRequestContent; 305 | 306 | /** Response data stored as JSON */ 307 | UPROPERTY() 308 | UVaRestJsonObject* ResponseJsonObj; 309 | 310 | /** Verb for making request (GET,POST,etc) */ 311 | ERequestVerb RequestVerb; 312 | 313 | /** Content type to be applied for request */ 314 | ERequestContentType RequestContentType; 315 | 316 | /** Mapping of header section to values. Used to generate final header string for request */ 317 | TMap RequestHeaders; 318 | 319 | /** Cached key/value header pairs. Parsed once request completes */ 320 | TMap ResponseHeaders; 321 | 322 | /** Http Response code */ 323 | int32 ResponseCode; 324 | 325 | /** Custom verb that will be used with RequestContentType == CUSTOM */ 326 | FString CustomVerb; 327 | 328 | /** Request we're currently processing */ 329 | TSharedRef HttpRequest = FHttpModule::Get().CreateRequest(); 330 | 331 | public: 332 | /** Returns reference to internal request object */ 333 | TSharedRef GetHttpRequest() const { return HttpRequest; }; 334 | 335 | }; 336 | -------------------------------------------------------------------------------- /Source/VaRestPlugin/Private/VaRestRequestJSON.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Vladimir Alyamkin. All Rights Reserved. 2 | 3 | #include "VaRestRequestJSON.h" 4 | #include "VaRestJsonObject.h" 5 | #include "VaRestLibrary.h" 6 | #include "VaRestSettings.h" 7 | #include "VaRestPluginPrivatePCH.h" 8 | 9 | #include "Misc/CoreMisc.h" 10 | #include "Runtime/Launch/Resources/Version.h" 11 | 12 | FString UVaRestRequestJSON::DeprecatedResponseString(TEXT("DEPRECATED: Please use GetResponseContentAsString() instead")); 13 | 14 | template void FVaRestLatentAction::Cancel() 15 | { 16 | UObject *Obj = Request.Get(); 17 | if (Obj != nullptr) 18 | { 19 | ((UVaRestRequestJSON*)Obj)->Cancel(); 20 | } 21 | } 22 | 23 | UVaRestRequestJSON::UVaRestRequestJSON(const class FObjectInitializer& PCIP) 24 | : Super(PCIP), 25 | BinaryContentType(TEXT("application/octet-stream")) 26 | { 27 | ContinueAction = nullptr; 28 | 29 | RequestVerb = ERequestVerb::GET; 30 | RequestContentType = ERequestContentType::x_www_form_urlencoded_url; 31 | 32 | ResetData(); 33 | } 34 | 35 | UVaRestRequestJSON* UVaRestRequestJSON::ConstructRequest(UObject* WorldContextObject) 36 | { 37 | return NewObject(); 38 | } 39 | 40 | UVaRestRequestJSON* UVaRestRequestJSON::ConstructRequestExt( 41 | UObject* WorldContextObject, 42 | ERequestVerb Verb, 43 | ERequestContentType ContentType) 44 | { 45 | UVaRestRequestJSON* Request = ConstructRequest(WorldContextObject); 46 | 47 | Request->SetVerb(Verb); 48 | Request->SetContentType(ContentType); 49 | 50 | return Request; 51 | } 52 | 53 | void UVaRestRequestJSON::SetVerb(ERequestVerb Verb) 54 | { 55 | RequestVerb = Verb; 56 | } 57 | 58 | void UVaRestRequestJSON::SetCustomVerb(FString Verb) 59 | { 60 | CustomVerb = Verb; 61 | } 62 | 63 | void UVaRestRequestJSON::SetContentType(ERequestContentType ContentType) 64 | { 65 | RequestContentType = ContentType; 66 | } 67 | 68 | void UVaRestRequestJSON::SetBinaryContentType(const FString& ContentType) 69 | { 70 | BinaryContentType = ContentType; 71 | } 72 | 73 | void UVaRestRequestJSON::SetBinaryRequestContent(const TArray& Bytes) 74 | { 75 | RequestBytes = Bytes; 76 | } 77 | 78 | void UVaRestRequestJSON::SetStringRequestContent(const FString& Content) 79 | { 80 | StringRequestContent = Content; 81 | } 82 | 83 | void UVaRestRequestJSON::SetHeader(const FString& HeaderName, const FString& HeaderValue) 84 | { 85 | RequestHeaders.Add(HeaderName, HeaderValue); 86 | } 87 | 88 | 89 | ////////////////////////////////////////////////////////////////////////// 90 | // Destruction and reset 91 | 92 | void UVaRestRequestJSON::ResetData() 93 | { 94 | ResetRequestData(); 95 | ResetResponseData(); 96 | } 97 | 98 | void UVaRestRequestJSON::ResetRequestData() 99 | { 100 | if (RequestJsonObj != nullptr) 101 | { 102 | RequestJsonObj->Reset(); 103 | } 104 | else 105 | { 106 | RequestJsonObj = NewObject(); 107 | } 108 | 109 | // See issue #90 110 | // HttpRequest = FHttpModule::Get().CreateRequest(); 111 | 112 | RequestBytes.Empty(); 113 | StringRequestContent.Empty(); 114 | } 115 | 116 | void UVaRestRequestJSON::ResetResponseData() 117 | { 118 | if (ResponseJsonObj != nullptr) 119 | { 120 | ResponseJsonObj->Reset(); 121 | } 122 | else 123 | { 124 | ResponseJsonObj = NewObject(); 125 | } 126 | 127 | ResponseHeaders.Empty(); 128 | ResponseCode = -1; 129 | ResponseSize = 0; 130 | 131 | bIsValidJsonResponse = false; 132 | 133 | // #127 Reset string to deprecated state 134 | ResponseContent = DeprecatedResponseString; 135 | } 136 | 137 | void UVaRestRequestJSON::Cancel() 138 | { 139 | ContinueAction = nullptr; 140 | 141 | ResetResponseData(); 142 | } 143 | 144 | 145 | ////////////////////////////////////////////////////////////////////////// 146 | // JSON data accessors 147 | 148 | UVaRestJsonObject* UVaRestRequestJSON::GetRequestObject() 149 | { 150 | return RequestJsonObj; 151 | } 152 | 153 | void UVaRestRequestJSON::SetRequestObject(UVaRestJsonObject* JsonObject) 154 | { 155 | RequestJsonObj = JsonObject; 156 | } 157 | 158 | UVaRestJsonObject* UVaRestRequestJSON::GetResponseObject() 159 | { 160 | return ResponseJsonObj; 161 | } 162 | 163 | void UVaRestRequestJSON::SetResponseObject(UVaRestJsonObject* JsonObject) 164 | { 165 | ResponseJsonObj = JsonObject; 166 | } 167 | 168 | 169 | /////////////////////////////////////////////////////////////////////////// 170 | // Response data access 171 | 172 | FString UVaRestRequestJSON::GetURL() 173 | { 174 | return HttpRequest->GetURL(); 175 | } 176 | 177 | ERequestStatus UVaRestRequestJSON::GetStatus() 178 | { 179 | return ERequestStatus((uint8)HttpRequest->GetStatus()); 180 | } 181 | 182 | int32 UVaRestRequestJSON::GetResponseCode() 183 | { 184 | return ResponseCode; 185 | } 186 | 187 | FString UVaRestRequestJSON::GetResponseHeader(const FString HeaderName) 188 | { 189 | FString Result; 190 | 191 | FString* Header = ResponseHeaders.Find(HeaderName); 192 | if (Header != nullptr) 193 | { 194 | Result = *Header; 195 | } 196 | 197 | return Result; 198 | } 199 | 200 | TArray UVaRestRequestJSON::GetAllResponseHeaders() 201 | { 202 | TArray Result; 203 | for (TMap::TConstIterator It(ResponseHeaders); It; ++It) 204 | { 205 | Result.Add(It.Key() + TEXT(": ") + It.Value()); 206 | } 207 | return Result; 208 | } 209 | 210 | 211 | ////////////////////////////////////////////////////////////////////////// 212 | // URL processing 213 | 214 | void UVaRestRequestJSON::SetURL(const FString& Url) 215 | { 216 | // Be sure to trim URL because it can break links on iOS 217 | FString TrimmedUrl = Url; 218 | 219 | #if ENGINE_MINOR_VERSION >= 18 220 | TrimmedUrl.TrimStartInline(); 221 | TrimmedUrl.TrimEndInline(); 222 | #else 223 | TrimmedUrl.Trim(); 224 | TrimmedUrl.TrimTrailing(); 225 | #endif 226 | 227 | HttpRequest->SetURL(TrimmedUrl); 228 | } 229 | 230 | void UVaRestRequestJSON::ProcessURL(const FString& Url) 231 | { 232 | SetURL(Url); 233 | ProcessRequest(); 234 | } 235 | 236 | void UVaRestRequestJSON::ApplyURL(const FString& Url, UVaRestJsonObject *&Result, UObject* WorldContextObject, FLatentActionInfo LatentInfo) 237 | { 238 | // Be sure to trim URL because it can break links on iOS 239 | FString TrimmedUrl = Url; 240 | 241 | #if ENGINE_MINOR_VERSION >= 18 242 | TrimmedUrl.TrimStartInline(); 243 | TrimmedUrl.TrimEndInline(); 244 | #else 245 | TrimmedUrl.Trim(); 246 | TrimmedUrl.TrimTrailing(); 247 | #endif 248 | 249 | HttpRequest->SetURL(TrimmedUrl); 250 | 251 | // Prepare latent action 252 | if (UWorld* World = GEngine->GetWorldFromContextObjectChecked(WorldContextObject)) 253 | { 254 | FLatentActionManager& LatentActionManager = World->GetLatentActionManager(); 255 | FVaRestLatentAction *Kont = LatentActionManager.FindExistingAction>(LatentInfo.CallbackTarget, LatentInfo.UUID); 256 | 257 | if (Kont != nullptr) 258 | { 259 | Kont->Cancel(); 260 | LatentActionManager.RemoveActionsForObject(LatentInfo.CallbackTarget); 261 | } 262 | 263 | LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, ContinueAction = new FVaRestLatentAction(this, Result, LatentInfo)); 264 | } 265 | 266 | ProcessRequest(); 267 | } 268 | 269 | void UVaRestRequestJSON::ExecuteProcessRequest() 270 | { 271 | if (HttpRequest->GetURL().Len() == 0) 272 | { 273 | UE_LOG(LogVaRest, Error, TEXT("Request execution attempt with empty URL")); 274 | return; 275 | } 276 | 277 | ProcessRequest(); 278 | } 279 | 280 | void UVaRestRequestJSON::ProcessRequest() 281 | { 282 | // Cache default settings for extended logs 283 | const UVaRestSettings* DefaultSettings = GetDefault(); 284 | 285 | // Set verb 286 | switch (RequestVerb) 287 | { 288 | case ERequestVerb::GET: 289 | HttpRequest->SetVerb(TEXT("GET")); 290 | break; 291 | 292 | case ERequestVerb::POST: 293 | HttpRequest->SetVerb(TEXT("POST")); 294 | break; 295 | 296 | case ERequestVerb::PUT: 297 | HttpRequest->SetVerb(TEXT("PUT")); 298 | break; 299 | 300 | case ERequestVerb::DEL: 301 | HttpRequest->SetVerb(TEXT("DELETE")); 302 | break; 303 | 304 | case ERequestVerb::CUSTOM: 305 | HttpRequest->SetVerb(CustomVerb); 306 | break; 307 | 308 | default: 309 | break; 310 | } 311 | 312 | // Set content-type 313 | switch (RequestContentType) 314 | { 315 | case ERequestContentType::x_www_form_urlencoded_url: 316 | { 317 | HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/x-www-form-urlencoded")); 318 | 319 | FString UrlParams = ""; 320 | uint16 ParamIdx = 0; 321 | 322 | // Loop through all the values and prepare additional url part 323 | for (auto RequestIt = RequestJsonObj->GetRootObject()->Values.CreateIterator(); RequestIt; ++RequestIt) 324 | { 325 | FString Key = RequestIt.Key(); 326 | FString Value = RequestIt.Value().Get()->AsString(); 327 | 328 | if (!Key.IsEmpty() && !Value.IsEmpty()) 329 | { 330 | UrlParams += ParamIdx == 0 ? "?" : "&"; 331 | UrlParams += UVaRestLibrary::PercentEncode(Key) + "=" + UVaRestLibrary::PercentEncode(Value); 332 | } 333 | 334 | ParamIdx++; 335 | } 336 | 337 | // Apply params 338 | HttpRequest->SetURL(HttpRequest->GetURL() + UrlParams); 339 | 340 | // Add optional string content 341 | if (!StringRequestContent.IsEmpty()) 342 | { 343 | HttpRequest->SetContentAsString(StringRequestContent); 344 | } 345 | 346 | // Check extended log to avoid security vulnerability (#133) 347 | if (DefaultSettings->bExtendedLog) 348 | { 349 | UE_LOG(LogVaRest, Log, TEXT("%s: Request (urlencoded): %s %s %s %s"), *VA_FUNC_LINE, *HttpRequest->GetVerb(), *HttpRequest->GetURL(), *UrlParams, *StringRequestContent); 350 | } 351 | else 352 | { 353 | UE_LOG(LogVaRest, Log, TEXT("%s: Request (urlencoded): %s %s (check bExtendedLog for additional data)"), *VA_FUNC_LINE, *HttpRequest->GetVerb(), *HttpRequest->GetURL()); 354 | } 355 | 356 | break; 357 | } 358 | case ERequestContentType::x_www_form_urlencoded_body: 359 | { 360 | HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/x-www-form-urlencoded")); 361 | 362 | FString UrlParams = ""; 363 | uint16 ParamIdx = 0; 364 | 365 | // Loop through all the values and prepare additional url part 366 | for (auto RequestIt = RequestJsonObj->GetRootObject()->Values.CreateIterator(); RequestIt; ++RequestIt) 367 | { 368 | FString Key = RequestIt.Key(); 369 | FString Value = RequestIt.Value().Get()->AsString(); 370 | 371 | if (!Key.IsEmpty() && !Value.IsEmpty()) 372 | { 373 | UrlParams += ParamIdx == 0 ? "" : "&"; 374 | UrlParams += UVaRestLibrary::PercentEncode(Key) + "=" + UVaRestLibrary::PercentEncode(Value); 375 | } 376 | 377 | ParamIdx++; 378 | } 379 | 380 | // Apply params 381 | HttpRequest->SetContentAsString(UrlParams); 382 | 383 | // Check extended log to avoid security vulnerability (#133) 384 | if (DefaultSettings->bExtendedLog) 385 | { 386 | UE_LOG(LogVaRest, Log, TEXT("%s: Request (url body): %s %s %s"), *VA_FUNC_LINE, *HttpRequest->GetVerb(), *HttpRequest->GetURL(), *UrlParams); 387 | } 388 | else 389 | { 390 | UE_LOG(LogVaRest, Log, TEXT("%s: Request (url body): %s %s (check bExtendedLog for additional data)"), *VA_FUNC_LINE, *HttpRequest->GetVerb(), *HttpRequest->GetURL()); 391 | } 392 | 393 | break; 394 | } 395 | case ERequestContentType::binary: 396 | { 397 | HttpRequest->SetHeader(TEXT("Content-Type"), BinaryContentType); 398 | HttpRequest->SetContent(RequestBytes); 399 | 400 | UE_LOG(LogVaRest, Log, TEXT("Request (binary): %s %s"), *HttpRequest->GetVerb(), *HttpRequest->GetURL()); 401 | 402 | break; 403 | } 404 | case ERequestContentType::json: 405 | { 406 | HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json")); 407 | 408 | // Serialize data to json string 409 | FString OutputString; 410 | TSharedRef< TJsonWriter<> > Writer = TJsonWriterFactory<>::Create(&OutputString); 411 | FJsonSerializer::Serialize(RequestJsonObj->GetRootObject().ToSharedRef(), Writer); 412 | 413 | // Set Json content 414 | HttpRequest->SetContentAsString(OutputString); 415 | 416 | UE_LOG(LogVaRest, Log, TEXT("Request (json): %s %s %sJSON(%s%s%s)JSON"), *HttpRequest->GetVerb(), *HttpRequest->GetURL(), LINE_TERMINATOR, LINE_TERMINATOR, *OutputString, LINE_TERMINATOR); 417 | 418 | break; 419 | } 420 | 421 | default: 422 | break; 423 | } 424 | 425 | // Apply additional headers 426 | for (TMap::TConstIterator It(RequestHeaders); It; ++It) 427 | { 428 | HttpRequest->SetHeader(It.Key(), It.Value()); 429 | } 430 | 431 | // Bind event 432 | HttpRequest->OnProcessRequestComplete().BindUObject(this, &UVaRestRequestJSON::OnProcessRequestComplete); 433 | 434 | // Execute the request 435 | HttpRequest->ProcessRequest(); 436 | } 437 | 438 | 439 | ////////////////////////////////////////////////////////////////////////// 440 | // Request callbacks 441 | 442 | void UVaRestRequestJSON::OnProcessRequestComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) 443 | { 444 | // Be sure that we have no data from previous response 445 | ResetResponseData(); 446 | 447 | // Check we have a response and save response code as int32 448 | if(Response.IsValid()) 449 | { 450 | ResponseCode = Response->GetResponseCode(); 451 | } 452 | 453 | // Check we have result to process futher 454 | if (!bWasSuccessful || !Response.IsValid()) 455 | { 456 | UE_LOG(LogVaRest, Error, TEXT("Request failed (%d): %s"), ResponseCode, *Request->GetURL()); 457 | 458 | // Broadcast the result event 459 | OnRequestFail.Broadcast(this); 460 | OnStaticRequestFail.Broadcast(this); 461 | 462 | return; 463 | } 464 | 465 | #if !(PLATFORM_IOS || PLATFORM_ANDROID) 466 | // Log response state 467 | UE_LOG(LogVaRest, Log, TEXT("Response (%d): %sJSON(%s%s%s)JSON"), ResponseCode, LINE_TERMINATOR, LINE_TERMINATOR, *Response->GetContentAsString(), LINE_TERMINATOR); 468 | #endif 469 | 470 | // Process response headers 471 | TArray Headers = Response->GetAllHeaders(); 472 | for (FString Header : Headers) 473 | { 474 | FString Key; 475 | FString Value; 476 | if (Header.Split(TEXT(": "), &Key, &Value)) 477 | { 478 | ResponseHeaders.Add(Key, Value); 479 | } 480 | } 481 | 482 | // Try to deserialize data to JSON 483 | const TArray& Bytes = Response->GetContent(); 484 | ResponseSize = ResponseJsonObj->DeserializeFromUTF8Bytes((const ANSICHAR*) Bytes.GetData(), Bytes.Num()); 485 | 486 | // Decide whether the request was successful 487 | bIsValidJsonResponse = bWasSuccessful && ResponseJsonObj->GetRootObject().IsValid(); 488 | 489 | if (!bIsValidJsonResponse) 490 | { 491 | // Save response data as a string 492 | ResponseContent = Response->GetContentAsString(); 493 | ResponseSize = ResponseContent.GetAllocatedSize(); 494 | } 495 | 496 | // Log errors 497 | if (!bIsValidJsonResponse) 498 | { 499 | if (!ResponseJsonObj->GetRootObject().IsValid()) 500 | { 501 | // As we assume it's recommended way to use current class, but not the only one, 502 | // it will be the warning instead of error 503 | UE_LOG(LogVaRest, Warning, TEXT("JSON could not be decoded!")); 504 | } 505 | } 506 | 507 | // Broadcast the result event 508 | OnRequestComplete.Broadcast(this); 509 | OnStaticRequestComplete.Broadcast(this); 510 | 511 | // Finish the latent action 512 | if (ContinueAction) 513 | { 514 | FVaRestLatentAction *K = ContinueAction; 515 | ContinueAction = nullptr; 516 | 517 | K->Call(ResponseJsonObj); 518 | } 519 | } 520 | 521 | 522 | ////////////////////////////////////////////////////////////////////////// 523 | // Tags 524 | 525 | void UVaRestRequestJSON::AddTag(FName Tag) 526 | { 527 | if (Tag != NAME_None) 528 | { 529 | Tags.AddUnique(Tag); 530 | } 531 | } 532 | 533 | int32 UVaRestRequestJSON::RemoveTag(FName Tag) 534 | { 535 | return Tags.Remove(Tag); 536 | } 537 | 538 | bool UVaRestRequestJSON::HasTag(FName Tag) const 539 | { 540 | return (Tag != NAME_None) && Tags.Contains(Tag); 541 | } 542 | 543 | 544 | ////////////////////////////////////////////////////////////////////////// 545 | // Data 546 | 547 | FString UVaRestRequestJSON::GetResponseContentAsString(bool bCacheResponseContent) 548 | { 549 | // Check we have valid json response 550 | if (!bIsValidJsonResponse) 551 | { 552 | // We've cached response content in OnProcessRequestComplete() 553 | return ResponseContent; 554 | } 555 | 556 | // Check we have valid response object 557 | if (!ResponseJsonObj || !ResponseJsonObj->IsValidLowLevel()) 558 | { 559 | // Discard previous cached string if we had one 560 | ResponseContent = DeprecatedResponseString; 561 | 562 | return TEXT("Invalid response"); 563 | } 564 | 565 | // Check if we should re-genetate it in runtime 566 | if (!bCacheResponseContent) 567 | { 568 | UE_LOG(LogVaRest, Warning, TEXT("%s: Use of uncashed getter could be slow"), *VA_FUNC_LINE); 569 | return ResponseJsonObj->EncodeJson(); 570 | } 571 | 572 | // Check that we haven't cached content yet 573 | if (ResponseContent == DeprecatedResponseString) 574 | { 575 | UE_LOG(LogVaRest, Warning, TEXT("%s: Response content string is cached"), *VA_FUNC_LINE); 576 | ResponseContent = ResponseJsonObj->EncodeJson(); 577 | } 578 | 579 | // Return previously cached content now 580 | return ResponseContent; 581 | } 582 | -------------------------------------------------------------------------------- /Source/VaRestPlugin/Private/VaRestJsonObject.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Vladimir Alyamkin. All Rights Reserved. 2 | 3 | #include "VaRestJsonObject.h" 4 | #include "VaRestJsonParser.h" 5 | #include "VaRestJsonValue.h" 6 | #include "VaRestPluginPrivatePCH.h" 7 | 8 | typedef TJsonWriterFactory< TCHAR, TCondensedJsonPrintPolicy > FCondensedJsonStringWriterFactory; 9 | typedef TJsonWriter< TCHAR, TCondensedJsonPrintPolicy > FCondensedJsonStringWriter; 10 | 11 | UVaRestJsonObject::UVaRestJsonObject(const class FObjectInitializer& PCIP) 12 | : Super(PCIP) 13 | { 14 | Reset(); 15 | } 16 | 17 | UVaRestJsonObject* UVaRestJsonObject::ConstructJsonObject(UObject* WorldContextObject) 18 | { 19 | return NewObject(); 20 | } 21 | 22 | void UVaRestJsonObject::Reset() 23 | { 24 | if (JsonObj.IsValid()) 25 | { 26 | JsonObj.Reset(); 27 | } 28 | 29 | JsonObj = MakeShareable(new FJsonObject()); 30 | } 31 | 32 | TSharedPtr& UVaRestJsonObject::GetRootObject() 33 | { 34 | return JsonObj; 35 | } 36 | 37 | void UVaRestJsonObject::SetRootObject(TSharedPtr& JsonObject) 38 | { 39 | if (JsonObject.IsValid()) 40 | { 41 | JsonObj = JsonObject; 42 | } 43 | else 44 | { 45 | UE_LOG(LogVaRest, Error, TEXT("%s: Trying to set invalid json object as root one. Reset now."), *VA_FUNC_LINE); 46 | Reset(); 47 | } 48 | } 49 | 50 | 51 | ////////////////////////////////////////////////////////////////////////// 52 | // Serialization 53 | 54 | FString UVaRestJsonObject::EncodeJson() const 55 | { 56 | if (!JsonObj.IsValid()) 57 | { 58 | return TEXT(""); 59 | } 60 | 61 | FString OutputString; 62 | TSharedRef< FCondensedJsonStringWriter > Writer = FCondensedJsonStringWriterFactory::Create(&OutputString); 63 | FJsonSerializer::Serialize(JsonObj.ToSharedRef(), Writer); 64 | 65 | return OutputString; 66 | } 67 | 68 | FString UVaRestJsonObject::EncodeJsonToSingleString() const 69 | { 70 | FString OutputString = EncodeJson(); 71 | 72 | // Remove line terminators 73 | OutputString.Replace(LINE_TERMINATOR, TEXT("")); 74 | 75 | // Remove tabs 76 | OutputString.Replace(LINE_TERMINATOR, TEXT("\t")); 77 | 78 | return OutputString; 79 | } 80 | 81 | bool UVaRestJsonObject::DecodeJson(const FString& JsonString) 82 | { 83 | DeserializeFromTCHARBytes(JsonString.GetCharArray().GetData(), JsonString.Len()); 84 | 85 | if (JsonObj.IsValid()) 86 | { 87 | return true; 88 | } 89 | 90 | // If we've failed to deserialize the string, we should clear our internal data 91 | Reset(); 92 | 93 | UE_LOG(LogVaRest, Error, TEXT("Json decoding failed for: %s"), *JsonString); 94 | 95 | return false; 96 | } 97 | 98 | 99 | ////////////////////////////////////////////////////////////////////////// 100 | // FJsonObject API 101 | 102 | TArray UVaRestJsonObject::GetFieldNames() const 103 | { 104 | TArray Result; 105 | 106 | if (!JsonObj.IsValid()) 107 | { 108 | return Result; 109 | } 110 | 111 | JsonObj->Values.GetKeys(Result); 112 | 113 | return Result; 114 | } 115 | 116 | bool UVaRestJsonObject::HasField(const FString& FieldName) const 117 | { 118 | if (!JsonObj.IsValid() || FieldName.IsEmpty()) 119 | { 120 | return false; 121 | } 122 | 123 | return JsonObj->HasField(FieldName); 124 | } 125 | 126 | void UVaRestJsonObject::RemoveField(const FString& FieldName) 127 | { 128 | if (!JsonObj.IsValid() || FieldName.IsEmpty()) 129 | { 130 | return; 131 | } 132 | 133 | JsonObj->RemoveField(FieldName); 134 | } 135 | 136 | UVaRestJsonValue* UVaRestJsonObject::GetField(const FString& FieldName) const 137 | { 138 | if (!JsonObj.IsValid() || FieldName.IsEmpty()) 139 | { 140 | return nullptr; 141 | } 142 | 143 | TSharedPtr NewVal = JsonObj->TryGetField(FieldName); 144 | if (NewVal.IsValid()) 145 | { 146 | UVaRestJsonValue* NewValue = NewObject(); 147 | NewValue->SetRootValue(NewVal); 148 | 149 | return NewValue; 150 | } 151 | 152 | return nullptr; 153 | } 154 | 155 | void UVaRestJsonObject::SetField(const FString& FieldName, UVaRestJsonValue* JsonValue) 156 | { 157 | if (!JsonObj.IsValid() || FieldName.IsEmpty()) 158 | { 159 | return; 160 | } 161 | 162 | JsonObj->SetField(FieldName, JsonValue->GetRootValue()); 163 | } 164 | 165 | 166 | ////////////////////////////////////////////////////////////////////////// 167 | // FJsonObject API Helpers (easy to use with simple Json objects) 168 | 169 | float UVaRestJsonObject::GetNumberField(const FString& FieldName) const 170 | { 171 | if (!JsonObj.IsValid() || !JsonObj->HasTypedField(FieldName)) 172 | { 173 | UE_LOG(LogVaRest, Warning, TEXT("No field with name %s of type Number"), *FieldName); 174 | return 0.0f; 175 | } 176 | 177 | return JsonObj->GetNumberField(FieldName); 178 | } 179 | 180 | void UVaRestJsonObject::SetNumberField(const FString& FieldName, float Number) 181 | { 182 | if (!JsonObj.IsValid() || FieldName.IsEmpty()) 183 | { 184 | return; 185 | } 186 | 187 | JsonObj->SetNumberField(FieldName, Number); 188 | } 189 | 190 | int32 UVaRestJsonObject::GetIntegerField(const FString& FieldName) const 191 | { 192 | if (!JsonObj.IsValid() || !JsonObj->HasTypedField(FieldName)) 193 | { 194 | UE_LOG(LogVaRest, Warning, TEXT("No field with name %s of type Number"), *FieldName); 195 | return 0; 196 | } 197 | 198 | return JsonObj->GetIntegerField(FieldName); 199 | } 200 | 201 | void UVaRestJsonObject::SetIntegerField(const FString& FieldName, int32 Number) 202 | { 203 | if (!JsonObj.IsValid() || FieldName.IsEmpty()) 204 | { 205 | return; 206 | } 207 | 208 | JsonObj->SetNumberField(FieldName, Number); 209 | } 210 | 211 | FString UVaRestJsonObject::GetStringField(const FString& FieldName) const 212 | { 213 | if (!JsonObj.IsValid() || !JsonObj->HasTypedField(FieldName)) 214 | { 215 | UE_LOG(LogVaRest, Warning, TEXT("No field with name %s of type String"), *FieldName); 216 | return TEXT(""); 217 | } 218 | 219 | return JsonObj->GetStringField(FieldName); 220 | } 221 | 222 | void UVaRestJsonObject::SetStringField(const FString& FieldName, const FString& StringValue) 223 | { 224 | if (!JsonObj.IsValid() || FieldName.IsEmpty()) 225 | { 226 | return; 227 | } 228 | 229 | JsonObj->SetStringField(FieldName, StringValue); 230 | } 231 | 232 | bool UVaRestJsonObject::GetBoolField(const FString& FieldName) const 233 | { 234 | if (!JsonObj.IsValid() || !JsonObj->HasTypedField(FieldName)) 235 | { 236 | UE_LOG(LogVaRest, Warning, TEXT("No field with name %s of type Boolean"), *FieldName); 237 | return false; 238 | } 239 | 240 | return JsonObj->GetBoolField(FieldName); 241 | } 242 | 243 | void UVaRestJsonObject::SetBoolField(const FString& FieldName, bool InValue) 244 | { 245 | if (!JsonObj.IsValid() || FieldName.IsEmpty()) 246 | { 247 | return; 248 | } 249 | 250 | JsonObj->SetBoolField(FieldName, InValue); 251 | } 252 | 253 | TArray UVaRestJsonObject::GetArrayField(const FString& FieldName) const 254 | { 255 | TArray OutArray; 256 | if (!JsonObj.IsValid() || FieldName.IsEmpty()) 257 | { 258 | return OutArray; 259 | } 260 | 261 | if (!JsonObj->HasTypedField(FieldName)) 262 | { 263 | UE_LOG(LogVaRest, Warning, TEXT("%s: No field with name %s of type Array"), *VA_FUNC_LINE, *FieldName); 264 | return OutArray; 265 | } 266 | 267 | TArray< TSharedPtr > ValArray = JsonObj->GetArrayField(FieldName); 268 | for (auto Value : ValArray) 269 | { 270 | UVaRestJsonValue* NewValue = NewObject(); 271 | NewValue->SetRootValue(Value); 272 | 273 | OutArray.Add(NewValue); 274 | } 275 | 276 | return OutArray; 277 | } 278 | 279 | void UVaRestJsonObject::SetArrayField(const FString& FieldName, const TArray& InArray) 280 | { 281 | if (!JsonObj.IsValid() || FieldName.IsEmpty()) 282 | { 283 | return; 284 | } 285 | 286 | TArray< TSharedPtr > ValArray; 287 | 288 | // Process input array and COPY original values 289 | for (auto InVal : InArray) 290 | { 291 | TSharedPtr JsonVal = InVal->GetRootValue(); 292 | 293 | switch (InVal->GetType()) 294 | { 295 | case EVaJson::None: 296 | break; 297 | 298 | case EVaJson::Null: 299 | ValArray.Add(MakeShareable(new FJsonValueNull())); 300 | break; 301 | 302 | case EVaJson::String: 303 | ValArray.Add(MakeShareable(new FJsonValueString(JsonVal->AsString()))); 304 | break; 305 | 306 | case EVaJson::Number: 307 | ValArray.Add(MakeShareable(new FJsonValueNumber(JsonVal->AsNumber()))); 308 | break; 309 | 310 | case EVaJson::Boolean: 311 | ValArray.Add(MakeShareable(new FJsonValueBoolean(JsonVal->AsBool()))); 312 | break; 313 | 314 | case EVaJson::Array: 315 | ValArray.Add(MakeShareable(new FJsonValueArray(JsonVal->AsArray()))); 316 | break; 317 | 318 | case EVaJson::Object: 319 | ValArray.Add(MakeShareable(new FJsonValueObject(JsonVal->AsObject()))); 320 | break; 321 | 322 | default: 323 | break; 324 | } 325 | } 326 | 327 | JsonObj->SetArrayField(FieldName, ValArray); 328 | } 329 | 330 | void UVaRestJsonObject::MergeJsonObject(UVaRestJsonObject* InJsonObject, bool Overwrite) 331 | { 332 | if (!InJsonObject || !InJsonObject->IsValidLowLevel()) 333 | { 334 | return; 335 | } 336 | 337 | TArray Keys = InJsonObject->GetFieldNames(); 338 | 339 | for (auto Key : Keys) 340 | { 341 | if (Overwrite == false && HasField(Key)) 342 | { 343 | continue; 344 | } 345 | 346 | SetField(Key, InJsonObject->GetField(Key)); 347 | } 348 | } 349 | 350 | UVaRestJsonObject* UVaRestJsonObject::GetObjectField(const FString& FieldName) const 351 | { 352 | if (!JsonObj.IsValid() || !JsonObj->HasTypedField(FieldName)) 353 | { 354 | UE_LOG(LogVaRest, Warning, TEXT("%s: No field with name %s of type Object"), *VA_FUNC_LINE, *FieldName); 355 | return nullptr; 356 | } 357 | 358 | TSharedPtr JsonObjField = JsonObj->GetObjectField(FieldName); 359 | 360 | UVaRestJsonObject* OutRestJsonObj = NewObject(); 361 | OutRestJsonObj->SetRootObject(JsonObjField); 362 | 363 | return OutRestJsonObj; 364 | } 365 | 366 | void UVaRestJsonObject::SetObjectField(const FString& FieldName, UVaRestJsonObject* JsonObject) 367 | { 368 | if (!JsonObj.IsValid() || FieldName.IsEmpty() || !JsonObject || !JsonObject->IsValidLowLevel()) 369 | { 370 | return; 371 | } 372 | 373 | JsonObj->SetObjectField(FieldName, JsonObject->GetRootObject()); 374 | } 375 | 376 | 377 | ////////////////////////////////////////////////////////////////////////// 378 | // Array fields helpers (uniform arrays) 379 | 380 | TArray UVaRestJsonObject::GetNumberArrayField(const FString& FieldName) const 381 | { 382 | TArray NumberArray; 383 | if (!JsonObj.IsValid() || !JsonObj->HasTypedField(FieldName) || FieldName.IsEmpty()) 384 | { 385 | UE_LOG(LogVaRest, Warning, TEXT("%s: No field with name %s of type Array"), *VA_FUNC_LINE, *FieldName); 386 | return NumberArray; 387 | } 388 | 389 | TArray > JsonArrayValues = JsonObj->GetArrayField(FieldName); 390 | for (TArray >::TConstIterator It(JsonArrayValues); It; ++It) 391 | { 392 | auto Value = (*It).Get(); 393 | if (Value->Type != EJson::Number) 394 | { 395 | UE_LOG(LogVaRest, Error, TEXT("Not Number element in array with field name %s"), *FieldName); 396 | } 397 | 398 | NumberArray.Add((*It)->AsNumber()); 399 | } 400 | 401 | return NumberArray; 402 | } 403 | 404 | void UVaRestJsonObject::SetNumberArrayField(const FString& FieldName, const TArray& NumberArray) 405 | { 406 | if (!JsonObj.IsValid() || FieldName.IsEmpty()) 407 | { 408 | return; 409 | } 410 | 411 | TArray< TSharedPtr > EntriesArray; 412 | 413 | for (auto Number : NumberArray) 414 | { 415 | EntriesArray.Add(MakeShareable(new FJsonValueNumber(Number))); 416 | } 417 | 418 | JsonObj->SetArrayField(FieldName, EntriesArray); 419 | } 420 | 421 | TArray UVaRestJsonObject::GetStringArrayField(const FString& FieldName) const 422 | { 423 | TArray StringArray; 424 | if (!JsonObj.IsValid() || !JsonObj->HasTypedField(FieldName) || FieldName.IsEmpty()) 425 | { 426 | UE_LOG(LogVaRest, Warning, TEXT("%s: No field with name %s of type Array"), *VA_FUNC_LINE, *FieldName); 427 | return StringArray; 428 | } 429 | 430 | TArray > JsonArrayValues = JsonObj->GetArrayField(FieldName); 431 | for (TArray >::TConstIterator It(JsonArrayValues); It; ++It) 432 | { 433 | auto Value = (*It).Get(); 434 | if (Value->Type != EJson::String) 435 | { 436 | UE_LOG(LogVaRest, Error, TEXT("Not String element in array with field name %s"), *FieldName); 437 | } 438 | 439 | StringArray.Add((*It)->AsString()); 440 | } 441 | 442 | return StringArray; 443 | } 444 | 445 | void UVaRestJsonObject::SetStringArrayField(const FString& FieldName, const TArray& StringArray) 446 | { 447 | if (!JsonObj.IsValid() || FieldName.IsEmpty()) 448 | { 449 | return; 450 | } 451 | 452 | TArray< TSharedPtr > EntriesArray; 453 | for (auto String : StringArray) 454 | { 455 | EntriesArray.Add(MakeShareable(new FJsonValueString(String))); 456 | } 457 | 458 | JsonObj->SetArrayField(FieldName, EntriesArray); 459 | } 460 | 461 | TArray UVaRestJsonObject::GetBoolArrayField(const FString& FieldName) const 462 | { 463 | TArray BoolArray; 464 | if (!JsonObj.IsValid() || !JsonObj->HasTypedField(FieldName) || FieldName.IsEmpty()) 465 | { 466 | UE_LOG(LogVaRest, Warning, TEXT("%s: No field with name %s of type Array"), *VA_FUNC_LINE, *FieldName); 467 | return BoolArray; 468 | } 469 | 470 | TArray > JsonArrayValues = JsonObj->GetArrayField(FieldName); 471 | for (TArray >::TConstIterator It(JsonArrayValues); It; ++It) 472 | { 473 | auto Value = (*It).Get(); 474 | if (Value->Type != EJson::Boolean) 475 | { 476 | UE_LOG(LogVaRest, Error, TEXT("Not Boolean element in array with field name %s"), *FieldName); 477 | } 478 | 479 | BoolArray.Add((*It)->AsBool()); 480 | } 481 | 482 | return BoolArray; 483 | } 484 | 485 | void UVaRestJsonObject::SetBoolArrayField(const FString& FieldName, const TArray& BoolArray) 486 | { 487 | if (!JsonObj.IsValid() || FieldName.IsEmpty()) 488 | { 489 | return; 490 | } 491 | 492 | TArray< TSharedPtr > EntriesArray; 493 | for (auto Boolean : BoolArray) 494 | { 495 | EntriesArray.Add(MakeShareable(new FJsonValueBoolean(Boolean))); 496 | } 497 | 498 | JsonObj->SetArrayField(FieldName, EntriesArray); 499 | } 500 | 501 | TArray UVaRestJsonObject::GetObjectArrayField(const FString& FieldName) const 502 | { 503 | TArray OutArray; 504 | if (!JsonObj.IsValid() || !JsonObj->HasTypedField(FieldName) || FieldName.IsEmpty()) 505 | { 506 | UE_LOG(LogVaRest, Warning, TEXT("%s: No field with name %s of type Array"), *VA_FUNC_LINE, *FieldName); 507 | return OutArray; 508 | } 509 | 510 | TArray< TSharedPtr > ValArray = JsonObj->GetArrayField(FieldName); 511 | for (auto Value : ValArray) 512 | { 513 | if (Value->Type != EJson::Object) 514 | { 515 | UE_LOG(LogVaRest, Error, TEXT("Not Object element in array with field name %s"), *FieldName); 516 | } 517 | 518 | TSharedPtr NewObj = Value->AsObject(); 519 | 520 | UVaRestJsonObject* NewJson = NewObject(); 521 | NewJson->SetRootObject(NewObj); 522 | 523 | OutArray.Add(NewJson); 524 | } 525 | 526 | return OutArray; 527 | } 528 | 529 | void UVaRestJsonObject::SetObjectArrayField(const FString& FieldName, const TArray& ObjectArray) 530 | { 531 | if (!JsonObj.IsValid() || FieldName.IsEmpty()) 532 | { 533 | return; 534 | } 535 | 536 | TArray< TSharedPtr > EntriesArray; 537 | for (auto Value : ObjectArray) 538 | { 539 | EntriesArray.Add(MakeShareable(new FJsonValueObject(Value->GetRootObject()))); 540 | } 541 | 542 | JsonObj->SetArrayField(FieldName, EntriesArray); 543 | } 544 | 545 | ////////////////////////////////////////////////////////////////////////// 546 | // Deserialize 547 | 548 | int32 UVaRestJsonObject::DeserializeFromUTF8Bytes(const ANSICHAR* Bytes, int32 Size) 549 | { 550 | FJSONReader Reader; 551 | 552 | #if ENGINE_MINOR_VERSION >= 19 553 | // Get destLen 554 | int32 DestinationLength = FUTF8ToTCHAR_Convert::ConvertedLength(Bytes, Size); 555 | TCHAR* DestinationBuffer = new TCHAR[DestinationLength]; 556 | 557 | // CONVERT to TCHAR string 558 | FUTF8ToTCHAR_Convert::Convert(DestinationBuffer, DestinationLength, Bytes, Size); 559 | 560 | int32 i = 0; 561 | while (i < DestinationLength) 562 | { 563 | if (!Reader.Read(DestinationBuffer[i++])) 564 | { 565 | break; 566 | } 567 | } 568 | #else 569 | const ANSICHAR* EndByte = Bytes + Size; 570 | while (Bytes < EndByte) 571 | { 572 | TCHAR Char = FUTF8ToTCHAR_Convert::utf8codepoint(&Bytes); 573 | 574 | if (Char > 0xFFFF) 575 | { 576 | Char = UNICODE_BOGUS_CHAR_CODEPOINT; 577 | } 578 | 579 | if (!Reader.Read(Char)) 580 | { 581 | break; 582 | } 583 | } 584 | #endif 585 | 586 | SetRootObject(Reader.State.Root); 587 | return Reader.State.Size; 588 | } 589 | 590 | int32 UVaRestJsonObject::DeserializeFromTCHARBytes(const TCHAR* Bytes, int32 Size) 591 | { 592 | FJSONReader Reader; 593 | 594 | int32 i = 0; 595 | while (i < Size) 596 | { 597 | if (!Reader.Read(Bytes[i++])) 598 | { 599 | break; 600 | } 601 | } 602 | 603 | SetRootObject(Reader.State.Root); 604 | return Reader.State.Size; 605 | } 606 | 607 | void UVaRestJsonObject::DecodeFromArchive(TUniquePtr& Reader) 608 | { 609 | FArchive& Ar = (*Reader.Get()); 610 | uint8 SymbolBytes[2]; 611 | 612 | // Read first two bytes 613 | Ar << SymbolBytes[0]; 614 | Ar << SymbolBytes[1]; 615 | 616 | bool bIsIntelByteOrder = true; 617 | 618 | if(SymbolBytes[0] == 0xff && SymbolBytes[1] == 0xfe) 619 | { 620 | // Unicode Intel byte order. Less 1 for the FFFE header, additional 1 for null terminator. 621 | bIsIntelByteOrder = true; 622 | } 623 | else if(SymbolBytes[0] == 0xfe && SymbolBytes[1] == 0xff) 624 | { 625 | // Unicode non-Intel byte order. Less 1 for the FFFE header, additional 1 for null terminator. 626 | bIsIntelByteOrder = false; 627 | } 628 | 629 | FJSONReader JsonReader; 630 | TCHAR Char; 631 | 632 | while (!Ar.AtEnd()) 633 | { 634 | Ar << SymbolBytes[0]; 635 | 636 | if (Ar.AtEnd()) 637 | { 638 | break; 639 | } 640 | 641 | Ar << SymbolBytes[1]; 642 | 643 | if (bIsIntelByteOrder) 644 | { 645 | Char = CharCast((UCS2CHAR)((uint16)SymbolBytes[0] + (uint16)SymbolBytes[1] * 256)); 646 | } 647 | else 648 | { 649 | Char = CharCast((UCS2CHAR)((uint16)SymbolBytes[1] + (uint16)SymbolBytes[0] * 256)); 650 | } 651 | 652 | if (!JsonReader.Read(Char)) 653 | { 654 | break; 655 | } 656 | } 657 | 658 | SetRootObject(JsonReader.State.Root); 659 | 660 | if (!Ar.Close()) 661 | { 662 | UE_LOG(LogVaRest, Error, TEXT("UVaRestJsonObject::DecodeFromArchive: Error! Can't close file!")); 663 | } 664 | 665 | } 666 | 667 | bool UVaRestJsonObject::WriteToFile(const FString& Path) 668 | { 669 | if (JsonObj.IsValid()) 670 | { 671 | TUniquePtr FileWriter(IFileManager::Get().CreateFileWriter(*Path)); 672 | if (!FileWriter) 673 | { 674 | return false; 675 | } 676 | 677 | FArchive& Ar = *FileWriter.Get(); 678 | 679 | UCS2CHAR BOM = UNICODE_BOM; 680 | Ar.Serialize( &BOM, sizeof(UCS2CHAR) ); 681 | 682 | FString Str = FString(TEXT("{")); 683 | WriteStringToArchive(Ar, *Str, Str.Len()); 684 | 685 | int32 ElementCount = 0; 686 | FJSONWriter JsonWriter; 687 | for (auto JsonObjectValuePair : JsonObj->Values) 688 | { 689 | Str = FString(TEXT("\"")); 690 | WriteStringToArchive(Ar, *Str, Str.Len()); 691 | 692 | const TCHAR* BufferPtr = *JsonObjectValuePair.Key; 693 | for (int i = 0; i < JsonObjectValuePair.Key.Len(); ++i) 694 | { 695 | Str = FString(1, &BufferPtr[i]); 696 | #if PLATFORM_WINDOWS 697 | WriteStringToArchive(Ar, *Str, Str.Len() - 1); 698 | #else 699 | WriteStringToArchive(Ar, *Str, Str.Len()); 700 | #endif 701 | } 702 | 703 | Str = FString(TEXT("\"")); 704 | WriteStringToArchive(Ar, *Str, Str.Len()); 705 | 706 | Str = FString(TEXT(":")); 707 | WriteStringToArchive(Ar, *Str, Str.Len()); 708 | 709 | ++ElementCount; 710 | 711 | JsonWriter.Write(JsonObjectValuePair.Value, FileWriter.Get(), ElementCount >= JsonObj->Values.Num()); 712 | } 713 | 714 | Str = FString(TEXT("}")); 715 | WriteStringToArchive(Ar, *Str, Str.Len()); 716 | 717 | FileWriter->Close(); 718 | 719 | return true; 720 | } 721 | else 722 | { 723 | UE_LOG(LogVaRest, Error, TEXT("UVaRestJsonObject::WriteToFile: Root object is invalid!")); 724 | return false; 725 | } 726 | } 727 | 728 | bool UVaRestJsonObject::WriteStringToArchive( FArchive& Ar, const TCHAR* StrPtr, int64 Len) 729 | { 730 | auto Src = StringCast(StrPtr, Len); 731 | Ar.Serialize( (UCS2CHAR*)Src.Get(), Src.Length() * sizeof(UCS2CHAR) ); 732 | 733 | return true; 734 | } 735 | -------------------------------------------------------------------------------- /Source/VaRestPlugin/Private/VaRestJsonParser.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2017 Mail.Ru Group. All Rights Reserved. 2 | 3 | #include "VaRestJsonParser.h" 4 | #include "VaRestJsonObject.h" 5 | #include "Dom/JsonObject.h" 6 | #include "Dom/JsonValue.h" 7 | #include "Logging/LogMacros.h" 8 | 9 | FJSONState::FJSONState() 10 | : Notation(EJSONNotation::NONE) 11 | , bEscape(false) 12 | , bError(false) 13 | , Quote(UNICODE_BOGUS_CHAR_CODEPOINT) 14 | { 15 | Key.Reserve(1024); 16 | Data.Reserve(4096); 17 | 18 | Root = TSharedPtr(nullptr); 19 | 20 | Objects.Reserve(64); 21 | Tokens.Reserve(64); 22 | 23 | Tokens.Add(EJSONToken::ROOT); 24 | 25 | Size = 0; 26 | } 27 | 28 | EJSONToken FJSONState::GetToken(int32 Index) 29 | { 30 | return Tokens.Num() > Index ? Tokens.Last(Index) : EJSONToken::ERROR; 31 | } 32 | 33 | bool FJSONState::CheckTokens(EJSONToken T1) 34 | { 35 | return T1 == GetToken(0); 36 | } 37 | 38 | bool FJSONState::CheckTokens(EJSONToken T1, EJSONToken T2) 39 | { 40 | return T1 == GetToken(1) && T2 == GetToken(0); 41 | } 42 | 43 | bool FJSONState::CheckTokens(EJSONToken T1, EJSONToken T2, EJSONToken T3) 44 | { 45 | return T1 == GetToken(2) && T2 == GetToken(1) && T3 == GetToken(0); 46 | } 47 | 48 | void FJSONState::PopToken(int32 Num) 49 | { 50 | if (Num > 0) 51 | { 52 | if (Tokens.Num() >= Num) 53 | { 54 | Tokens.RemoveAt(Tokens.Num() - Num, Num, false); 55 | } 56 | else 57 | { 58 | bError = true; 59 | } 60 | } 61 | 62 | Notation = EJSONNotation::NONE; 63 | } 64 | 65 | void FJSONState::PopObject() 66 | { 67 | if (Objects.Num() > 0) 68 | { 69 | auto Object = Objects.Pop(false); 70 | if (Object->Type == EJson::Object) 71 | { 72 | return; 73 | } 74 | } 75 | 76 | bError = true; 77 | } 78 | 79 | void FJSONState::PopArray() 80 | { 81 | if (Objects.Num() > 0) 82 | { 83 | auto Object = Objects.Pop(false); 84 | if (Object->Type == EJson::Array) 85 | { 86 | return; 87 | } 88 | } 89 | 90 | bError = true; 91 | } 92 | 93 | void FJSONState::PopValue(bool bCheckType) 94 | { 95 | if (Objects.Num() > 0) 96 | { 97 | auto Value = Objects.Last(0); 98 | if (Value->Type == EJson::Object || Value->Type == EJson::Array) 99 | { 100 | if (bCheckType) 101 | { 102 | bError = true; 103 | } 104 | } 105 | else 106 | { 107 | Objects.Pop(false); 108 | if (Objects.Num() > 0) 109 | { 110 | switch(Value->Type) 111 | { 112 | case EJson::Null: 113 | { 114 | auto LowerCase = Data.ToLower(); 115 | if (LowerCase != TEXT("null")) 116 | { 117 | bError = true; 118 | } 119 | break; 120 | } 121 | case EJson::String: 122 | { 123 | FJsonValueNonConstString* JsonValueString = ((FJsonValueNonConstString*) Value.Get()); 124 | JsonValueString->AsNonConstString() = Data; 125 | JsonValueString->AsNonConstString().Shrink(); 126 | Size += JsonValueString->AsNonConstString().GetAllocatedSize(); 127 | break; 128 | } 129 | case EJson::Number: 130 | { 131 | FString LowerCase = Data.ToLower(); 132 | int32 ePosition = INDEX_NONE; 133 | LowerCase.FindChar('e', ePosition); 134 | if (ePosition == INDEX_NONE) 135 | { 136 | if (LowerCase.IsNumeric()) 137 | { 138 | ((FJsonValueNonConstNumber*) Value.Get())->AsNonConstNumber() = FCString::Atod(*LowerCase); 139 | } 140 | else 141 | { 142 | bError = true; 143 | } 144 | } 145 | else if (LowerCase.Len() > ePosition + 2) 146 | { 147 | FString Left = LowerCase.Left(ePosition); 148 | FString Rigth = LowerCase.Right(LowerCase.Len() - ePosition - 1); 149 | if (Left.IsNumeric() && Rigth.IsNumeric()) 150 | { 151 | ((FJsonValueNonConstNumber*) Value.Get())->AsNonConstNumber() = FCString::Atod(*Left) * FMath::Pow(10.f, FCString::Atoi(*Rigth)); 152 | } 153 | else 154 | { 155 | bError = true; 156 | } 157 | } 158 | else 159 | { 160 | bError = true; 161 | } 162 | break; 163 | } 164 | case EJson::Boolean: 165 | { 166 | auto LowerCase = Data.ToLower(); 167 | if (LowerCase == TEXT("true")) 168 | { 169 | ((FJsonValueNonConstBoolean*) Value.Get())->AsNonConstBool() = true; 170 | } 171 | else if (LowerCase == TEXT("false")) 172 | { 173 | ((FJsonValueNonConstBoolean*) Value.Get())->AsNonConstBool() = false; 174 | } 175 | else 176 | { 177 | bError = true; 178 | } 179 | break; 180 | } 181 | default: 182 | { 183 | bError = true; 184 | return; 185 | } 186 | } 187 | 188 | ClearData(); 189 | 190 | auto Container = Objects.Last(0); 191 | if (Container->Type == EJson::Object) 192 | { 193 | if (Key.Len() > 0) 194 | { 195 | FString KeyCopy = Key; 196 | KeyCopy.Shrink(); 197 | Container->AsObject()->SetField(KeyCopy, Value); 198 | Size += KeyCopy.GetAllocatedSize(); 199 | ClearKey(); 200 | } 201 | else 202 | { 203 | bError = true; 204 | } 205 | } 206 | else if (Container->Type == EJson::Array) 207 | { 208 | ((FJsonValueNonConstArray*) Container.Get())->AsNonConstArray().Add(Value); 209 | } 210 | else 211 | { 212 | bError = true; 213 | } 214 | } 215 | else 216 | { 217 | bError = true; 218 | } 219 | } 220 | } 221 | else 222 | { 223 | bError = true; 224 | } 225 | } 226 | 227 | FJsonValue* FJSONState::GetLast() 228 | { 229 | if (Objects.Num() > 0) 230 | { 231 | return Objects.Last(0).Get(); 232 | } 233 | bError = true; 234 | return nullptr; 235 | } 236 | 237 | FJsonValueObject* FJSONState::GetObject() 238 | { 239 | FJsonValue* Value = GetLast(); 240 | if (Value != nullptr && Value->Type == EJson::Object) 241 | { 242 | return (FJsonValueObject*) Value; 243 | } 244 | bError = true; 245 | return nullptr; 246 | } 247 | 248 | FJsonValueNonConstArray* FJSONState::GetArray() 249 | { 250 | FJsonValue* Value = GetLast(); 251 | if (Value != nullptr && Value->Type == EJson::Array) 252 | { 253 | return (FJsonValueNonConstArray*) Value; 254 | } 255 | bError = true; 256 | return nullptr; 257 | } 258 | 259 | TSharedPtr FJSONState::PushObject() 260 | { 261 | TSharedPtr Result(new FJsonValueObject(TSharedPtr(new FJsonObject()))); 262 | Objects.Add(Result); 263 | Size += sizeof(TSharedPtr) + sizeof(FJsonValueObject); 264 | return Result; 265 | } 266 | 267 | TSharedPtr FJSONState::PushObject(TSharedPtr Object) 268 | { 269 | TSharedPtr Result(new FJsonValueObject(Object)); 270 | Objects.Add(Result); 271 | Size += sizeof(TSharedPtr) + sizeof(FJsonValueObject); 272 | return Result; 273 | } 274 | 275 | TSharedPtr FJSONState::PushArray() 276 | { 277 | TArray> Empty; 278 | TSharedPtr Result(new FJsonValueNonConstArray(Empty)); 279 | Objects.Add(Result); 280 | Size += sizeof(TSharedPtr) + sizeof(FJsonValueNonConstArray); 281 | return Result; 282 | } 283 | 284 | TSharedPtr FJSONState::PushBoolean() 285 | { 286 | TSharedPtr Result(new FJsonValueNonConstBoolean(false)); 287 | Objects.Add(Result); 288 | Size += sizeof(TSharedPtr) + sizeof(FJsonValueNonConstBoolean); 289 | return Result; 290 | } 291 | 292 | TSharedPtr FJSONState::PushNull() 293 | { 294 | TSharedPtr Result(new FJsonValueNull()); 295 | Objects.Add(Result); 296 | Size += sizeof(TSharedPtr) + sizeof(FJsonValueNull); 297 | return Result; 298 | } 299 | 300 | TSharedPtr FJSONState::PushNumber() 301 | { 302 | TSharedPtr Result(new FJsonValueNonConstNumber(0.f)); 303 | Objects.Add(Result); 304 | Size += sizeof(TSharedPtr) + sizeof(FJsonValueNonConstNumber); 305 | return Result; 306 | } 307 | 308 | TSharedPtr FJSONState::PushString() 309 | { 310 | TSharedPtr Result(new FJsonValueNonConstString(TEXT(""))); 311 | Objects.Add(Result); 312 | Size += sizeof(TSharedPtr) + sizeof(FJsonValueNonConstString); 313 | return Result; 314 | } 315 | 316 | void FJSONState::ClearData() 317 | { 318 | Data.Empty(Data.GetCharArray().Max()); 319 | } 320 | 321 | void FJSONState::ClearKey() 322 | { 323 | Key.Empty(Key.GetCharArray().Max()); 324 | } 325 | 326 | void FJSONState::DataToKey() 327 | { 328 | ClearKey(); 329 | Key += Data; 330 | ClearData(); 331 | } 332 | 333 | void FJSONState::Error() 334 | { 335 | bError = true; 336 | } 337 | 338 | FJSONReader::FJSONReader() 339 | { 340 | 341 | } 342 | 343 | bool FJSONReader::IsNewLine(const TCHAR& Char) 344 | { 345 | return Char == '\n'; 346 | } 347 | 348 | bool FJSONReader::IsSpace(const TCHAR& Char) 349 | { 350 | return IsNewLine(Char) || Char == ' ' || Char == '\t' || Char == '\r'; 351 | } 352 | 353 | bool FJSONReader::FindToken(const TCHAR& Char) 354 | { 355 | if (State.bEscape) 356 | { 357 | return false; 358 | } 359 | 360 | if (State.Notation != EJSONNotation::STRING) 361 | { 362 | switch(Char) 363 | { 364 | case '{': State.Tokens.Add(EJSONToken::CURLY_BEGIN); return true; 365 | case '}': State.Tokens.Add(EJSONToken::CURLY_END); return true; 366 | case '[': State.Tokens.Add(EJSONToken::SQUARE_BEGIN); return true; 367 | case ']': State.Tokens.Add(EJSONToken::SQUARE_END); return true; 368 | case ',': State.Tokens.Add(EJSONToken::COMMA); return true; 369 | case ':': State.Tokens.Add(EJSONToken::COLON); return true; 370 | } 371 | } 372 | return false; 373 | } 374 | 375 | void FJSONReader::UpdateNotation() 376 | { 377 | switch(State.GetToken()) 378 | { 379 | case EJSONToken::ROOT: 380 | { 381 | return; 382 | } 383 | case EJSONToken::CURLY_BEGIN: 384 | { 385 | if (State.CheckTokens(EJSONToken::SQUARE_BEGIN, EJSONToken::CURLY_BEGIN)) // Object in array "[{" 386 | { 387 | State.Notation = EJSONNotation::OBJECT; 388 | auto Value = State.GetArray(); 389 | if (Value != nullptr) 390 | { 391 | Value->AsNonConstArray().Add(State.PushObject()); 392 | } 393 | else 394 | { 395 | State.Error(); 396 | } 397 | } 398 | else if (State.CheckTokens(EJSONToken::CURLY_BEGIN, EJSONToken::COLON, EJSONToken::CURLY_BEGIN)) // Object in key "{:{" 399 | { 400 | if (State.Key.Len() > 0) 401 | { 402 | State.Notation = EJSONNotation::OBJECT; 403 | auto Value = State.GetObject(); 404 | if (Value != nullptr) 405 | { 406 | Value->AsObject()->SetField(State.Key, State.PushObject()); 407 | State.ClearKey(); 408 | } 409 | else 410 | { 411 | State.Error(); 412 | } 413 | } 414 | else 415 | { 416 | State.Error(); 417 | } 418 | } 419 | else if (State.CheckTokens(EJSONToken::ROOT, EJSONToken::CURLY_BEGIN)) // Root object "{" 420 | { 421 | if (State.Root.IsValid()) 422 | { 423 | State.Error(); 424 | } 425 | else 426 | { 427 | State.Root = TSharedPtr(new FJsonObject()); 428 | State.PushObject(State.Root); // add root object 429 | State.Notation = EJSONNotation::OBJECT; 430 | } 431 | } 432 | else 433 | { 434 | State.Error(); 435 | } 436 | break; 437 | } 438 | case EJSONToken::CURLY_END: 439 | { 440 | if (State.CheckTokens(EJSONToken::CURLY_BEGIN, EJSONToken::CURLY_END)) // Close object "{}" 441 | { 442 | State.PopToken(2); // pop "{}" 443 | State.PopObject(); // remove object 444 | } 445 | else if (State.CheckTokens(EJSONToken::CURLY_BEGIN, EJSONToken::COLON, EJSONToken::CURLY_END)) // Close object "{:}" 446 | { 447 | State.PopToken(3); // pop "{:}" 448 | State.PopValue(); // remove value 449 | State.PopObject(); // remove object 450 | } 451 | else 452 | { 453 | State.Error(); 454 | } 455 | 456 | if (State.CheckTokens(EJSONToken::COLON)) // Object in object ":" 457 | { 458 | State.PopToken(1); // pop ":" 459 | } 460 | 461 | State.Notation = EJSONNotation::SKIP; 462 | 463 | break; 464 | } 465 | case EJSONToken::SQUARE_BEGIN: 466 | { 467 | if (State.CheckTokens(EJSONToken::SQUARE_BEGIN, EJSONToken::SQUARE_BEGIN)) // Array in array "[[" 468 | { 469 | State.Notation = EJSONNotation::ARRAY; 470 | auto Value = State.GetArray(); 471 | if (Value != nullptr) 472 | { 473 | Value->AsNonConstArray().Add(State.PushArray()); 474 | } 475 | else 476 | { 477 | State.Error(); 478 | } 479 | } 480 | else if (State.CheckTokens(EJSONToken::CURLY_BEGIN, EJSONToken::COLON, EJSONToken::SQUARE_BEGIN)) // Array in key "{:[" 481 | { 482 | State.Notation = EJSONNotation::ARRAY; 483 | if (State.Key.Len() > 0) 484 | { 485 | auto Value = State.GetObject(); 486 | if (Value != nullptr) 487 | { 488 | Value->AsObject()->SetField(State.Key, State.PushArray()); 489 | State.ClearKey(); 490 | } 491 | else 492 | { 493 | State.Error(); 494 | } 495 | } 496 | else 497 | { 498 | State.Error(); 499 | } 500 | } 501 | else if (State.CheckTokens(EJSONToken::ROOT, EJSONToken::SQUARE_BEGIN)) // Root array "{" 502 | { 503 | State.Error(); // Not support 504 | } 505 | else 506 | { 507 | State.Error(); 508 | } 509 | break; 510 | } 511 | case EJSONToken::SQUARE_END: 512 | { 513 | if (State.CheckTokens(EJSONToken::SQUARE_BEGIN, EJSONToken::SQUARE_END)) // Close array "[]" 514 | { 515 | State.PopToken(2); // remove token "[]" 516 | State.PopValue(false); // remove value if exists 517 | State.PopArray(); // remove array 518 | 519 | if (State.CheckTokens(EJSONToken::COLON)) // Array in object ":" 520 | { 521 | State.PopToken(1); // pop ":" 522 | } 523 | } 524 | else 525 | { 526 | State.Error(); 527 | } 528 | 529 | State.Notation = EJSONNotation::SKIP; 530 | 531 | break; 532 | } 533 | case EJSONToken::COMMA: 534 | { 535 | if (State.CheckTokens(EJSONToken::CURLY_BEGIN, EJSONToken::COLON, EJSONToken::COMMA)) // Next record in object "{:," 536 | { 537 | State.PopToken(2); // remove token ":," 538 | State.PopValue(false); // remove value 539 | State.Notation = EJSONNotation::OBJECT; 540 | } 541 | else if (State.CheckTokens(EJSONToken::CURLY_BEGIN, EJSONToken::COMMA)) // Next record in object "{," 542 | { 543 | State.PopToken(1); // remove token "," 544 | State.Notation = EJSONNotation::OBJECT; 545 | } 546 | else if (State.CheckTokens(EJSONToken::SQUARE_BEGIN, EJSONToken::COMMA)) // Next record in array "[," 547 | { 548 | State.PopToken(1); // remove token "," 549 | State.PopValue(false); // remove value 550 | State.Notation = EJSONNotation::ARRAY; 551 | } 552 | else 553 | { 554 | State.Error(); 555 | } 556 | break; 557 | } 558 | case EJSONToken::COLON: 559 | { 560 | if (State.CheckTokens(EJSONToken::CURLY_BEGIN, EJSONToken::COLON)) // Object key close "{:" 561 | { 562 | State.Notation = EJSONNotation::OBJECT; 563 | if (State.Data.Len() > 0) 564 | { 565 | State.DataToKey(); 566 | } 567 | else 568 | { 569 | State.Error(); 570 | } 571 | } 572 | else 573 | { 574 | State.Error(); 575 | } 576 | break; 577 | } 578 | case EJSONToken::ERROR: 579 | { 580 | State.Error(); 581 | break; 582 | } 583 | } 584 | 585 | if (!State.bError && State.Notation == EJSONNotation::NONE) 586 | { 587 | UpdateNotation(); 588 | } 589 | } 590 | 591 | void FJSONReader::ReadAsString(const TCHAR& Char) 592 | { 593 | if (IsNewLine(Char)) 594 | { 595 | State.Error(); 596 | return; 597 | } 598 | 599 | if (!State.bEscape && State.Quote == Char) 600 | { 601 | State.Quote = UNICODE_BOGUS_CHAR_CODEPOINT; 602 | State.Notation = EJSONNotation::SKIP; 603 | } 604 | else 605 | { 606 | if (State.bEscape) 607 | { 608 | switch(Char) 609 | { 610 | case 'n': State.Data.AppendChar('\n'); break; 611 | case 't': State.Data.AppendChar('\t'); break; 612 | default: State.Data.AppendChar(Char); break; 613 | } 614 | } 615 | else 616 | { 617 | State.Data.AppendChar(Char); 618 | } 619 | } 620 | } 621 | 622 | void FJSONReader::ReadAsStringSpecial(const TCHAR& Char) 623 | { 624 | if (IsSpace(Char) && State.Data.Len() > 0) 625 | { 626 | State.Notation = EJSONNotation::SKIP; 627 | return; 628 | } 629 | 630 | State.Data.AppendChar(Char); 631 | } 632 | 633 | void FJSONReader::ReadAsNumber(const TCHAR& Char) 634 | { 635 | if (IsSpace(Char) && State.Data.Len() > 0) 636 | { 637 | State.Notation = EJSONNotation::SKIP; 638 | return; 639 | } 640 | 641 | if ((Char >= '0' && Char <= '9') || Char == '-' || Char == '.' || Char == '+' || Char == 'e' || Char == 'E') 642 | { 643 | State.Data.AppendChar(Char); 644 | } 645 | else 646 | { 647 | State.Error(); 648 | } 649 | } 650 | 651 | void FJSONReader::ReadBasicValue(const TCHAR& Char) 652 | { 653 | switch(Char) 654 | { 655 | case 'T': 656 | case 't': 657 | case 'F': 658 | case 'f': 659 | { 660 | State.PushBoolean(); 661 | State.Notation = EJSONNotation::STRING_SPECIAL; 662 | ReadAsStringSpecial(Char); 663 | return; 664 | } 665 | case 'N': 666 | case 'n': 667 | { 668 | State.PushNull(); 669 | State.Notation = EJSONNotation::STRING_SPECIAL; 670 | ReadAsStringSpecial(Char); 671 | return; 672 | } 673 | case '\'': 674 | case '"': 675 | { 676 | State.PushString(); 677 | State.Notation = EJSONNotation::STRING; 678 | State.Quote = Char; 679 | return; 680 | } 681 | } 682 | 683 | if ((Char >= '0' && Char <= '9') || Char == '-') 684 | { 685 | State.PushNumber(); 686 | State.Notation = EJSONNotation::NUMBER; 687 | ReadAsNumber(Char); 688 | return; 689 | } 690 | } 691 | 692 | void FJSONReader::ReadAsArray(const TCHAR& Char) 693 | { 694 | if (IsSpace(Char)) 695 | { 696 | return; 697 | } 698 | ReadBasicValue(Char); 699 | } 700 | 701 | void FJSONReader::ReadAsObject(const TCHAR& Char) 702 | { 703 | if (IsSpace(Char)) 704 | { 705 | return; 706 | } 707 | 708 | if (State.CheckTokens(EJSONToken::CURLY_BEGIN)) // read key "{" 709 | { 710 | if (Char == '\'' || Char == '"') 711 | { 712 | State.Notation = EJSONNotation::STRING; 713 | State.Quote = Char; 714 | } 715 | else 716 | { 717 | State.Notation = EJSONNotation::STRING_SPECIAL; 718 | ReadAsStringSpecial(Char); 719 | } 720 | } 721 | else if (State.CheckTokens(EJSONToken::CURLY_BEGIN, EJSONToken::COLON)) // read value "{:" 722 | { 723 | ReadBasicValue(Char); 724 | } 725 | } 726 | 727 | void FJSONReader::Skip(const TCHAR& Char) 728 | { 729 | if (!IsSpace(Char)) 730 | { 731 | State.Error(); 732 | } 733 | } 734 | 735 | bool FJSONReader::Read(const TCHAR Char) 736 | { 737 | if (Char == '\\' && !State.bEscape) 738 | { 739 | State.bEscape = true; 740 | return true; 741 | } 742 | 743 | if (FindToken(Char)) 744 | { 745 | State.Notation = EJSONNotation::NONE; 746 | UpdateNotation(); 747 | return true; 748 | } 749 | 750 | switch(State.Notation) 751 | { 752 | case EJSONNotation::NONE: UpdateNotation(); break; 753 | 754 | case EJSONNotation::STRING: ReadAsString(Char); break; 755 | case EJSONNotation::STRING_SPECIAL: ReadAsStringSpecial(Char); break; 756 | case EJSONNotation::NUMBER: ReadAsNumber(Char); break; 757 | case EJSONNotation::ARRAY: ReadAsArray(Char); break; 758 | case EJSONNotation::OBJECT: ReadAsObject(Char); break; 759 | 760 | case EJSONNotation::SKIP: Skip(Char); break; 761 | } 762 | 763 | if (State.bError) 764 | { 765 | State.Root = TSharedPtr(nullptr); 766 | State.Size = 0; 767 | return false; 768 | } 769 | 770 | State.bEscape = false; 771 | 772 | return true; 773 | } 774 | 775 | FJSONWriter::FJSONWriter() 776 | { 777 | 778 | } 779 | 780 | bool FJSONWriter::GetStartChar(const TSharedPtr& JsonValue, FString& Str) 781 | { 782 | switch (JsonValue->Type) 783 | { 784 | case EJson::Object: 785 | Str = FString(TEXT("{")); 786 | break; 787 | case EJson::Array: 788 | Str = FString(TEXT("[")); 789 | break; 790 | case EJson::String: 791 | Str = FString(TEXT("\"")); 792 | break; 793 | default: 794 | return false; 795 | break; 796 | } 797 | 798 | return true; 799 | } 800 | 801 | bool FJSONWriter::GetEndChar(const TSharedPtr& JsonValue, FString& Str) 802 | { 803 | switch (JsonValue->Type) 804 | { 805 | case EJson::Object: 806 | Str = FString(TEXT("}")); 807 | break; 808 | case EJson::Array: 809 | Str = FString(TEXT("]")); 810 | break; 811 | case EJson::String: 812 | Str = FString(TEXT("\"")); 813 | break; 814 | default: 815 | return false; 816 | break; 817 | } 818 | 819 | return true; 820 | } 821 | 822 | void FJSONWriter::Write(TSharedPtr JsonValue, FArchive* Writer, bool IsLastElement) 823 | { 824 | FString Str; 825 | FArchive& Ar = *Writer; 826 | 827 | if (GetStartChar(JsonValue, Str)) 828 | { 829 | UVaRestJsonObject::WriteStringToArchive(Ar, *Str, Str.Len()); 830 | } 831 | 832 | switch (JsonValue->Type) 833 | { 834 | case EJson::Object: 835 | { 836 | int ElementsCount = 0; 837 | auto Values = JsonValue->AsObject()->Values; 838 | 839 | for (auto& ChildJsonPair : Values) 840 | { 841 | Str = FString(TEXT("\"")); 842 | UVaRestJsonObject::WriteStringToArchive(Ar, *Str, Str.Len()); 843 | 844 | const TCHAR* BufferPtr = *ChildJsonPair.Key; 845 | for (int i = 0; i < ChildJsonPair.Key.Len(); ++i) 846 | { 847 | Str = FString(1, &ChildJsonPair.Key[i]); 848 | #if PLATFORM_WINDOWS 849 | UVaRestJsonObject::WriteStringToArchive(Ar, *Str, Str.Len() - 1); 850 | #else 851 | UVaRestJsonObject::WriteStringToArchive(Ar, *Str, Str.Len()); 852 | #endif 853 | } 854 | 855 | Str = FString(TEXT("\"")); 856 | UVaRestJsonObject::WriteStringToArchive(Ar, *Str, Str.Len()); 857 | 858 | Str = FString(TEXT(":")); 859 | UVaRestJsonObject::WriteStringToArchive(Ar, *Str, Str.Len()); 860 | 861 | ++ElementsCount; 862 | 863 | Write(ChildJsonPair.Value, Writer, ElementsCount >= Values.Num()); 864 | } 865 | break; 866 | } 867 | case EJson::Array: 868 | { 869 | int ElementsCount = 0; 870 | auto Array = JsonValue->AsArray(); 871 | 872 | for (auto& ChildJsonValue : Array) 873 | { 874 | ++ElementsCount; 875 | Write(ChildJsonValue, Writer, ElementsCount >= Array.Num()); 876 | } 877 | break; 878 | } 879 | default: 880 | { 881 | FString Value = JsonValue->AsString(); 882 | 883 | const TCHAR* BufferPtr = *Value; 884 | for (int i = 0; i < Value.Len(); ++i) 885 | { 886 | Str = FString(1, &BufferPtr[i]); 887 | if (Str == TEXT("\"")) 888 | { 889 | Str = FString(TEXT("\\")); 890 | UVaRestJsonObject::WriteStringToArchive(Ar, *Str, Str.Len()); 891 | Str = FString(1, &BufferPtr[i]); 892 | } 893 | if (Str == TEXT("\n")) 894 | { 895 | Str = FString(TEXT("\\")); 896 | UVaRestJsonObject::WriteStringToArchive(Ar, *Str, Str.Len()); 897 | Str = FString(TEXT("n")); 898 | UVaRestJsonObject::WriteStringToArchive(Ar, *Str, Str.Len()); 899 | Str = FString(1, &BufferPtr[i]); 900 | } 901 | else if (Str == TEXT("\t")) 902 | { 903 | Str = FString(TEXT("\\")); 904 | UVaRestJsonObject::WriteStringToArchive(Ar, *Str, Str.Len()); 905 | Str = FString(TEXT("t")); 906 | UVaRestJsonObject::WriteStringToArchive(Ar, *Str, Str.Len()); 907 | Str = FString(1, &BufferPtr[i]); 908 | } 909 | else 910 | { 911 | #if PLATFORM_WINDOWS 912 | UVaRestJsonObject::WriteStringToArchive(Ar, *Str, Str.Len() - 1); 913 | #else 914 | UVaRestJsonObject::WriteStringToArchive(Ar, *Str, Str.Len()); 915 | #endif 916 | } 917 | } 918 | 919 | break; 920 | } 921 | } 922 | 923 | if (GetEndChar(JsonValue, Str)) 924 | { 925 | UVaRestJsonObject::WriteStringToArchive(Ar, *Str, Str.Len()); 926 | } 927 | 928 | if (!IsLastElement) 929 | { 930 | Str = FString(TEXT(",")); 931 | UVaRestJsonObject::WriteStringToArchive(Ar, *Str, Str.Len()); 932 | } 933 | } 934 | --------------------------------------------------------------------------------