diff --git a/LICENSE b/LICENSE
index d22fb65f..b83e3c77 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,7 +1,11 @@
The MIT License (MIT)
+Socket.IO Client UE4
Copyright (c) 2016 Jan Kaniewski (Getnamo)
+VARest
+Copyright (c) 2014 Vladimir Alyamkin
+
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
diff --git a/README.md b/README.md
index b1f6409f..7b7e4af3 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@ socket.io client plugin for UE4
[Socket.io](http://socket.io/) is a performant real-time bi-directional communication library. There are two parts, the server written in node.js and the client typically javascript for the web. There are alternative client implementations and this repo uses the [C++11 client library](https://github.com/socketio/socket.io-client-cpp) ported to UE4.
-Uses [Socket.io prebuild libraries for VS2015](https://github.com/getnamo/socketio-client-prebuild)
+Uses [Socket.io prebuild libraries for VS2015](https://github.com/getnamo/socketio-client-prebuild) and SIOJson forked from ufna's [VaRest](https://github.com/ufna/VaRest)
[Unreal Forum Thread](https://forums.unrealengine.com/showthread.php?110680-Plugin-Socket-io-Client)
@@ -26,23 +26,100 @@ Add the SocketIO Client Component to your blueprint actor of choice
![IMG](http://i.imgur.com/lSkfHQ2.png)
-Specify your address and port, defaults to localhost (127.0.0.1) at port 3000
+By default the component will auto connect *on begin play* to your default address and port [http://localhost:3000](http://localhost:3000). You can change this default address to connect to your service instead.
-![IMG](http://i.imgur.com/rjm2pKw.png)
+![IMG](http://i.imgur.com/dWxCmvQ.png)
+
+If you want to connect at your own time, you change the default variable *Should Auto Connect* to false and then call *Connect* with your address
Call *Bind Event* for each event you wish the client to subscribe, e.g. 'chat message'
If you expect to receive events, select your component and in the Details pane press the + to add an 'On' event to your event graph
-![IMG](http://i.imgur.com/mZVTJWE.png)
-
Handle this event for your event types, e.g. printing 'chat message' event strings.
-![IMG](http://i.imgur.com/0BCFOoS.png)
+![IMG](http://i.imgur.com/vVlNBlx.png)
+
If you want to send information to the server, emit events on the SocketIO Client Component, e.g. pressing M to emit a 'chat message' string
-![IMG](http://i.imgur.com/EOadatA.png)
+![IMG](http://i.imgur.com/nihMSPz.png)
+
+## Blueprint - Advanced
+
+### Simple Json
+
+You can formulate any *SIOJsonValue* directly in blueprint. Apart from native basic types which are supported directly via conversion and e.g. *Construct Json String Value*, you can construct *SIOJsonObject*s and fill their fields.
+
+![IMG](http://i.imgur.com/PnxD6Ui.png)
+
+Start with *Construct Json Object* then set any desired fields. In this instance we wanted to make the JSON *{"myString":"Hello"}* so we used *Set String Field* and then auto-converted the object into a message.
+
+### Complex Json
+
+By combining arrays and objects you can form almost any data type, nest away!
+
+![IMG](http://i.imgur.com/lj07Jsw.png)
+
+### Structs
+
+The plugin supports making *SIOJsonValue*s from any unreal structs you make, including ones defined purely in blueprints!
+
+An easy example of a familiar struct is the *Vector* type
+
+![IMG](http://i.imgur.com/mPHOw0C.png)
+
+But you can make a custom type and emit it or nest it inside other *SIOJsonValue*s which should make it easy to organize your data however you want it.
+
+![IMG](http://i.imgur.com/czi0AIF.png)
+
+### Binary
+
+Socket.IO spec supports raw binary data types and these should be capable of being mixed in with other JSON types as usual. This plugin supports binaries as a first class citizen in blueprints and any arrays of bytes can be embedded and decoded in the chain as expected.
+
+![IMG](http://i.imgur.com/PqxEJqI.png)
+
+### Decoding Responses
+
+There are many ways to decode your *SIOJsonValue* message, it all depends on your data setup. You can even decode your *JsonObject*s directly into structs, if the JSON structure has matching variable names.
+
+![IMG](http://i.imgur.com/urAh2TH.png)
+
+### Conversion
+
+Most primitive types have auto-conversion nodes to simplify your workflow. E.g. if you wanted to emit a float literal you can get a reference to your float and simply drag to the *message* parameter to auto-convert into a *SIOJsonValue*.
+
+![IMG](http://i.imgur.com/4T79TUV.gif)
+
+Supported auto-conversion
+
+
Float
+Int
+Bool
+String
+SIOJsonObject
+
+### Emit with Callback
+
+You can have a callback when, for example, you need an acknowledgement or if you're fetching data from the server. You can respond to this callback straight in your blueprint.
+
+![IMG](http://i.imgur.com/Ed01Jq0.png)
+
+Instead of using *Emit* you use *Emit With Callback* and by default the target is the owning actor blueprint so you can leave that parameter blank and simply type your function name e.g. *OnEcho* function.
+
+![IMG](http://i.imgur.com/hXMXDd2.png)
+
+If the function is missing or named incorrectly, your output log will warn you.
+
+![IMG](http://i.imgur.com/PQinDYy.gif)
+
+Your function expects a *SIOJsonValue* reference signature. By default this contains your first response value from you callback parameter. If your callback uses more than one parameter, make a second *SIOJsonValue* Input parameter which contains an array of all the responses.
+
+### Binding Events to Functions
+
+Instead of using the event graph and comparing strings, you can bind an event directly to a function. The format to make the function is the same as callbacks.
+
+![IMG](http://i.imgur.com/7fA1qca.png)
## How to use - C++
@@ -56,20 +133,22 @@ To use the C++ code from the plugin add it as a dependency module in your projec
and *CreateDefaultSubobject* in your constructor
-```SocketIOClientComponent = CreateDefaultSubobject(TEXT("SocketIOClientComponent"));```
+```c++
+SIOClientComponent = CreateDefaultSubobject(TEXT("SocketIOClientComponent"));
+```
or reference it from another component by getting it on begin play e.g.
-```
-SIOComponent = Cast(this->GetOwner()->GetComponentByClass(USocketIOClientComponent::StaticClass()));
-if (!SIOComponent)
+```c++
+SIOClientComponent = Cast(this->GetOwner()->GetComponentByClass(USocketIOClientComponent::StaticClass()));
+if (!SIOClientComponent)
{
UE_LOG(LogTemp, Warning, TEXT("No sister socket IO component found"));
return;
}
else
{
- UE_LOG(LogTemp, Log, TEXT("Found SIOComponent: %s"), *SIOComponent->GetDesc());
+ UE_LOG(LogTemp, Log, TEXT("Found SIOClientComponent: %s"), *SIOComponent->GetDesc());
}
```
@@ -79,58 +158,189 @@ To connect simply change your address, the component will auto-connect on compon
```
-USocketIOClientComponent* SIOComponent; //get a reference or add as subobject in your actor
+USocketIOClientComponent* SIOClientComponent; //get a reference or add as subobject in your actor
//the component will autoconnect, but you may wish to change the url before it does that via
-SIOComponent->AddressAndPort = FString("http://127.0.0.1:3000"); //change your address
+SIOClientComponent->AddressAndPort = FString("http://127.0.0.1:3000"); //change your address
```
You can also connect at your own time by disabling auto-connect and connecting either to the default address or a custom one
```
//you can also disable auto connect and connect it at your own time via
-SIOComponent->ShouldAutoConnect = false;
-SIOComponent->Connect();
+SIOClientComponent->ShouldAutoConnect = false;
+SIOClientComponent->Connect();
//You can also easily disconnect at some point, reconnect to another address
-SIOComponent->Disconnect();
-SIOComponent->Connect(FString("http://127.0.0.1:3000"));
+SIOClientComponent->Disconnect();
+SIOClientComponent->Connect(FString("http://127.0.0.1:3000"));
```
### Emitting Events
+In C++ you can use *EmitNative*, *EmitRaw*, or *EmitRawBinary*. *EmitNative* is fully overloaded and expects all kinds of native UE4 data types and is the recommended method.
+
####String
-Emit a string via
+Emit an FString. Note that *FString(TEXT("yourString"))* is recommended if you have performance concerns due to internal conversion from ```char*```
+
+```c++
+SIOClientComponent->EmitNative(FString("nativeTest"), FString("hi"));
+```
+
+####Number
-```SIOComponent->Emit(FString("myevent"), FString(TEXT("some data or stringified json"));```
+Emit a double
+
+```c++
+SIOClientComponent->EmitNative(FString("nativeTest"), -3.5f);
+```
+
+####Boolean
+
+Emit a raw boolean
+
+```c++
+SIOClientComponent->EmitNative(FString("nativeTest"), true);
+```
####Binary or raw data
-Emit raw data via
+Emit raw data via a TArray
+```c++
+TArray Buffer; //null terminated 'Hi!'
+Buffer.Add(0x48);
+Buffer.Add(0x69);
+Buffer.Add(0x21);
+Buffer.Add(0x00);
+
+SIOClientComponent->EmitNative(FString("nativeTest"), Buffer);
```
-TArray Buffer;
-//fill buffer with your data
+or
-SIOComponent->EmitBuffer(FString("myBinarySendEvent"), Buffer.GetData(), Buffer.Num());
+```c++
+SIOComponent->EmitRawBinary(FString("myBinarySendEvent"), Buffer.GetData(), Buffer.Num());
```
-####Complex message using sio::message
+####FJsonObject - Simple
-see [sio::message](https://github.com/socketio/socket.io-client-cpp/blob/master/src/sio_message.h) for how to form a raw message. Generally it supports a lot of std:: variants e.g. std::string or more complex messages e.g. [socket.io c++ emit readme](https://github.com/socketio/socket.io-client-cpp#emit-an-event). Note that there are static helper functions attached to the component class to convert from std::string to FString and the reverse.
+Option 1 - Shorthand
+
+```c++
+//Basic one field object e.g. {"myKey":"myValue"}
+auto JsonObject = USIOJConvert::MakeJsonObject();
+JsonObject->SetStringField(FString("myKey"), FString("myValue"));
+SIOClientComponent->EmitNative(FString("nativeTest"), JsonObject);
```
-static std::string StdString(FString UEString);
-static FString FStringFromStd(std::string StdString);
+Option 2 - Standard
+
+```c++
+TSharedPtr JsonObject = MakeShareable(new FJsonObject);
+```
+
+####FJsonObject - Complex Example
+
+A nested example using various methods
+
+```c++
+//All types, nested
+TSharedPtr JsonObject = MakeShareable(new FJsonObject); //make object option2
+JsonObject->SetBoolField(FString("myBool"), false);
+JsonObject->SetStringField(FString("myString"), FString("Socket.io is easy"));
+JsonObject->SetNumberField(FString("myNumber"), 9001);
+
+JsonObject->SetField(FString("myBinary1"), USIOJConvert::ToJsonValue(Buffer)); //binary option1 - shorthand
+JsonObject->SetField(FString("myBinary2"), MakeShareable(new FJsonValueBinary(Buffer))); //binary option2
+
+JsonObject->SetArrayField(FString("myArray"), ArrayValue);
+JsonObject->SetObjectField(FString("myNestedObject"), SmallObject);
+
+SIOClientComponent->EmitNative(FString("nativeTest"), JsonObject);
```
-
-e.g. emitting {type:"image"} object
+####Callback Example
+Below is an example of emitting a simple object with the server using the passed in callback to return a response or acknowledgement.
+
+```c++
+//Make an object {"myKey":"myValue"}
+TSharedPtr JsonObject = MakeShareable(new FJsonObject);
+JsonObject->SetStringField(FString("myKey"), FString("myValue"));
+
+//Show what we emitted
+UE_LOG(LogTemp, Log, TEXT("1) Made a simple object and emitted: %s"), *USIOJConvert::ToJsonString(JsonObject));
+
+//Emit event "callbackTest" expecting an echo callback with the object we sent
+SIOClientComponent->EmitNative(FString("callbackTest"), JsonObject, [&](auto Response)
+{
+ //Response is an array of JsonValues, in our case we expect an object response, grab first element as an object.
+ auto Message = Response[0]->AsObject();
+
+ //Show what we received
+ UE_LOG(LogTemp, Log, TEXT("2) Received a response: %s"), *USIOJConvert::ToJsonString(Message));
+});
```
+
+####UStruct
+
+Plugin supports automatic conversion to/from UStructs, below is an example of a struct roundtrip, being in Json format on the server side.
+
+```c++
+USTRUCT()
+struct FTestCppStruct
+{
+ GENERATED_BODY()
+
+ UPROPERTY()
+ int32 Index;
+
+ UPROPERTY()
+ float SomeNumber;
+
+ UPROPERTY()
+ FString Name;
+};
+```
+
+```c++
+//Set your struct variables
+FTestCppStruct TestStruct;
+TestStruct.Name = FString("George");
+TestStruct.Index = 5;
+TestStruct.SomeNumber = 5.123f;
+
+SIOClientComponent->EmitNative(FString("callbackTest"), FTestCppStruct::StaticStruct(), &TestStruct, [&](auto Response)
+{
+ auto Message = Response[0]->AsObject();
+
+ //Show what we received
+ UE_LOG(LogTemp, Log, TEXT("Received a response: %s"), *USIOJConvert::ToJsonString(Message));
+
+ //Set our second struct to the new values
+ USIOJConvert::JsonObjectToUStruct(Message, FTestCppStruct::StaticStruct(), &MemberStruct);
+
+ //Show that struct
+ UE_LOG(LogTemp, Log, TEXT("Our received member name is now: %s"), *MemberStruct.Name);
+});
+```
+
+###Alternative Raw C++ Complex message using sio::message
+
+see [sio::message](https://github.com/socketio/socket.io-client-cpp/blob/master/src/sio_message.h) for how to form a raw message. Generally it supports a lot of std:: variants e.g. std::string or more complex messages e.g. [socket.io c++ emit readme](https://github.com/socketio/socket.io-client-cpp#emit-an-event). Note that there are static helper functions attached to the component class to convert from std::string to FString and the reverse.
+
+```c++
+static std::string USIOMessageConvert::StdString(FString UEString);
+
+static FString USIOMessageConvert::FStringFromStd(std::string StdString);
+```
+
+e.g. emitting *{type:"image"}* object
+
+```c++
//create object message
auto message = sio::object_message::create();
@@ -143,7 +353,7 @@ SIOComponent->EmitRaw(ShareResourceEventName, message);
with a callback
-```
+```c++
SIOComponent->EmitRawWithCallback(FString("myRawMessageEventWithAck"), string_message::create(username), [&](message::list const& msg) {
//got data, handle it here
});
@@ -154,19 +364,10 @@ SIOComponent->EmitRawWithCallback(FString("myRawMessageEventWithAck"), string_me
To receive events you can bind lambdas which makes things awesomely easy e.g.
-#### String
-
-```
-SIOComponent->BindStringMessageLambdaToEvent([&](const FString& Name, const FString& Data)
- {
- //do something with your string data
- }, FString(TEXT("myStringReceiveEvent")));
-```
-
#### Binary
-```
-SIOComponent->BindBinaryMessageLambdaToEvent([&](const FString& Name, const TArray& Buffer)
+```c++
+SIOComponent->OnBinaryEvent([&](const FString& Name, const TArray& Buffer)
{
//Do something with your buffer
}, FString(TEXT("myBinaryReceiveEvent")));
@@ -178,8 +379,8 @@ Currently the only way to handle json messages as the plugin doesn't auto-conver
e.g. expecting a result {type:"some type"}
-```
-SIOComponent->BindRawMessageLambdaToEvent([&](const FString& Name, const sio::message::ptr& Message)
+```c++
+SIOComponent->OnRawEvent([&](const FString& Name, const sio::message::ptr& Message)
{
//if you expected an object e.g. {}
if (Message->get_flag() != sio::message::flag_object)
diff --git a/SocketIOClient.uplugin b/SocketIOClient.uplugin
index cb74c138..ee99ca5f 100644
--- a/SocketIOClient.uplugin
+++ b/SocketIOClient.uplugin
@@ -1,7 +1,7 @@
{
"FileVersion": 3,
"Version": 1,
- "VersionName": "0.2.5",
+ "VersionName": "0.4.0",
"FriendlyName": "SocketIOClient",
"Description": "Socket IO C++ Client ported to UE4",
"Category": "Networking",
@@ -17,6 +17,15 @@
"Name": "SocketIOClient",
"Type": "Runtime",
"LoadingPhase": "Default"
+ },
+ {
+ "Name" : "SIOJson",
+ "Type" : "Runtime",
+ "LoadingPhase": "PreDefault"
+ },
+ {
+ "Name": "SIOJEditorPlugin",
+ "Type": "Developer"
}
]
}
\ No newline at end of file
diff --git a/Source/SIOJEditorPlugin/Private/SIOJEditorPlugin.cpp b/Source/SIOJEditorPlugin/Private/SIOJEditorPlugin.cpp
new file mode 100644
index 00000000..bbfa9b0a
--- /dev/null
+++ b/Source/SIOJEditorPlugin/Private/SIOJEditorPlugin.cpp
@@ -0,0 +1,22 @@
+// Copyright 2015 Vladimir Alyamkin. All Rights Reserved.
+// Original code by https://github.com/unktomi
+
+#include "SIOJEditorPluginPrivatePCH.h"
+#include "SIOJEditorPlugin.h"
+
+#define LOCTEXT_NAMESPACE "FSIOJEditorPluginModule"
+
+void FSIOJEditorPluginModule::StartupModule()
+{
+ // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
+}
+
+void FSIOJEditorPluginModule::ShutdownModule()
+{
+ // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
+ // we call this function before unloading the module.
+}
+
+#undef LOCTEXT_NAMESPACE
+
+IMPLEMENT_MODULE(FSIOJEditorPluginModule, SIOJEditorPlugin)
diff --git a/Source/SIOJEditorPlugin/Private/SIOJEditorPluginPrivatePCH.h b/Source/SIOJEditorPlugin/Private/SIOJEditorPluginPrivatePCH.h
new file mode 100644
index 00000000..a76f3887
--- /dev/null
+++ b/Source/SIOJEditorPlugin/Private/SIOJEditorPluginPrivatePCH.h
@@ -0,0 +1,16 @@
+// Copyright 2015 Vladimir Alyamkin. All Rights Reserved.
+// Original code by https://github.com/unktomi
+
+#pragma once
+
+#include "KismetCompiler.h"
+#include "EditorCategoryUtils.h"
+#include "EdGraph/EdGraph.h"
+#include "EdGraph/EdGraphNodeUtils.h" // for FNodeTextCache
+#include "EdGraphSchema_K2.h"
+#include "BlueprintNodeSpawner.h"
+#include "BlueprintActionDatabaseRegistrar.h"
+#include "BlueprintFieldNodeSpawner.h"
+#include "EditorCategoryUtils.h"
+#include "BlueprintActionFilter.h"
+
diff --git a/Source/SIOJEditorPlugin/Private/SIOJ_BreakJson.cpp b/Source/SIOJEditorPlugin/Private/SIOJ_BreakJson.cpp
new file mode 100644
index 00000000..7ca8a013
--- /dev/null
+++ b/Source/SIOJEditorPlugin/Private/SIOJ_BreakJson.cpp
@@ -0,0 +1,284 @@
+// Copyright 2015 Vladimir Alyamkin. All Rights Reserved.
+// Original code by https://github.com/unktomi
+
+#include "SIOJEditorPluginPrivatePCH.h"
+#include "SIOJ_BreakJson.h"
+
+#include "Runtime/Launch/Resources/Version.h"
+
+#define LOCTEXT_NAMESPACE "SIOJ_BreakJson"
+
+class FKCHandler_BreakJson : public FNodeHandlingFunctor
+{
+
+public:
+ FKCHandler_BreakJson(FKismetCompilerContext& InCompilerContext)
+ : FNodeHandlingFunctor(InCompilerContext)
+ {
+ }
+
+ virtual void Compile(FKismetFunctionContext& Context, UEdGraphNode* Node) override
+ {
+ UEdGraphPin* InputPin = NULL;
+
+ for (int32 PinIndex = 0; PinIndex < Node->Pins.Num(); ++PinIndex)
+ {
+ UEdGraphPin* Pin = Node->Pins[PinIndex];
+ if (Pin && (EGPD_Input == Pin->Direction))
+ {
+ InputPin = Pin;
+ break;
+ }
+ }
+
+ UEdGraphPin *InNet = FEdGraphUtilities::GetNetFromPin(InputPin);
+ UClass *Class = Cast(StaticLoadObject(UClass::StaticClass(), NULL, TEXT("class'SIOJPlugin.SIOJJsonObject'")));
+
+ FBPTerminal **SourceTerm = Context.NetMap.Find(InNet);
+ if (SourceTerm == nullptr)
+ {
+ return;
+ }
+
+ for (int32 PinIndex = 0; PinIndex < Node->Pins.Num(); ++PinIndex)
+ {
+ UEdGraphPin* Pin = Node->Pins[PinIndex];
+ if (Pin && (EGPD_Output == Pin->Direction))
+ {
+ if (Pin->LinkedTo.Num() < 1)
+ {
+ continue;
+ }
+
+ FBPTerminal **Target = Context.NetMap.Find(Pin);
+
+ const FString &FieldName = Pin->PinName;
+ const FString &FieldType = Pin->PinType.PinCategory;
+
+ FBPTerminal* FieldNameTerm = Context.CreateLocalTerminal(ETerminalSpecification::TS_Literal);
+ FieldNameTerm->Type.PinCategory = CompilerContext.GetSchema()->PC_String;
+#if ENGINE_MINOR_VERSION >= 13
+ FieldNameTerm->SourcePin = Pin;
+#else
+ FieldNameTerm->Source = Pin;
+#endif
+ FieldNameTerm->Name = FieldName;
+ FieldNameTerm->TextLiteral = FText::FromString(FieldName);
+
+ FBlueprintCompiledStatement& Statement = Context.AppendStatementForNode(Node);
+ FName FunctionName;
+
+ bool bIsArray = Pin->PinType.bIsArray;
+
+ if (FieldType == CompilerContext.GetSchema()->PC_Boolean)
+ {
+ FunctionName = bIsArray ? TEXT("GetBoolArrayField") : TEXT("GetBoolField");
+ }
+ else if (FieldType == CompilerContext.GetSchema()->PC_Float)
+ {
+ FunctionName = bIsArray ? TEXT("GetNumberArrayField") : TEXT("GetNumberField");
+ }
+ else if (FieldType == CompilerContext.GetSchema()->PC_String)
+ {
+ FunctionName = bIsArray ? TEXT("GetStringArrayField") : TEXT("GetStringField");
+ }
+ else if (FieldType == CompilerContext.GetSchema()->PC_Object)
+ {
+ FunctionName = bIsArray ? TEXT("GetObjectArrayField") : TEXT("GetObjectField");
+ }
+ else
+ {
+ continue;
+ }
+
+ UFunction *FunctionPtr = Class->FindFunctionByName(FunctionName);
+ Statement.Type = KCST_CallFunction;
+ Statement.FunctionToCall = FunctionPtr;
+ Statement.FunctionContext = *SourceTerm;
+ Statement.bIsParentContext = false;
+ Statement.LHS = *Target;
+ Statement.RHS.Add(FieldNameTerm);
+ }
+ }
+ }
+
+ FBPTerminal* RegisterInputTerm(FKismetFunctionContext& Context, USIOJ_BreakJson* Node)
+ {
+ // Find input pin
+ UEdGraphPin* InputPin = NULL;
+ for (int32 PinIndex = 0; PinIndex < Node->Pins.Num(); ++PinIndex)
+ {
+ UEdGraphPin* Pin = Node->Pins[PinIndex];
+ if (Pin && (EGPD_Input == Pin->Direction))
+ {
+ InputPin = Pin;
+ break;
+ }
+ }
+ check(NULL != InputPin);
+
+ // Find structure source net
+ UEdGraphPin* Net = FEdGraphUtilities::GetNetFromPin(InputPin);
+ FBPTerminal **TermPtr = Context.NetMap.Find(Net);
+
+ if (!TermPtr)
+ {
+ FBPTerminal *Term = Context.CreateLocalTerminalFromPinAutoChooseScope(Net, Context.NetNameMap->MakeValidName(Net));
+
+ Context.NetMap.Add(Net, Term);
+
+ return Term;
+ }
+
+ return *TermPtr;
+ }
+
+ void RegisterOutputTerm(FKismetFunctionContext& Context, UEdGraphPin* OutputPin, FBPTerminal* ContextTerm)
+ {
+ FBPTerminal *Term = Context.CreateLocalTerminalFromPinAutoChooseScope(OutputPin, Context.NetNameMap->MakeValidName(OutputPin));
+ Context.NetMap.Add(OutputPin, Term);
+ }
+
+ virtual void RegisterNets(FKismetFunctionContext& Context, UEdGraphNode* InNode) override
+ {
+ USIOJ_BreakJson* Node = Cast(InNode);
+ FNodeHandlingFunctor::RegisterNets(Context, Node);
+
+ check(NULL != Node);
+
+ if (FBPTerminal* StructContextTerm = RegisterInputTerm(Context, Node))
+ {
+ for (int32 PinIndex = 0; PinIndex < Node->Pins.Num(); ++PinIndex)
+ {
+ UEdGraphPin* Pin = Node->Pins[PinIndex];
+ if (NULL != Pin && EGPD_Output == Pin->Direction)
+ {
+ RegisterOutputTerm(Context, Pin, StructContextTerm);
+ }
+ }
+ }
+ }
+};
+
+/**
+ * Main node class
+ */
+USIOJ_BreakJson::USIOJ_BreakJson(const FObjectInitializer &ObjectInitializer)
+ : Super(ObjectInitializer)
+{
+}
+
+FNodeHandlingFunctor* USIOJ_BreakJson::CreateNodeHandler(class FKismetCompilerContext& CompilerContext) const
+{
+ return new FKCHandler_BreakJson(CompilerContext);
+}
+
+void USIOJ_BreakJson::AllocateDefaultPins()
+{
+ const UEdGraphSchema_K2* K2Schema = GetDefault();
+
+ UClass *Class = Cast(StaticLoadObject(UClass::StaticClass(), NULL, TEXT("class'SIOJPlugin.SIOJJsonObject'")));
+ UEdGraphPin* Pin = CreatePin(EGPD_Input, K2Schema->PC_Object, TEXT(""), Class, false, false, TEXT("Target"));
+
+ K2Schema->SetPinDefaultValueBasedOnType(Pin);
+
+ CreateProjectionPins(Pin);
+}
+
+FLinearColor USIOJ_BreakJson::GetNodeTitleColor() const
+{
+ return FLinearColor(255.0f, 255.0f, 0.0f);
+}
+
+void USIOJ_BreakJson::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent)
+{
+ bool bIsDirty = false;
+
+ FName PropertyName = (PropertyChangedEvent.Property != NULL) ? PropertyChangedEvent.Property->GetFName() : NAME_None;
+ if (true || PropertyName == TEXT("Outputs"))
+ {
+ bIsDirty = true;
+ }
+
+ if (bIsDirty)
+ {
+ ReconstructNode();
+ GetGraph()->NotifyGraphChanged();
+ }
+
+ Super::PostEditChangeProperty(PropertyChangedEvent);
+}
+
+void USIOJ_BreakJson::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const
+{
+ // actions get registered under specific object-keys; the idea is that
+ // actions might have to be updated (or deleted) if their object-key is
+ // mutated (or removed)... here we use the node's class (so if the node
+ // type disappears, then the action should go with it)
+ UClass* ActionKey = GetClass();
+
+ // to keep from needlessly instantiating a UBlueprintNodeSpawner, first
+ // check to make sure that the registrar is looking for actions of this type
+ // (could be regenerating actions for a specific asset, and therefore the
+ // registrar would only accept actions corresponding to that asset)
+ if (ActionRegistrar.IsOpenForRegistration(ActionKey))
+ {
+ UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass());
+ check(NodeSpawner != nullptr);
+
+ ActionRegistrar.AddBlueprintAction(ActionKey, NodeSpawner);
+ }
+}
+
+FText USIOJ_BreakJson::GetMenuCategory() const
+{
+ static FNodeTextCache CachedCategory;
+
+ if (CachedCategory.IsOutOfDate(this))
+ {
+ // FText::Format() is slow, so we cache this to save on performance
+ CachedCategory.SetCachedText(FEditorCategoryUtils::BuildCategoryString(FCommonEditorCategory::Utilities, LOCTEXT("ActionMenuCategory", "Va Rest")), this);
+ }
+ return CachedCategory;
+}
+
+void USIOJ_BreakJson::CreateProjectionPins(UEdGraphPin *Source)
+{
+ const UEdGraphSchema_K2* K2Schema = GetDefault();
+ UClass *Class = Cast(StaticLoadObject(UClass::StaticClass(), NULL, TEXT("class'SIOJPlugin.SIOJJsonObject'")));
+
+ for (TArray::TIterator it(Outputs); it; ++it)
+ {
+ FString Type;
+ UObject *Subtype = nullptr;
+ FString FieldName = (*it).Name;
+
+ switch ((*it).Type) {
+ case ESIOJ_JsonType::JSON_Bool:
+ Type = K2Schema->PC_Boolean;
+ break;
+
+ case ESIOJ_JsonType::JSON_Number:
+ Type = K2Schema->PC_Float;
+ break;
+
+ case ESIOJ_JsonType::JSON_String:
+ Type = K2Schema->PC_String;
+ break;
+
+ case ESIOJ_JsonType::JSON_Object:
+ Type = K2Schema->PC_Object;
+ Subtype = Class;
+ break;
+ }
+
+ UEdGraphPin *OutputPin = CreatePin(EGPD_Output, Type, TEXT(""), Subtype, (*it).bIsArray, false, (*it).Name);
+ }
+}
+
+FText USIOJ_BreakJson::GetNodeTitle(ENodeTitleType::Type TitleType) const
+{
+ return LOCTEXT("SIOJ_Break_Json.NodeTitle", "Break Json");
+}
+
+#undef LOCTEXT_NAMESPACE
diff --git a/Source/SIOJEditorPlugin/Public/SIOJEditorPlugin.h b/Source/SIOJEditorPlugin/Public/SIOJEditorPlugin.h
new file mode 100644
index 00000000..96aa0f25
--- /dev/null
+++ b/Source/SIOJEditorPlugin/Public/SIOJEditorPlugin.h
@@ -0,0 +1,16 @@
+// Copyright 2015 Vladimir Alyamkin. All Rights Reserved.
+// Original code by https://github.com/unktomi
+
+#pragma once
+
+#include "ModuleManager.h"
+
+class FSIOJEditorPluginModule : public IModuleInterface
+{
+
+public:
+ /** IModuleInterface implementation */
+ virtual void StartupModule() override;
+ virtual void ShutdownModule() override;
+
+};
diff --git a/Source/SIOJEditorPlugin/Public/SIOJ_BreakJson.h b/Source/SIOJEditorPlugin/Public/SIOJ_BreakJson.h
new file mode 100644
index 00000000..857a81fc
--- /dev/null
+++ b/Source/SIOJEditorPlugin/Public/SIOJ_BreakJson.h
@@ -0,0 +1,64 @@
+// Copyright 2015 Vladimir Alyamkin. All Rights Reserved.
+// Original code by https://github.com/unktomi
+
+#pragma once
+
+#include "Engine.h"
+#include "K2Node.h"
+
+#include "SIOJ_BreakJson.generated.h"
+
+UENUM(BlueprintType)
+enum class ESIOJ_JsonType : uint8
+{
+ //JSON_Null UMETA(DisplayName = "Null"),
+ JSON_Bool UMETA(DisplayName = "Boolean"),
+ JSON_Number UMETA(DisplayName = "Number"),
+ JSON_String UMETA(DisplayName = "String"),
+ JSON_Object UMETA(DisplayName = "Object")
+};
+
+USTRUCT(BlueprintType)
+struct FSIOJ_NamedType
+{
+ GENERATED_USTRUCT_BODY();
+
+ UPROPERTY(EditAnywhere, Category = NamedType)
+ FString Name;
+
+ UPROPERTY(EditAnywhere, Category = NamedType)
+ ESIOJ_JsonType Type;
+
+ UPROPERTY(EditAnywhere, Category = NamedType)
+ bool bIsArray;
+};
+
+UCLASS(BlueprintType, Blueprintable)
+class SIOJEDITORPLUGIN_API USIOJ_BreakJson : public UK2Node
+{
+ GENERATED_UCLASS_BODY()
+
+public:
+ // Begin UEdGraphNode interface.
+ virtual void AllocateDefaultPins() override;
+ virtual FLinearColor GetNodeTitleColor() const override;
+ virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override;
+ // End UEdGraphNode interface.
+
+ // Begin UK2Node interface
+ virtual bool IsNodePure() const { return true; }
+ virtual bool ShouldShowNodeProperties() const { return true; }
+ void GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const override;
+ virtual FText GetMenuCategory() const override;
+ virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
+ virtual class FNodeHandlingFunctor* CreateNodeHandler(class FKismetCompilerContext& CompilerContext) const override;
+ // End UK2Node interface.
+
+protected:
+ virtual void CreateProjectionPins(UEdGraphPin *Source);
+
+public:
+ UPROPERTY(EditAnywhere, Category = PinOptions)
+ TArray Outputs;
+
+};
diff --git a/Source/SIOJEditorPlugin/SIOJEditorPlugin.Build.cs b/Source/SIOJEditorPlugin/SIOJEditorPlugin.Build.cs
new file mode 100644
index 00000000..6eadbf84
--- /dev/null
+++ b/Source/SIOJEditorPlugin/SIOJEditorPlugin.Build.cs
@@ -0,0 +1,67 @@
+// Copyright 2015 Vladimir Alyamkin. All Rights Reserved.
+
+using UnrealBuildTool;
+
+public class SIOJEditorPlugin : ModuleRules
+{
+ public SIOJEditorPlugin(TargetInfo Target)
+ {
+
+ PublicIncludePaths.AddRange(
+ new string[] {
+ "SIOJson",
+ "SIOJson/Public"
+
+ // ... add public include paths required here ...
+ });
+
+
+ PrivateIncludePaths.AddRange(
+ new string[] {
+ "SIOJEditorPlugin/Private",
+
+ // ... add other private include paths required here ...
+ });
+
+
+ PublicDependencyModuleNames.AddRange(
+ new string[]
+ {
+ "Core",
+ "SIOJson"
+
+ // ... add other public dependencies that you statically link with here ...
+ });
+
+
+ PrivateDependencyModuleNames.AddRange(
+ new string[]
+ {
+ "CoreUObject",
+ "Engine",
+ "Slate",
+ "SlateCore",
+ "InputCore",
+ "AssetTools",
+ "UnrealEd", // for FAssetEditorManager
+ "KismetWidgets",
+ "KismetCompiler",
+ "BlueprintGraph",
+ "GraphEditor",
+ "Kismet", // for FWorkflowCentricApplication
+ "PropertyEditor",
+ "EditorStyle",
+ "Sequencer",
+ "DetailCustomizations",
+ "Settings",
+ "RenderCore"
+ });
+
+
+ DynamicallyLoadedModuleNames.AddRange(
+ new string[]
+ {
+ // ... add any modules that your module loads dynamically here ...
+ });
+ }
+}
diff --git a/Source/SIOJson/Private/SIOJConvert.cpp b/Source/SIOJson/Private/SIOJConvert.cpp
new file mode 100644
index 00000000..ca25f156
--- /dev/null
+++ b/Source/SIOJson/Private/SIOJConvert.cpp
@@ -0,0 +1,402 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#include "SIOJsonPrivatePCH.h"
+
+typedef TJsonWriterFactory< TCHAR, TCondensedJsonPrintPolicy > FCondensedJsonStringWriterFactory;
+typedef TJsonWriter< TCHAR, TCondensedJsonPrintPolicy > FCondensedJsonStringWriter;
+
+FString USIOJConvert::ToJsonString(const TSharedPtr& JsonObject)
+{
+ FString OutputString;
+ TSharedRef< FCondensedJsonStringWriter > Writer = FCondensedJsonStringWriterFactory::Create(&OutputString);
+ FJsonSerializer::Serialize(JsonObject.ToSharedRef(), Writer);
+ return OutputString;
+}
+
+FString USIOJConvert::ToJsonString(const TArray>& JsonValueArray)
+{
+ FString OutputString;
+ TSharedRef< FCondensedJsonStringWriter > Writer = FCondensedJsonStringWriterFactory::Create(&OutputString);
+ FJsonSerializer::Serialize(JsonValueArray, Writer);
+ return OutputString;
+}
+
+FString USIOJConvert::ToJsonString(const TSharedPtr& JsonValue)
+{
+ if (JsonValue->Type == EJson::None)
+ {
+ return FString();
+ }
+ else if (JsonValue->Type == EJson::Null)
+ {
+ return FString();
+ }
+ else if (JsonValue->Type == EJson::String)
+ {
+ return JsonValue->AsString();
+ }
+ else if (JsonValue->Type == EJson::Number)
+ {
+ return FString::Printf(TEXT("%f"),JsonValue->AsNumber());
+ }
+ else if (JsonValue->Type == EJson::Boolean)
+ {
+ return FString::Printf(TEXT("%d"), JsonValue->AsBool());
+ }
+ else if (JsonValue->Type == EJson::Array)
+ {
+ return ToJsonString(JsonValue->AsArray());
+ }
+ else if (JsonValue->Type == EJson::Object)
+ {
+ return ToJsonString(JsonValue->AsObject());
+ }
+ else
+ {
+ return FString();
+ }
+}
+
+USIOJsonValue* USIOJConvert::ToSIOJsonValue(const TArray>& JsonValueArray)
+{
+ TArray< TSharedPtr > ValueArray;
+ for (auto InVal : JsonValueArray)
+ {
+ ValueArray.Add(InVal);
+ }
+
+ USIOJsonValue* ResultValue = NewObject();
+ TSharedPtr NewVal = MakeShareable(new FJsonValueArray(ValueArray));
+ ResultValue->SetRootValue(NewVal);
+
+ return ResultValue;
+}
+
+#pragma endregion ToJsonValue
+
+TSharedPtr USIOJConvert::JsonStringToJsonValue(const FString& JsonString)
+{
+ //Null
+ if (JsonString.IsEmpty())
+ {
+ return MakeShareable(new FJsonValueNull);
+ }
+
+ //Number
+ if (JsonString.IsNumeric())
+ {
+ //convert to double
+ return MakeShareable(new FJsonValueNumber(FCString::Atod(*JsonString)));
+ }
+
+ //Object
+ if (JsonString.StartsWith(FString(TEXT("{"))))
+ {
+ TSharedPtr< FJsonObject > JsonObject = MakeShareable(new FJsonObject);
+ TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create(*JsonString);
+ bool success = FJsonSerializer::Deserialize(Reader, JsonObject);
+
+ if (success)
+ {
+ return MakeShareable(new FJsonValueObject(JsonObject));
+ }
+ }
+
+ //Array
+ if (JsonString.StartsWith(FString(TEXT("["))))
+ {
+ TArray < TSharedPtr> RawJsonValueArray;
+ TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create(*JsonString);
+ bool success = FJsonSerializer::Deserialize(Reader, RawJsonValueArray);
+
+ if (success)
+ {
+ return MakeShareable(new FJsonValueArray(RawJsonValueArray));
+ }
+ }
+
+ //Bool
+ if (JsonString == FString("true") || JsonString == FString("false"))
+ {
+ bool BooleanValue = (JsonString == FString("true"));
+ return MakeShareable(new FJsonValueBoolean(BooleanValue));
+ }
+
+ //String
+ return MakeShareable(new FJsonValueString(JsonString));
+}
+
+TSharedPtr USIOJConvert::ToJsonValue(const TSharedPtr& JsonObject)
+{
+ return MakeShareable(new FJsonValueObject(JsonObject));
+}
+
+TSharedPtr USIOJConvert::ToJsonValue(const FString& StringValue)
+{
+ return MakeShareable(new FJsonValueString(StringValue));
+}
+
+TSharedPtr USIOJConvert::ToJsonValue(double NumberValue)
+{
+ return MakeShareable(new FJsonValueNumber(NumberValue));
+}
+
+TSharedPtr USIOJConvert::ToJsonValue(bool BoolValue)
+{
+ return MakeShareable(new FJsonValueBoolean(BoolValue));
+}
+
+TSharedPtr USIOJConvert::ToJsonValue(const TArray& BinaryValue)
+{
+ return MakeShareable(new FJsonValueBinary(BinaryValue));
+}
+
+TSharedPtr USIOJConvert::ToJsonValue(const TArray>& ArrayValue)
+{
+ return MakeShareable(new FJsonValueArray(ArrayValue));
+}
+
+#pragma endregion ToJsonValue
+
+TArray> USIOJConvert::JsonStringToJsonArray(const FString& JsonString)
+{
+ TArray < TSharedPtr> RawJsonValueArray;
+ TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create(*JsonString);
+ FJsonSerializer::Deserialize(Reader, RawJsonValueArray);
+
+ return RawJsonValueArray;
+}
+
+TSharedPtr USIOJConvert::ToJsonObject(const FString& JsonString)
+{
+ TSharedPtr< FJsonObject > JsonObject = MakeShareable(new FJsonObject);
+ TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create(*JsonString);
+ FJsonSerializer::Deserialize(Reader, JsonObject);
+ return JsonObject;
+}
+
+TSharedPtr USIOJConvert::ToJsonObject(UStruct* Struct, void* StructPtr, bool IsBlueprintStruct)
+{
+ if (IsBlueprintStruct)
+ {
+ //Get the object keys
+ TSharedPtr Object = ToJsonObject(Struct, StructPtr, false);
+
+ //Wrap it into a value and pass it into the trimmer
+ TSharedPtr JsonValue = MakeShareable(new FJsonValueObject(Object));
+ TrimValueKeyNames(JsonValue);
+
+ //Return object with trimmed names
+ return JsonValue->AsObject();
+ }
+ else
+ {
+ TSharedRef JsonObject = MakeShareable(new FJsonObject);
+ bool success = FJsonObjectConverter::UStructToJsonObject(Struct, StructPtr, JsonObject, 0, 0);
+ return JsonObject;
+ }
+}
+
+TSharedPtr USIOJConvert::MakeJsonObject()
+{
+ return MakeShareable(new FJsonObject);
+}
+
+bool USIOJConvert::JsonObjectToUStruct(TSharedPtr JsonObject, UStruct* Struct, void* StructPtr, bool IsBlueprintStruct)
+{
+ if (IsBlueprintStruct)
+ {
+ //Json object we pass will have their trimmed BP names, e.g. boolKey vs boolKey_8_EDBB36654CF43866C376DE921373AF23
+ //so we have to match them to the verbose versions, get a map of the names
+
+ TSharedPtr KeyMap = MakeShareable(new FTrimmedKeyMap);
+ SetTrimmedKeyMapForStruct(KeyMap, Struct);
+
+ //Adjust our passed in JsonObject to use the long key names
+ TSharedPtr JsonValue = MakeShareable(new FJsonValueObject(JsonObject));
+ ReplaceJsonValueNamesWithMap(JsonValue, KeyMap);
+
+ //Now it's a regular struct and will fill correctly
+ return JsonObjectToUStruct(JsonObject, Struct, StructPtr, false);
+ }
+ else
+ {
+ return FJsonObjectConverter::JsonObjectToUStruct(JsonObject.ToSharedRef(), Struct, StructPtr, 0, 0);
+ }
+}
+
+void USIOJConvert::TrimValueKeyNames(const TSharedPtr& JsonValue)
+{
+ //Array?
+ if (JsonValue->Type == EJson::Array)
+ {
+ auto Array = JsonValue->AsArray();
+
+ for (auto SubValue : Array)
+ {
+ TrimValueKeyNames(SubValue);
+ }
+ }
+ //Object?
+ else if (JsonValue->Type == EJson::Object)
+ {
+ auto JsonObject = JsonValue->AsObject();
+ for (auto Pair : JsonObject->Values)
+ {
+ const FString& Key = Pair.Key;
+ FString TrimmedKey;
+
+ bool DidNeedTrimming = TrimKey(Key, TrimmedKey);
+
+ //Positive count? trim it
+ if (DidNeedTrimming)
+ {
+ //Trim subvalue if applicable
+ auto SubValue = Pair.Value;
+ TrimValueKeyNames(SubValue);
+
+ JsonObject->SetField(TrimmedKey, SubValue);
+ JsonObject->RemoveField(Key);
+
+ //UE_LOG(LogTemp, Log, TEXT("orig: %s, trimmed: %s"), *Pair.Key, *TrimmedKey);
+ }
+ else
+ {
+ //UE_LOG(LogTemp, Log, TEXT("untrimmed: %s"), *Pair.Key);
+ }
+ }
+ }
+}
+
+bool USIOJConvert::TrimKey(const FString& InLongKey, FString& OutTrimmedKey)
+{
+ //Look for the position of the 2nd '_'
+ int32 LastIndex = InLongKey.Find(TEXT("_"), ESearchCase::IgnoreCase, ESearchDir::FromEnd);
+ LastIndex = InLongKey.Find(TEXT("_"), ESearchCase::IgnoreCase, ESearchDir::FromEnd, LastIndex);
+
+ if (LastIndex >= 0)
+ {
+ OutTrimmedKey = InLongKey.Mid(0, LastIndex);;
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+
+
+void USIOJConvert::SetTrimmedKeyMapForStruct(TSharedPtr& InMap, UStruct* Struct)
+{
+ //Get the child fields
+ auto FieldPtr = Struct->Children;
+
+ //If it hasn't been set, the long key is the json standardized long name
+ if (InMap->LongKey.IsEmpty())
+ {
+ InMap->LongKey = FJsonObjectConverter::StandardizeCase(Struct->GetName());
+ }
+
+ //For each child field...
+ while (FieldPtr != NULL) {
+ //Map our trimmed name to our full name
+ const FString& LowerKey = FJsonObjectConverter::StandardizeCase(FieldPtr->GetName());
+ FString TrimmedKey;
+ bool DidTrim = TrimKey(LowerKey, TrimmedKey);
+
+ //Set the key map
+ TSharedPtr SubMap = MakeShareable(new FTrimmedKeyMap);
+ SubMap->LongKey = LowerKey;
+
+ //No-trim case, trim = long
+ if (!DidTrim)
+ {
+ TrimmedKey = SubMap->LongKey;
+ }
+
+ //Did we get a substructure?
+ UStructProperty* SubStruct = Cast(FieldPtr);
+ if (SubStruct != NULL)
+ {
+ //We did, embed the sub-map
+ SetTrimmedKeyMapForStruct(SubMap, SubStruct->Struct);
+ }
+
+ //Did we get a sub-array?
+ UArrayProperty* ArrayProp = Cast(FieldPtr);
+ if (ArrayProp != NULL)
+ {
+ //set the inner map for the inner property
+ SetTrimmedKeyMapForProp(SubMap, ArrayProp->Inner);
+
+ //UE_LOG(LogTemp, Log, TEXT("found array: %s"), *ArrayProp->GetName());
+ }
+
+ InMap->SubMap.Add(TrimmedKey, SubMap);
+ //UE_LOG(LogTemp, Log, TEXT("long: %s, trim: %s, is struct: %d"), *SubMap->LongKey, *TrimmedKey, SubStruct != NULL);
+
+ FieldPtr = FieldPtr->Next;
+ }
+
+ //UE_LOG(LogTemp, Log, TEXT("Final map: %d"), InMap->SubMap.Num());
+}
+
+void USIOJConvert::SetTrimmedKeyMapForProp(TSharedPtr& InMap, UProperty* InnerProperty)
+{
+
+ //UE_LOG(LogTemp, Log, TEXT("got prop: %s"), *InnerProperty->GetName());
+ UStructProperty* SubStruct = Cast(InnerProperty);
+ if (SubStruct != NULL)
+ {
+ //We did, embed the sub-map
+ SetTrimmedKeyMapForStruct(InMap, SubStruct->Struct);
+ }
+
+ //Did we get a sub-array?
+ UArrayProperty* ArrayProp = Cast(InnerProperty);
+ if (ArrayProp != NULL)
+ {
+ SetTrimmedKeyMapForProp(InMap, ArrayProp->Inner);
+
+ }
+}
+
+void USIOJConvert::ReplaceJsonValueNamesWithMap(TSharedPtr& JsonValue, TSharedPtr KeyMap)
+{
+ if (JsonValue->Type == EJson::Object)
+ {
+ //Go through each key in the object
+ auto Object = JsonValue->AsObject();
+ auto SubMap = KeyMap->SubMap;
+ auto AllValues = Object->Values;
+
+ for (auto Pair : AllValues)
+ {
+ if (SubMap.Num() > 0)
+ {
+ //Get the long key for entry
+ const FString& LongKey = SubMap[Pair.Key]->LongKey;
+
+ //loop nested structures
+ ReplaceJsonValueNamesWithMap(Pair.Value, SubMap[Pair.Key]);
+
+ if (Pair.Key != LongKey)
+ {
+ //finally set the field and remove the old field
+ Object->SetField(LongKey, Pair.Value);
+ Object->RemoveField(Pair.Key);
+ }
+ }
+ }
+ }
+ else if (JsonValue->Type == EJson::Array)
+ {
+ auto Array = JsonValue->AsArray();
+ for (auto Item : Array)
+ {
+ //UE_LOG(LogTemp, Log, TEXT("%s"), *Item->AsString());
+ ReplaceJsonValueNamesWithMap(Item, KeyMap);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/SIOJson/Private/SIOJLibrary.cpp b/Source/SIOJson/Private/SIOJLibrary.cpp
new file mode 100644
index 00000000..c2f7b1e8
--- /dev/null
+++ b/Source/SIOJson/Private/SIOJLibrary.cpp
@@ -0,0 +1,204 @@
+// Copyright 2016 Vladimir Alyamkin. All Rights Reserved.
+
+#include "SIOJsonPrivatePCH.h"
+#include "Base64.h"
+
+//////////////////////////////////////////////////////////////////////////
+// Helpers
+
+FString USIOJLibrary::PercentEncode(const FString& Source)
+{
+ FString OutText = Source;
+
+ OutText = OutText.Replace(TEXT(" "), TEXT("%20"));
+ OutText = OutText.Replace(TEXT("!"), TEXT("%21"));
+ OutText = OutText.Replace(TEXT("\""), TEXT("%22"));
+ OutText = OutText.Replace(TEXT("#"), TEXT("%23"));
+ OutText = OutText.Replace(TEXT("$"), TEXT("%24"));
+ OutText = OutText.Replace(TEXT("&"), TEXT("%26"));
+ OutText = OutText.Replace(TEXT("'"), TEXT("%27"));
+ OutText = OutText.Replace(TEXT("("), TEXT("%28"));
+ OutText = OutText.Replace(TEXT(")"), TEXT("%29"));
+ OutText = OutText.Replace(TEXT("*"), TEXT("%2A"));
+ OutText = OutText.Replace(TEXT("+"), TEXT("%2B"));
+ OutText = OutText.Replace(TEXT(","), TEXT("%2C"));
+ OutText = OutText.Replace(TEXT("/"), TEXT("%2F"));
+ OutText = OutText.Replace(TEXT(":"), TEXT("%3A"));
+ OutText = OutText.Replace(TEXT(";"), TEXT("%3B"));
+ OutText = OutText.Replace(TEXT("="), TEXT("%3D"));
+ OutText = OutText.Replace(TEXT("?"), TEXT("%3F"));
+ OutText = OutText.Replace(TEXT("@"), TEXT("%40"));
+ OutText = OutText.Replace(TEXT("["), TEXT("%5B"));
+ OutText = OutText.Replace(TEXT("]"), TEXT("%5D"));
+ OutText = OutText.Replace(TEXT("{"), TEXT("%7B"));
+ OutText = OutText.Replace(TEXT("}"), TEXT("%7D"));
+
+ return OutText;
+}
+
+FString USIOJLibrary::Base64Encode(const FString& Source)
+{
+ return FBase64::Encode(Source);
+}
+
+bool USIOJLibrary::Base64Decode(const FString& Source, FString& Dest)
+{
+ return FBase64::Decode(Source, Dest);
+}
+
+
+bool USIOJLibrary::StringToJsonValueArray(const FString& JsonString, TArray& OutJsonValueArray)
+{
+ TArray < TSharedPtr> RawJsonValueArray;
+ TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create(*JsonString);
+ FJsonSerializer::Deserialize(Reader, RawJsonValueArray);
+
+ for (auto Value : RawJsonValueArray)
+ {
+ auto SJsonValue = NewObject();
+ SJsonValue->SetRootValue(Value);
+ OutJsonValueArray.Add(SJsonValue);
+ }
+
+ return OutJsonValueArray.Num() > 0;
+}
+
+//////////////////////////////////////////////////////////////////////////
+// Easy URL processing
+
+TMap USIOJLibrary::RequestMap;
+
+
+FString USIOJLibrary::Conv_JsonObjectToString(USIOJsonObject* InObject)
+{
+ return InObject->EncodeJson();
+}
+
+
+USIOJsonObject* USIOJLibrary::Conv_JsonValueToJsonObject(class USIOJsonValue* InValue)
+{
+ return InValue->AsObject();
+}
+
+USIOJsonValue* USIOJLibrary::Conv_ArrayToJsonValue(const TArray& InArray)
+{
+ return USIOJsonValue::ConstructJsonValueArray(nullptr, InArray);
+}
+
+
+USIOJsonValue* USIOJLibrary::Conv_JsonObjectToJsonValue(USIOJsonObject* InObject)
+{
+ return USIOJsonValue::ConstructJsonValueObject(InObject, nullptr);
+}
+
+
+USIOJsonValue* USIOJLibrary::Conv_BytesToJsonValue(const TArray& InBytes)
+{
+ return USIOJsonValue::ConstructJsonValueBinary(nullptr, InBytes);
+}
+
+
+USIOJsonValue* USIOJLibrary::Conv_StringToJsonValue(const FString& InString)
+{
+ return USIOJsonValue::ConstructJsonValueString(nullptr, InString);
+}
+
+
+USIOJsonValue* USIOJLibrary::Conv_IntToJsonValue(int32 InInt)
+{
+ TSharedPtr NewVal = MakeShareable(new FJsonValueNumber(InInt));
+
+ USIOJsonValue* NewValue = NewObject();
+ NewValue->SetRootValue(NewVal);
+
+ return NewValue;
+}
+
+USIOJsonValue* USIOJLibrary::Conv_FloatToJsonValue(float InFloat)
+{
+ return USIOJsonValue::ConstructJsonValueNumber(nullptr, InFloat);
+}
+
+
+USIOJsonValue* USIOJLibrary::Conv_BoolToJsonValue(bool InBool)
+{
+ return USIOJsonValue::ConstructJsonValueBool(nullptr, InBool);
+}
+
+
+int32 USIOJLibrary::Conv_JsonValueToInt(USIOJsonValue* InValue)
+{
+ return (int32)InValue->AsNumber();
+}
+
+
+float USIOJLibrary::Conv_JsonValueToFloat(USIOJsonValue* InValue)
+{
+ return InValue->AsNumber();
+}
+
+
+bool USIOJLibrary::Conv_JsonValueToBool(USIOJsonValue* InValue)
+{
+ return InValue->AsBool();
+}
+
+
+TArray USIOJLibrary::Conv_JsonValueToBytes(USIOJsonValue* InValue)
+{
+ return InValue->AsBinary();
+}
+
+void USIOJLibrary::CallURL(UObject* WorldContextObject, const FString& URL, ERequestVerb Verb, ERequestContentType ContentType, USIOJsonObject* SIOJJson, const FSIOJCallDelegate& Callback)
+{
+ UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject);
+ if (World == nullptr)
+ {
+ UE_LOG(LogSIOJ, Error, TEXT("USIOJLibrary: Wrong world context"))
+ return;
+ }
+
+ // Check we have valid data json
+ if (SIOJJson == nullptr)
+ {
+ SIOJJson = USIOJsonObject::ConstructJsonObject(WorldContextObject);
+ }
+
+ USIOJRequestJSON* Request = NewObject();
+
+ Request->SetVerb(Verb);
+ Request->SetContentType(ContentType);
+ Request->SetRequestObject(SIOJJson);
+
+ FSIOJCallResponse Response;
+ Response.Request = Request;
+ Response.WorldContextObject = WorldContextObject;
+ Response.Callback = Callback;
+
+ Response.CompleteDelegateHandle = Request->OnStaticRequestComplete.AddStatic(&USIOJLibrary::OnCallComplete);
+ Response.FailDelegateHandle = Request->OnStaticRequestFail.AddStatic(&USIOJLibrary::OnCallComplete);
+
+ RequestMap.Add(Request, Response);
+
+ Request->ResetResponseData();
+ Request->ProcessURL(URL);
+}
+
+void USIOJLibrary::OnCallComplete(USIOJRequestJSON* Request)
+{
+ if (!RequestMap.Contains(Request))
+ {
+ return;
+ }
+
+ FSIOJCallResponse* Response = RequestMap.Find(Request);
+
+ Request->OnStaticRequestComplete.Remove(Response->CompleteDelegateHandle);
+ Request->OnStaticRequestFail.Remove(Response->FailDelegateHandle);
+
+ Response->Callback.ExecuteIfBound(Request);
+
+ Response->WorldContextObject = nullptr;
+ Response->Request = nullptr;
+ RequestMap.Remove(Request);
+}
diff --git a/Source/SIOJson/Private/SIOJRequestJSON.cpp b/Source/SIOJson/Private/SIOJRequestJSON.cpp
new file mode 100644
index 00000000..fa889638
--- /dev/null
+++ b/Source/SIOJson/Private/SIOJRequestJSON.cpp
@@ -0,0 +1,450 @@
+// Copyright 2014 Vladimir Alyamkin. All Rights Reserved.
+
+#include "SIOJsonPrivatePCH.h"
+#include "CoreMisc.h"
+
+template void FSIOJLatentAction::Cancel()
+{
+ UObject *Obj = Request.Get();
+ if (Obj != nullptr)
+ {
+ ((USIOJRequestJSON*)Obj)->Cancel();
+ }
+}
+
+USIOJRequestJSON::USIOJRequestJSON(const class FObjectInitializer& PCIP)
+ : Super(PCIP),
+ BinaryContentType(TEXT("application/octet-stream"))
+{
+ RequestVerb = ERequestVerb::GET;
+ RequestContentType = ERequestContentType::x_www_form_urlencoded_url;
+
+ ResetData();
+}
+
+USIOJRequestJSON* USIOJRequestJSON::ConstructRequest(UObject* WorldContextObject)
+{
+ return NewObject();
+}
+
+USIOJRequestJSON* USIOJRequestJSON::ConstructRequestExt(
+ UObject* WorldContextObject,
+ ERequestVerb Verb,
+ ERequestContentType ContentType)
+{
+ USIOJRequestJSON* Request = ConstructRequest(WorldContextObject);
+
+ Request->SetVerb(Verb);
+ Request->SetContentType(ContentType);
+
+ return Request;
+}
+
+void USIOJRequestJSON::SetVerb(ERequestVerb Verb)
+{
+ RequestVerb = Verb;
+}
+
+void USIOJRequestJSON::SetCustomVerb(FString Verb)
+{
+ CustomVerb = Verb;
+}
+
+void USIOJRequestJSON::SetContentType(ERequestContentType ContentType)
+{
+ RequestContentType = ContentType;
+}
+
+void USIOJRequestJSON::SetBinaryContentType(const FString &ContentType)
+{
+ BinaryContentType = ContentType;
+}
+
+void USIOJRequestJSON::SetBinaryRequestContent(const TArray &Bytes)
+{
+ RequestBytes = Bytes;
+}
+
+void USIOJRequestJSON::SetHeader(const FString& HeaderName, const FString& HeaderValue)
+{
+ RequestHeaders.Add(HeaderName, HeaderValue);
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+// Destruction and reset
+
+void USIOJRequestJSON::ResetData()
+{
+ ResetRequestData();
+ ResetResponseData();
+}
+
+void USIOJRequestJSON::ResetRequestData()
+{
+ if (RequestJsonObj != nullptr)
+ {
+ RequestJsonObj->Reset();
+ }
+ else
+ {
+ RequestJsonObj = NewObject();
+ }
+
+ HttpRequest = FHttpModule::Get().CreateRequest();
+}
+
+void USIOJRequestJSON::ResetResponseData()
+{
+ if (ResponseJsonObj != nullptr)
+ {
+ ResponseJsonObj->Reset();
+ }
+ else
+ {
+ ResponseJsonObj = NewObject();
+ }
+
+ ResponseHeaders.Empty();
+ ResponseCode = -1;
+
+ bIsValidJsonResponse = false;
+}
+
+void USIOJRequestJSON::Cancel()
+{
+ ContinueAction = nullptr;
+
+ ResetResponseData();
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+// JSON data accessors
+
+USIOJsonObject* USIOJRequestJSON::GetRequestObject()
+{
+ return RequestJsonObj;
+}
+
+void USIOJRequestJSON::SetRequestObject(USIOJsonObject* JsonObject)
+{
+ RequestJsonObj = JsonObject;
+}
+
+USIOJsonObject* USIOJRequestJSON::GetResponseObject()
+{
+ return ResponseJsonObj;
+}
+
+void USIOJRequestJSON::SetResponseObject(USIOJsonObject* JsonObject)
+{
+ ResponseJsonObj = JsonObject;
+}
+
+
+///////////////////////////////////////////////////////////////////////////
+// Response data access
+
+FString USIOJRequestJSON::GetURL()
+{
+ return HttpRequest->GetURL();
+}
+
+ERequestStatus USIOJRequestJSON::GetStatus()
+{
+ return ERequestStatus((uint8)HttpRequest->GetStatus());
+}
+
+int32 USIOJRequestJSON::GetResponseCode()
+{
+ return ResponseCode;
+}
+
+FString USIOJRequestJSON::GetResponseHeader(const FString HeaderName)
+{
+ FString Result;
+
+ FString* Header = ResponseHeaders.Find(HeaderName);
+ if (Header != nullptr)
+ {
+ Result = *Header;
+ }
+
+ return Result;
+}
+
+TArray USIOJRequestJSON::GetAllResponseHeaders()
+{
+ TArray Result;
+ for (TMap::TConstIterator It(ResponseHeaders); It; ++It)
+ {
+ Result.Add(It.Key() + TEXT(": ") + It.Value());
+ }
+ return Result;
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+// URL processing
+
+void USIOJRequestJSON::ProcessURL(const FString& Url)
+{
+ HttpRequest->SetURL(Url);
+
+ ProcessRequest();
+}
+
+void USIOJRequestJSON::ApplyURL(const FString& Url, USIOJsonObject *&Result, UObject* WorldContextObject, FLatentActionInfo LatentInfo)
+{
+ HttpRequest->SetURL(Url);
+
+ // Prepare latent action
+ if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject))
+ {
+ FLatentActionManager& LatentActionManager = World->GetLatentActionManager();
+ FSIOJLatentAction *Kont = LatentActionManager.FindExistingAction>(LatentInfo.CallbackTarget, LatentInfo.UUID);
+
+ if (Kont != nullptr)
+ {
+ Kont->Cancel();
+ LatentActionManager.RemoveActionsForObject(LatentInfo.CallbackTarget);
+ }
+
+ LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, ContinueAction = new FSIOJLatentAction(this, Result, LatentInfo));
+ }
+
+ ProcessRequest();
+}
+
+void USIOJRequestJSON::ProcessRequest()
+{
+ // Set verb
+ switch (RequestVerb)
+ {
+ case ERequestVerb::GET:
+ HttpRequest->SetVerb(TEXT("GET"));
+ break;
+
+ case ERequestVerb::POST:
+ HttpRequest->SetVerb(TEXT("POST"));
+ break;
+
+ case ERequestVerb::PUT:
+ HttpRequest->SetVerb(TEXT("PUT"));
+ break;
+
+ case ERequestVerb::DEL:
+ HttpRequest->SetVerb(TEXT("DELETE"));
+ break;
+
+ case ERequestVerb::CUSTOM:
+ HttpRequest->SetVerb(CustomVerb);
+ break;
+
+ default:
+ break;
+ }
+
+ // Set content-type
+ switch (RequestContentType)
+ {
+ case ERequestContentType::x_www_form_urlencoded_url:
+ {
+ HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/x-www-form-urlencoded"));
+
+ FString UrlParams = "";
+ uint16 ParamIdx = 0;
+
+ // Loop through all the values and prepare additional url part
+ for (auto RequestIt = RequestJsonObj->GetRootObject()->Values.CreateIterator(); RequestIt; ++RequestIt)
+ {
+ FString Key = RequestIt.Key();
+ FString Value = RequestIt.Value().Get()->AsString();
+
+ if (!Key.IsEmpty() && !Value.IsEmpty())
+ {
+ UrlParams += ParamIdx == 0 ? "?" : "&";
+ UrlParams += USIOJLibrary::PercentEncode(Key) + "=" + USIOJLibrary::PercentEncode(Value);
+ }
+
+ ParamIdx++;
+ }
+
+ // Apply params
+ HttpRequest->SetURL(HttpRequest->GetURL() + UrlParams);
+
+ UE_LOG(LogSIOJ, Log, TEXT("Request (urlencoded): %s %s %s"), *HttpRequest->GetVerb(), *HttpRequest->GetURL(), *UrlParams);
+
+ break;
+ }
+ case ERequestContentType::x_www_form_urlencoded_body:
+ {
+ HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/x-www-form-urlencoded"));
+
+ FString UrlParams = "";
+ uint16 ParamIdx = 0;
+
+ // Loop through all the values and prepare additional url part
+ for (auto RequestIt = RequestJsonObj->GetRootObject()->Values.CreateIterator(); RequestIt; ++RequestIt)
+ {
+ FString Key = RequestIt.Key();
+ FString Value = RequestIt.Value().Get()->AsString();
+
+ if (!Key.IsEmpty() && !Value.IsEmpty())
+ {
+ UrlParams += ParamIdx == 0 ? "" : "&";
+ UrlParams += USIOJLibrary::PercentEncode(Key) + "=" + USIOJLibrary::PercentEncode(Value);
+ }
+
+ ParamIdx++;
+ }
+
+ // Apply params
+ HttpRequest->SetContentAsString(UrlParams);
+
+ UE_LOG(LogSIOJ, Log, TEXT("Request (url body): %s %s %s"), *HttpRequest->GetVerb(), *HttpRequest->GetURL(), *UrlParams);
+
+ break;
+ }
+ case ERequestContentType::binary:
+ {
+ HttpRequest->SetHeader(TEXT("Content-Type"), BinaryContentType);
+ HttpRequest->SetContent(RequestBytes);
+
+ UE_LOG(LogSIOJ, Log, TEXT("Request (binary): %s %s"), *HttpRequest->GetVerb(), *HttpRequest->GetURL());
+
+ break;
+ }
+ case ERequestContentType::json:
+ {
+ HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
+
+ // Serialize data to json string
+ FString OutputString;
+ TSharedRef< TJsonWriter<> > Writer = TJsonWriterFactory<>::Create(&OutputString);
+ FJsonSerializer::Serialize(RequestJsonObj->GetRootObject().ToSharedRef(), Writer);
+
+ // Set Json content
+ HttpRequest->SetContentAsString(OutputString);
+
+ UE_LOG(LogSIOJ, Log, TEXT("Request (json): %s %s %s"), *HttpRequest->GetVerb(), *HttpRequest->GetURL(), *OutputString);
+
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ // Apply additional headers
+ for (TMap::TConstIterator It(RequestHeaders); It; ++It)
+ {
+ HttpRequest->SetHeader(It.Key(), It.Value());
+ }
+
+ // Bind event
+ HttpRequest->OnProcessRequestComplete().BindUObject(this, &USIOJRequestJSON::OnProcessRequestComplete);
+
+ // Execute the request
+ HttpRequest->ProcessRequest();
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+// Request callbacks
+
+void USIOJRequestJSON::OnProcessRequestComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
+{
+ // Be sure that we have no data from previous response
+ ResetResponseData();
+
+ // Check we have a response and save response code as int32
+ if(Response.IsValid())
+ {
+ ResponseCode = Response->GetResponseCode();
+ }
+
+ // Check we have result to process futher
+ if (!bWasSuccessful || !Response.IsValid())
+ {
+ UE_LOG(LogSIOJ, Error, TEXT("Request failed (%d): %s"), ResponseCode, *Request->GetURL());
+
+ // Broadcast the result event
+ OnRequestFail.Broadcast(this);
+ OnStaticRequestFail.Broadcast(this);
+
+ return;
+ }
+
+ // Save response data as a string
+ ResponseContent = Response->GetContentAsString();
+
+ // Log response state
+ UE_LOG(LogSIOJ, Log, TEXT("Response (%d): %s"), ResponseCode, *ResponseContent);
+
+ // Process response headers
+ TArray Headers = Response->GetAllHeaders();
+ for (FString Header : Headers)
+ {
+ FString Key;
+ FString Value;
+ if (Header.Split(TEXT(": "), &Key, &Value))
+ {
+ ResponseHeaders.Add(Key, Value);
+ }
+ }
+
+ // Try to deserialize data to JSON
+ TSharedRef> JsonReader = TJsonReaderFactory::Create(ResponseContent);
+ FJsonSerializer::Deserialize(JsonReader, ResponseJsonObj->GetRootObject());
+
+ // Decide whether the request was successful
+ bIsValidJsonResponse = bWasSuccessful && ResponseJsonObj->GetRootObject().IsValid();
+
+ // Log errors
+ if (!bIsValidJsonResponse)
+ {
+ if (!ResponseJsonObj->GetRootObject().IsValid())
+ {
+ // As we assume it's recommended way to use current class, but not the only one,
+ // it will be the warning instead of error
+ UE_LOG(LogSIOJ, Warning, TEXT("JSON could not be decoded!"));
+ }
+ }
+
+ // Broadcast the result event
+ OnRequestComplete.Broadcast(this);
+ OnStaticRequestComplete.Broadcast(this);
+
+ // Finish the latent action
+ if (ContinueAction)
+ {
+ FSIOJLatentAction *K = ContinueAction;
+ ContinueAction = nullptr;
+
+ K->Call(ResponseJsonObj);
+ }
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+// Tags
+
+void USIOJRequestJSON::AddTag(FName Tag)
+{
+ if (Tag != NAME_None)
+ {
+ Tags.AddUnique(Tag);
+ }
+}
+
+int32 USIOJRequestJSON::RemoveTag(FName Tag)
+{
+ return Tags.Remove(Tag);
+}
+
+bool USIOJRequestJSON::HasTag(FName Tag) const
+{
+ return (Tag != NAME_None) && Tags.Contains(Tag);
+}
diff --git a/Source/SIOJson/Private/SIOJson.cpp b/Source/SIOJson/Private/SIOJson.cpp
new file mode 100644
index 00000000..d1772f49
--- /dev/null
+++ b/Source/SIOJson/Private/SIOJson.cpp
@@ -0,0 +1,24 @@
+// Copyright 2014 Vladimir Alyamkin. All Rights Reserved.
+
+#include "SIOJsonPrivatePCH.h"
+
+class FSIOJson : public ISIOJson
+{
+ /** IModuleInterface implementation */
+ virtual void StartupModule() override
+ {
+ // @HACK Force classes to be compiled on shipping build
+ USIOJsonObject::StaticClass();
+ USIOJsonValue::StaticClass();
+ USIOJRequestJSON::StaticClass();
+ }
+
+ virtual void ShutdownModule() override
+ {
+
+ }
+};
+
+IMPLEMENT_MODULE(FSIOJson, SIOJson)
+
+DEFINE_LOG_CATEGORY(LogSIOJ);
diff --git a/Source/SIOJson/Private/SIOJsonObject.cpp b/Source/SIOJson/Private/SIOJsonObject.cpp
new file mode 100644
index 00000000..492ccd49
--- /dev/null
+++ b/Source/SIOJson/Private/SIOJsonObject.cpp
@@ -0,0 +1,549 @@
+// Copyright 2014 Vladimir Alyamkin. All Rights Reserved.
+
+#include "SIOJsonPrivatePCH.h"
+
+typedef TJsonWriterFactory< TCHAR, TCondensedJsonPrintPolicy > FCondensedJsonStringWriterFactory;
+typedef TJsonWriter< TCHAR, TCondensedJsonPrintPolicy > FCondensedJsonStringWriter;
+
+USIOJsonObject::USIOJsonObject(const class FObjectInitializer& PCIP)
+ : Super(PCIP)
+{
+ Reset();
+}
+
+USIOJsonObject* USIOJsonObject::ConstructJsonObject(UObject* WorldContextObject)
+{
+ return NewObject();
+}
+
+void USIOJsonObject::Reset()
+{
+ if (JsonObj.IsValid())
+ {
+ JsonObj.Reset();
+ }
+
+ JsonObj = MakeShareable(new FJsonObject());
+}
+
+TSharedPtr& USIOJsonObject::GetRootObject()
+{
+ return JsonObj;
+}
+
+void USIOJsonObject::SetRootObject(TSharedPtr& JsonObject)
+{
+ JsonObj = JsonObject;
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+// Serialization
+
+FString USIOJsonObject::EncodeJson() const
+{
+ if (!JsonObj.IsValid())
+ {
+ return TEXT("");
+ }
+
+ FString OutputString;
+ TSharedRef< FCondensedJsonStringWriter > Writer = FCondensedJsonStringWriterFactory::Create(&OutputString);
+ FJsonSerializer::Serialize(JsonObj.ToSharedRef(), Writer);
+
+ return OutputString;
+}
+
+FString USIOJsonObject::EncodeJsonToSingleString() const
+{
+ FString OutputString = EncodeJson();
+
+ // Remove line terminators
+ OutputString.Replace(LINE_TERMINATOR, TEXT(""));
+
+ // Remove tabs
+ OutputString.Replace(LINE_TERMINATOR, TEXT("\t"));
+
+ return OutputString;
+}
+
+bool USIOJsonObject::DecodeJson(const FString& JsonString)
+{
+ TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create(*JsonString);
+ if (FJsonSerializer::Deserialize(Reader, JsonObj) && JsonObj.IsValid())
+ {
+ return true;
+ }
+
+ // If we've failed to deserialize the string, we should clear our internal data
+ Reset();
+
+ UE_LOG(LogSIOJ, Error, TEXT("Json decoding failed for: %s"), *JsonString);
+
+ return false;
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+// FJsonObject API
+
+TArray USIOJsonObject::GetFieldNames()
+{
+ TArray Result;
+
+ if (!JsonObj.IsValid())
+ {
+ return Result;
+ }
+
+ JsonObj->Values.GetKeys(Result);
+
+ return Result;
+}
+
+bool USIOJsonObject::HasField(const FString& FieldName) const
+{
+ if (!JsonObj.IsValid() || FieldName.IsEmpty())
+ {
+ return false;
+ }
+
+ return JsonObj->HasField(FieldName);
+}
+
+void USIOJsonObject::RemoveField(const FString& FieldName)
+{
+ if (!JsonObj.IsValid() || FieldName.IsEmpty())
+ {
+ return;
+ }
+
+ JsonObj->RemoveField(FieldName);
+}
+
+USIOJsonValue* USIOJsonObject::GetField(const FString& FieldName) const
+{
+ if (!JsonObj.IsValid() || FieldName.IsEmpty())
+ {
+ return nullptr;
+ }
+
+ TSharedPtr NewVal = JsonObj->TryGetField(FieldName);
+ if (NewVal.IsValid())
+ {
+ USIOJsonValue* NewValue = NewObject();
+ NewValue->SetRootValue(NewVal);
+
+ return NewValue;
+ }
+
+ return nullptr;
+}
+
+void USIOJsonObject::SetField(const FString& FieldName, USIOJsonValue* JsonValue)
+{
+ if (!JsonObj.IsValid() || FieldName.IsEmpty())
+ {
+ return;
+ }
+
+ JsonObj->SetField(FieldName, JsonValue->GetRootValue());
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+// FJsonObject API Helpers (easy to use with simple Json objects)
+
+float USIOJsonObject::GetNumberField(const FString& FieldName) const
+{
+ if (!JsonObj.IsValid() || !JsonObj->HasTypedField(FieldName))
+ {
+ UE_LOG(LogSIOJ, Warning, TEXT("No field with name %s of type Number"), *FieldName);
+ return 0.0f;
+ }
+
+ return JsonObj->GetNumberField(FieldName);
+}
+
+void USIOJsonObject::SetNumberField(const FString& FieldName, float Number)
+{
+ if (!JsonObj.IsValid() || FieldName.IsEmpty())
+ {
+ return;
+ }
+
+ JsonObj->SetNumberField(FieldName, Number);
+}
+
+FString USIOJsonObject::GetStringField(const FString& FieldName) const
+{
+ if (!JsonObj.IsValid() || !JsonObj->HasTypedField(FieldName))
+ {
+ UE_LOG(LogSIOJ, Warning, TEXT("No field with name %s of type String"), *FieldName);
+ return TEXT("");
+ }
+
+ return JsonObj->GetStringField(FieldName);
+}
+
+void USIOJsonObject::SetStringField(const FString& FieldName, const FString& StringValue)
+{
+ if (!JsonObj.IsValid() || FieldName.IsEmpty())
+ {
+ return;
+ }
+
+ JsonObj->SetStringField(FieldName, StringValue);
+}
+
+bool USIOJsonObject::GetBoolField(const FString& FieldName) const
+{
+ if (!JsonObj.IsValid() || !JsonObj->HasTypedField(FieldName))
+ {
+ UE_LOG(LogSIOJ, Warning, TEXT("No field with name %s of type Boolean"), *FieldName);
+ return false;
+ }
+
+ return JsonObj->GetBoolField(FieldName);
+}
+
+void USIOJsonObject::SetBoolField(const FString& FieldName, bool InValue)
+{
+ if (!JsonObj.IsValid() || FieldName.IsEmpty())
+ {
+ return;
+ }
+
+ JsonObj->SetBoolField(FieldName, InValue);
+}
+
+TArray USIOJsonObject::GetArrayField(const FString& FieldName)
+{
+ if (!JsonObj->HasTypedField(FieldName))
+ {
+ UE_LOG(LogSIOJ, Warning, TEXT("No field with name %s of type Array"), *FieldName);
+ }
+
+ TArray OutArray;
+ if (!JsonObj.IsValid() || FieldName.IsEmpty())
+ {
+ return OutArray;
+ }
+
+ TArray< TSharedPtr > ValArray = JsonObj->GetArrayField(FieldName);
+ for (auto Value : ValArray)
+ {
+ USIOJsonValue* NewValue = NewObject();
+ NewValue->SetRootValue(Value);
+
+ OutArray.Add(NewValue);
+ }
+
+ return OutArray;
+}
+
+void USIOJsonObject::SetArrayField(const FString& FieldName, const TArray& InArray)
+{
+ if (!JsonObj.IsValid() || FieldName.IsEmpty())
+ {
+ return;
+ }
+
+ TArray< TSharedPtr > ValArray;
+
+ // Process input array and COPY original values
+ for (auto InVal : InArray)
+ {
+ TSharedPtr JsonVal = InVal->GetRootValue();
+
+ switch (InVal->GetType())
+ {
+ case ESIOJson::None:
+ break;
+
+ case ESIOJson::Null:
+ ValArray.Add(MakeShareable(new FJsonValueNull()));
+ break;
+
+ case ESIOJson::String:
+ ValArray.Add(MakeShareable(new FJsonValueString(JsonVal->AsString())));
+ break;
+
+ case ESIOJson::Number:
+ ValArray.Add(MakeShareable(new FJsonValueNumber(JsonVal->AsNumber())));
+ break;
+
+ case ESIOJson::Boolean:
+ ValArray.Add(MakeShareable(new FJsonValueBoolean(JsonVal->AsBool())));
+ break;
+
+ case ESIOJson::Array:
+ ValArray.Add(MakeShareable(new FJsonValueArray(JsonVal->AsArray())));
+ break;
+
+ case ESIOJson::Object:
+ ValArray.Add(MakeShareable(new FJsonValueObject(JsonVal->AsObject())));
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ JsonObj->SetArrayField(FieldName, ValArray);
+}
+
+void USIOJsonObject::MergeJsonObject(USIOJsonObject* InJsonObject, bool Overwrite)
+{
+ TArray Keys = InJsonObject->GetFieldNames();
+
+ for (auto Key : Keys)
+ {
+ if (Overwrite == false && HasField(Key))
+ {
+ continue;
+ }
+
+ SetField(Key, InJsonObject->GetField(Key));
+ }
+}
+
+USIOJsonObject* USIOJsonObject::GetObjectField(const FString& FieldName) const
+{
+ if (!JsonObj.IsValid() || !JsonObj->HasTypedField(FieldName))
+ {
+ UE_LOG(LogSIOJ, Warning, TEXT("No field with name %s of type Object"), *FieldName);
+ return nullptr;
+ }
+
+ TSharedPtr JsonObjField = JsonObj->GetObjectField(FieldName);
+
+ USIOJsonObject* OutRestJsonObj = NewObject();
+ OutRestJsonObj->SetRootObject(JsonObjField);
+
+ return OutRestJsonObj;
+}
+
+void USIOJsonObject::SetObjectField(const FString& FieldName, USIOJsonObject* JsonObject)
+{
+ if (!JsonObj.IsValid() || FieldName.IsEmpty())
+ {
+ return;
+ }
+
+ JsonObj->SetObjectField(FieldName, JsonObject->GetRootObject());
+}
+
+
+TArray USIOJsonObject::GetBinaryField(const FString& FieldName) const
+{
+ if (!JsonObj->HasTypedField(FieldName))
+ {
+ UE_LOG(LogSIOJ, Warning, TEXT("No field with name %s of type String"), *FieldName);
+ }
+ TSharedPtr JsonValue = JsonObj->TryGetField(FieldName);
+
+ if (FJsonValueBinary::IsBinary(JsonValue))
+ {
+ return FJsonValueBinary::AsBinary(JsonValue);
+ }
+ else
+ {
+ TArray EmptyArray;
+ return EmptyArray;
+ }
+}
+
+void USIOJsonObject::SetBinaryField(const FString& FieldName, const TArray& Bytes)
+{
+ if (!JsonObj.IsValid() || FieldName.IsEmpty())
+ {
+ return;
+ }
+ TSharedPtr JsonValue = MakeShareable(new FJsonValueBinary(Bytes));
+ JsonObj->SetField(FieldName, JsonValue);
+}
+
+//////////////////////////////////////////////////////////////////////////
+// Array fields helpers (uniform arrays)
+
+TArray USIOJsonObject::GetNumberArrayField(const FString& FieldName)
+{
+ if (!JsonObj->HasTypedField(FieldName))
+ {
+ UE_LOG(LogSIOJ, Warning, TEXT("No field with name %s of type Array"), *FieldName);
+ }
+
+ TArray NumberArray;
+ if (!JsonObj.IsValid() || FieldName.IsEmpty())
+ {
+ return NumberArray;
+ }
+
+ TArray > JsonArrayValues = JsonObj->GetArrayField(FieldName);
+ for (TArray >::TConstIterator It(JsonArrayValues); It; ++It)
+ {
+ auto Value = (*It).Get();
+ if (Value->Type != EJson::Number)
+ {
+ UE_LOG(LogSIOJ, Error, TEXT("Not Number element in array with field name %s"), *FieldName);
+ }
+
+ NumberArray.Add((*It)->AsNumber());
+ }
+
+ return NumberArray;
+}
+
+void USIOJsonObject::SetNumberArrayField(const FString& FieldName, const TArray& NumberArray)
+{
+ if (!JsonObj.IsValid() || FieldName.IsEmpty())
+ {
+ return;
+ }
+
+ TArray< TSharedPtr > EntriesArray;
+
+ for (auto Number : NumberArray)
+ {
+ EntriesArray.Add(MakeShareable(new FJsonValueNumber(Number)));
+ }
+
+ JsonObj->SetArrayField(FieldName, EntriesArray);
+}
+
+TArray USIOJsonObject::GetStringArrayField(const FString& FieldName)
+{
+ if (!JsonObj->HasTypedField(FieldName))
+ {
+ UE_LOG(LogSIOJ, Warning, TEXT("No field with name %s of type Array"), *FieldName);
+ }
+
+ TArray StringArray;
+ if (!JsonObj.IsValid() || FieldName.IsEmpty())
+ {
+ return StringArray;
+ }
+
+ TArray > JsonArrayValues = JsonObj->GetArrayField(FieldName);
+ for (TArray >::TConstIterator It(JsonArrayValues); It; ++It)
+ {
+ auto Value = (*It).Get();
+ if (Value->Type != EJson::String)
+ {
+ UE_LOG(LogSIOJ, Error, TEXT("Not String element in array with field name %s"), *FieldName);
+ }
+
+ StringArray.Add((*It)->AsString());
+ }
+
+ return StringArray;
+}
+
+void USIOJsonObject::SetStringArrayField(const FString& FieldName, const TArray& StringArray)
+{
+ if (!JsonObj.IsValid() || FieldName.IsEmpty())
+ {
+ return;
+ }
+
+ TArray< TSharedPtr > EntriesArray;
+ for (auto String : StringArray)
+ {
+ EntriesArray.Add(MakeShareable(new FJsonValueString(String)));
+ }
+
+ JsonObj->SetArrayField(FieldName, EntriesArray);
+}
+
+TArray USIOJsonObject::GetBoolArrayField(const FString& FieldName)
+{
+ if (!JsonObj->HasTypedField(FieldName))
+ {
+ UE_LOG(LogSIOJ, Warning, TEXT("No field with name %s of type Array"), *FieldName);
+ }
+
+ TArray BoolArray;
+ if (!JsonObj.IsValid() || FieldName.IsEmpty())
+ {
+ return BoolArray;
+ }
+
+ TArray > JsonArrayValues = JsonObj->GetArrayField(FieldName);
+ for (TArray >::TConstIterator It(JsonArrayValues); It; ++It)
+ {
+ auto Value = (*It).Get();
+ if (Value->Type != EJson::Boolean)
+ {
+ UE_LOG(LogSIOJ, Error, TEXT("Not Boolean element in array with field name %s"), *FieldName);
+ }
+
+ BoolArray.Add((*It)->AsBool());
+ }
+
+ return BoolArray;
+}
+
+void USIOJsonObject::SetBoolArrayField(const FString& FieldName, const TArray& BoolArray)
+{
+ if (!JsonObj.IsValid() || FieldName.IsEmpty())
+ {
+ return;
+ }
+
+ TArray< TSharedPtr > EntriesArray;
+ for (auto Boolean : BoolArray)
+ {
+ EntriesArray.Add(MakeShareable(new FJsonValueBoolean(Boolean)));
+ }
+
+ JsonObj->SetArrayField(FieldName, EntriesArray);
+}
+
+TArray USIOJsonObject::GetObjectArrayField(const FString& FieldName)
+{
+ if (!JsonObj->HasTypedField(FieldName))
+ {
+ UE_LOG(LogSIOJ, Warning, TEXT("No field with name %s of type Array"), *FieldName);
+ }
+
+ TArray OutArray;
+ if (!JsonObj.IsValid() || FieldName.IsEmpty())
+ {
+ return OutArray;
+ }
+
+ TArray< TSharedPtr > ValArray = JsonObj->GetArrayField(FieldName);
+ for (auto Value : ValArray)
+ {
+ if (Value->Type != EJson::Object)
+ {
+ UE_LOG(LogSIOJ, Error, TEXT("Not Object element in array with field name %s"), *FieldName);
+ }
+
+ TSharedPtr NewObj = Value->AsObject();
+
+ USIOJsonObject* NewJson = NewObject();
+ NewJson->SetRootObject(NewObj);
+
+ OutArray.Add(NewJson);
+ }
+
+ return OutArray;
+}
+
+void USIOJsonObject::SetObjectArrayField(const FString& FieldName, const TArray& ObjectArray)
+{
+ if (!JsonObj.IsValid() || FieldName.IsEmpty())
+ {
+ return;
+ }
+
+ TArray< TSharedPtr > EntriesArray;
+ for (auto Value : ObjectArray)
+ {
+ EntriesArray.Add(MakeShareable(new FJsonValueObject(Value->GetRootObject())));
+ }
+
+ JsonObj->SetArrayField(FieldName, EntriesArray);
+}
diff --git a/Source/SIOJson/Private/SIOJsonPrivatePCH.h b/Source/SIOJson/Private/SIOJsonPrivatePCH.h
new file mode 100644
index 00000000..b30916be
--- /dev/null
+++ b/Source/SIOJson/Private/SIOJsonPrivatePCH.h
@@ -0,0 +1,31 @@
+// Copyright 2014 Vladimir Alyamkin. All Rights Reserved.
+
+#pragma once
+
+#include "CoreUObject.h"
+#include "Engine.h"
+
+#include "Delegate.h"
+#include "Http.h"
+#include "Map.h"
+#include "Json.h"
+#include "JsonUtilities.h"
+
+#include "LatentActions.h"
+#include "Core.h"
+#include "Engine.h"
+#include "SharedPointer.h"
+
+// You should place include statements to your module's private header files here. You only need to
+// add includes for headers that are used in most of your module's source files though.
+#include "ModuleManager.h"
+
+DECLARE_LOG_CATEGORY_EXTERN(LogSIOJ, Log, All);
+
+#include "ISIOJson.h"
+#include "SIOJConvert.h"
+#include "SIOJsonObject.h"
+#include "SIOJsonValue.h"
+#include "SIOJLibrary.h"
+#include "SIOJRequestJSON.h"
+#include "SIOJTypes.h"
diff --git a/Source/SIOJson/Private/SIOJsonValue.cpp b/Source/SIOJson/Private/SIOJsonValue.cpp
new file mode 100644
index 00000000..9387ace4
--- /dev/null
+++ b/Source/SIOJson/Private/SIOJsonValue.cpp
@@ -0,0 +1,337 @@
+// Copyright 2014 Vladimir Alyamkin. All Rights Reserved.
+
+#include "SIOJsonPrivatePCH.h"
+#include "SIOJConvert.h"
+
+#pragma region FJsonValueBinary
+
+TArray FJsonValueBinary::AsBinary(const TSharedPtr& InJsonValue)
+{
+ if (FJsonValueBinary::IsBinary(InJsonValue))
+ {
+ TSharedPtr BinaryValue = StaticCastSharedPtr(InJsonValue);
+ return BinaryValue->AsBinary();
+ }
+ else
+ {
+ TArray EmptyArray;
+ return EmptyArray;
+ }
+}
+
+
+bool FJsonValueBinary::IsBinary(const TSharedPtr& InJsonValue)
+{
+ //use our hackery to determine if we got a binary string
+ bool IgnoreBool;
+ return !InJsonValue->TryGetBool(IgnoreBool);
+}
+
+#pragma endregion FJsonValueBinary
+#pragma region USIOJsonValue
+
+USIOJsonValue::USIOJsonValue(const class FObjectInitializer& PCIP)
+ : Super(PCIP)
+{
+
+}
+
+USIOJsonValue* USIOJsonValue::ConstructJsonValueNumber(UObject* WorldContextObject, float Number)
+{
+ TSharedPtr NewVal = MakeShareable(new FJsonValueNumber(Number));
+
+ USIOJsonValue* NewValue = NewObject();
+ NewValue->SetRootValue(NewVal);
+
+ return NewValue;
+}
+
+USIOJsonValue* USIOJsonValue::ConstructJsonValueString(UObject* WorldContextObject, const FString& StringValue)
+{
+ TSharedPtr NewVal = MakeShareable(new FJsonValueString(StringValue));
+
+ USIOJsonValue* NewValue = NewObject();
+ NewValue->SetRootValue(NewVal);
+
+ return NewValue;
+}
+
+USIOJsonValue* USIOJsonValue::ConstructJsonValueBool(UObject* WorldContextObject, bool InValue)
+{
+ TSharedPtr NewVal = MakeShareable(new FJsonValueBoolean(InValue));
+
+ USIOJsonValue* NewValue = NewObject();
+ NewValue->SetRootValue(NewVal);
+
+ return NewValue;
+}
+
+USIOJsonValue* USIOJsonValue::ConstructJsonValueArray(UObject* WorldContextObject, const TArray& InArray)
+{
+ // Prepare data array to create new value
+ TArray< TSharedPtr > ValueArray;
+ for (auto InVal : InArray)
+ {
+ ValueArray.Add(InVal->GetRootValue());
+ }
+
+ TSharedPtr NewVal = MakeShareable(new FJsonValueArray(ValueArray));
+
+ USIOJsonValue* NewValue = NewObject();
+ NewValue->SetRootValue(NewVal);
+
+ return NewValue;
+}
+
+USIOJsonValue* USIOJsonValue::ConstructJsonValueObject(USIOJsonObject *JsonObject, UObject* WorldContextObject)
+{
+ TSharedPtr NewVal = MakeShareable(new FJsonValueObject(JsonObject->GetRootObject()));
+
+ USIOJsonValue* NewValue = NewObject();
+ NewValue->SetRootValue(NewVal);
+
+ return NewValue;
+}
+
+
+USIOJsonValue* USIOJsonValue::ConstructJsonValueBinary(UObject* WorldContextObject, TArray ByteArray)
+{
+ TSharedPtr NewVal = MakeShareable(new FJsonValueBinary(ByteArray));
+
+ USIOJsonValue* NewValue = NewObject();
+ NewValue->SetRootValue(NewVal);
+
+ return NewValue;
+}
+
+USIOJsonValue* ConstructJsonValue(UObject* WorldContextObject, const TSharedPtr& InValue)
+{
+ TSharedPtr NewVal = InValue;
+
+ USIOJsonValue* NewValue = NewObject();
+ NewValue->SetRootValue(NewVal);
+
+ return NewValue;
+}
+
+
+USIOJsonValue* USIOJsonValue::ValueFromJsonString(UObject* WorldContextObject, const FString& StringValue)
+{
+ TSharedPtr NewVal = USIOJConvert::JsonStringToJsonValue(StringValue);
+
+ USIOJsonValue* NewValue = NewObject();
+ NewValue->SetRootValue(NewVal);
+
+ return NewValue;
+}
+
+TSharedPtr& USIOJsonValue::GetRootValue()
+{
+ return JsonVal;
+}
+
+void USIOJsonValue::SetRootValue(TSharedPtr& JsonValue)
+{
+ JsonVal = JsonValue;
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+// FJsonValue API
+
+ESIOJson::Type USIOJsonValue::GetType() const
+{
+ if (!JsonVal.IsValid())
+ {
+ return ESIOJson::None;
+ }
+
+ switch (JsonVal->Type)
+ {
+ case EJson::None:
+ return ESIOJson::None;
+
+ case EJson::Null:
+ return ESIOJson::Null;
+
+ case EJson::String:
+ if (FJsonValueBinary::IsBinary(JsonVal))
+ {
+ return ESIOJson::Binary;
+ }
+ else
+ {
+ return ESIOJson::String;
+ }
+ case EJson::Number:
+ return ESIOJson::Number;
+
+ case EJson::Boolean:
+ return ESIOJson::Boolean;
+
+ case EJson::Array:
+ return ESIOJson::Array;
+
+ case EJson::Object:
+ return ESIOJson::Object;
+
+ default:
+ return ESIOJson::None;
+ }
+}
+
+FString USIOJsonValue::GetTypeString() const
+{
+ if (!JsonVal.IsValid())
+ {
+ return "None";
+ }
+
+ switch (JsonVal->Type)
+ {
+ case EJson::None:
+ return TEXT("None");
+
+ case EJson::Null:
+ return TEXT("Null");
+
+ case EJson::String:
+ return TEXT("String");
+
+ case EJson::Number:
+ return TEXT("Number");
+
+ case EJson::Boolean:
+ return TEXT("Boolean");
+
+ case EJson::Array:
+ return TEXT("Array");
+
+ case EJson::Object:
+ return TEXT("Object");
+
+ default:
+ return TEXT("None");
+ }
+}
+
+bool USIOJsonValue::IsNull() const
+{
+ if (!JsonVal.IsValid())
+ {
+ return true;
+ }
+
+ return JsonVal->IsNull();
+}
+
+float USIOJsonValue::AsNumber() const
+{
+ if (!JsonVal.IsValid())
+ {
+ ErrorMessage(TEXT("Number"));
+ return 0.f;
+ }
+
+ return JsonVal->AsNumber();
+}
+
+FString USIOJsonValue::AsString() const
+{
+ if (!JsonVal.IsValid())
+ {
+ ErrorMessage(TEXT("String"));
+ return FString();
+ }
+
+ return JsonVal->AsString();
+}
+
+bool USIOJsonValue::AsBool() const
+{
+ if (!JsonVal.IsValid())
+ {
+ ErrorMessage(TEXT("Boolean"));
+ return false;
+ }
+
+ return JsonVal->AsBool();
+}
+
+TArray USIOJsonValue::AsArray() const
+{
+ TArray OutArray;
+
+ if (!JsonVal.IsValid())
+ {
+ ErrorMessage(TEXT("Array"));
+ return OutArray;
+ }
+
+ TArray< TSharedPtr > ValArray = JsonVal->AsArray();
+ for (auto Value : ValArray)
+ {
+ USIOJsonValue* NewValue = NewObject();
+ NewValue->SetRootValue(Value);
+
+ OutArray.Add(NewValue);
+ }
+
+ return OutArray;
+}
+
+USIOJsonObject* USIOJsonValue::AsObject()
+{
+ if (!JsonVal.IsValid())
+ {
+ ErrorMessage(TEXT("Object"));
+ return nullptr;
+ }
+
+ TSharedPtr NewObj = JsonVal->AsObject();
+
+ USIOJsonObject* JsonObj = NewObject();
+ JsonObj->SetRootObject(NewObj);
+
+ return JsonObj;
+}
+
+
+TArray USIOJsonValue::AsBinary()
+{
+ if (!JsonVal.IsValid())
+ {
+ ErrorMessage(TEXT("Binary"));
+ TArray ByteArray;
+ return ByteArray;
+ }
+
+ //binary object pretending & starts with non-json format? it's our disguise binary
+ if (JsonVal->Type == EJson::String &&
+ FJsonValueBinary::IsBinary(JsonVal))
+ {
+ //Valid binary available
+ return FJsonValueBinary::AsBinary(JsonVal);
+ }
+ else
+ {
+ //Empty array
+ TArray ByteArray;
+ return ByteArray;
+ }
+}
+
+FString USIOJsonValue::EncodeJson() const
+{
+ return USIOJConvert::ToJsonString(JsonVal);
+}
+
+//////////////////////////////////////////////////////////////////////////
+// Helpers
+
+void USIOJsonValue::ErrorMessage(const FString& InType) const
+{
+ UE_LOG(LogSIOJ, Error, TEXT("Json Value of type '%s' used as a '%s'."), *GetTypeString(), *InType);
+}
+
+#pragma endregion USIOJsonValue
\ No newline at end of file
diff --git a/Source/SIOJson/Public/ISIOJson.h b/Source/SIOJson/Public/ISIOJson.h
new file mode 100644
index 00000000..62c5bf4f
--- /dev/null
+++ b/Source/SIOJson/Public/ISIOJson.h
@@ -0,0 +1,38 @@
+// Copyright 2014 Vladimir Alyamkin. All Rights Reserved.
+
+#pragma once
+
+#include "ModuleManager.h"
+
+
+/**
+ * The public interface to this module. In most cases, this interface is only public to sibling modules
+ * within this plugin.
+ */
+class ISIOJson : public IModuleInterface
+{
+
+public:
+
+ /**
+ * Singleton-like access to this module's interface. This is just for convenience!
+ * Beware of calling this during the shutdown phase, though. Your module might have been unloaded already.
+ *
+ * @return Returns singleton instance, loading the module on demand if needed
+ */
+ static inline ISIOJson& Get()
+ {
+ return FModuleManager::LoadModuleChecked< ISIOJson >( "SIOJson" );
+ }
+
+ /**
+ * Checks to see if this module is loaded and ready. It is only valid to call Get() if IsAvailable() returns true.
+ *
+ * @return True if the module is loaded and ready to use
+ */
+ static inline bool IsAvailable()
+ {
+ return FModuleManager::Get().IsModuleLoaded( "SIOJson" );
+ }
+};
+
diff --git a/Source/SIOJson/Public/SIOJConvert.h b/Source/SIOJson/Public/SIOJConvert.h
new file mode 100644
index 00000000..163cf380
--- /dev/null
+++ b/Source/SIOJson/Public/SIOJConvert.h
@@ -0,0 +1,62 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#pragma once
+
+#include "Object.h"
+#include "SIOJConvert.generated.h"
+
+struct FTrimmedKeyMap
+{
+ FString LongKey;
+ TMap> SubMap;
+};
+
+/**
+ *
+ */
+UCLASS()
+class SIOJSON_API USIOJConvert : public UObject
+{
+ GENERATED_BODY()
+public:
+
+ //encode/decode json convenience wrappers
+ static FString ToJsonString(const TSharedPtr& JsonObject);
+ static FString ToJsonString(const TSharedPtr& JsonValue);
+ static FString ToJsonString(const TArray>& JsonValueArray);
+
+ static TSharedPtr ToJsonObject(const FString& JsonString);
+ static TSharedPtr MakeJsonObject();
+
+
+ //Structs
+
+ //Will trim names if specified as blueprint
+ static TSharedPtr ToJsonObject(UStruct* Struct, void* StructPtr, bool IsBlueprintStruct = false);
+
+ //Expects a JsonObject, if blueprint struct it will lengthen the names to fill properly
+ static bool JsonObjectToUStruct(TSharedPtr JsonObject, UStruct* Struct, void* StructPtr, bool IsBlueprintStruct = false);
+
+
+ //typically from callbacks
+ static class USIOJsonValue* ToSIOJsonValue(const TArray>& JsonValueArray);
+
+ //Convenience overrides for JsonValues
+ static TSharedPtr ToJsonValue(const TSharedPtr& JsonObject);
+ static TSharedPtr ToJsonValue(const FString& StringValue);
+ static TSharedPtr ToJsonValue(double NumberValue);
+ static TSharedPtr ToJsonValue(bool BoolValue);
+ static TSharedPtr ToJsonValue(const TArray& BinaryValue);
+ static TSharedPtr ToJsonValue(const TArray>& ArrayValue);
+
+ static TSharedPtr JsonStringToJsonValue(const FString& JsonString);
+ static TArray> JsonStringToJsonArray(const FString& JsonString);
+
+
+ //internal utility, exposed for modularity
+ static void TrimValueKeyNames(const TSharedPtr& JsonValue);
+ static bool TrimKey(const FString& InLongKey, FString& OutTrimmedKey);
+ static void SetTrimmedKeyMapForStruct(TSharedPtr& InMap, UStruct* Struct);
+ static void SetTrimmedKeyMapForProp(TSharedPtr& InMap, UProperty* ArrayInnerProp);
+ static void ReplaceJsonValueNamesWithMap(TSharedPtr& InValue, TSharedPtr KeyMap);
+};
diff --git a/Source/SIOJson/Public/SIOJLibrary.h b/Source/SIOJson/Public/SIOJLibrary.h
new file mode 100644
index 00000000..61543a2b
--- /dev/null
+++ b/Source/SIOJson/Public/SIOJLibrary.h
@@ -0,0 +1,199 @@
+// Copyright 2016 Vladimir Alyamkin. All Rights Reserved.
+
+#pragma once
+
+#include "Kismet/BlueprintFunctionLibrary.h"
+
+#include "SIOJTypes.h"
+#include "SIOJLibrary.generated.h"
+
+class USIOJRequestJSON;
+class USIOJsonObject;
+
+DECLARE_DYNAMIC_DELEGATE_OneParam(FSIOJCallDelegate, USIOJRequestJSON*, Request);
+
+USTRUCT()
+struct FSIOJCallResponse
+{
+ GENERATED_USTRUCT_BODY()
+
+ UPROPERTY()
+ USIOJRequestJSON* Request;
+
+ UPROPERTY()
+ UObject* WorldContextObject;
+
+ UPROPERTY()
+ FSIOJCallDelegate Callback;
+
+ FDelegateHandle CompleteDelegateHandle;
+ FDelegateHandle FailDelegateHandle;
+
+ FSIOJCallResponse()
+ : Request(nullptr)
+ , WorldContextObject(nullptr)
+ {
+ }
+
+};
+
+/**
+ * Usefull tools for REST communications
+ */
+UCLASS()
+class USIOJLibrary : public UBlueprintFunctionLibrary
+{
+ GENERATED_BODY()
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // Helpers
+
+public:
+ /** Applies percent-encoding to text */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Utility")
+ static FString PercentEncode(const FString& Source);
+
+ /**
+ * Encodes a FString into a Base64 string
+ *
+ * @param Source The string data to convert
+ * @return A string that encodes the binary data in a way that can be safely transmitted via various Internet protocols
+ */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Utility", meta = (DisplayName = "Base64 Encode"))
+ static FString Base64Encode(const FString& Source);
+
+ /**
+ * Decodes a Base64 string into a FString
+ *
+ * @param Source The stringified data to convert
+ * @param Dest The out buffer that will be filled with the decoded data
+ * @return True if the buffer was decoded, false if it failed to decode
+ */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Utility", meta = (DisplayName = "Base64 Decode"))
+ static bool Base64Decode(const FString& Source, FString& Dest);
+
+ //////////////////////////////////////////////////////////////////////////
+ // Easy URL processing
+
+ /**
+ * Decodes a json string into an array of stringified json Values
+ *
+ * @param JsonString Input stringified json
+ * @param OutJsonValueArray The decoded Array of JsonValue
+ */
+ UFUNCTION(BlueprintPure, Category = "SIOJ|Utility")
+ static bool StringToJsonValueArray(const FString& JsonString, TArray& OutJsonValueArray);
+
+ /**
+ * Uses the reflection system to convert an unreal struct into a JsonObject
+ *
+ * @param AnyStruct The struct you wish to convert
+ * @return Converted Json Object
+ */
+ UFUNCTION(BlueprintPure, Category = "SocketIOFunctions", CustomThunk, meta = (CustomStructureParam = "AnyStruct"))
+ static USIOJsonObject* StructToJsonObject(UProperty* AnyStruct);
+
+ /**
+ * Uses the reflection system to fill an unreal struct from data defined in JsonObject.
+ *
+ * @param JsonObject The source JsonObject for properties to fill
+ * @param AnyStruct The struct you wish to fill
+ * @return Whether all properties filled correctly
+ */
+ UFUNCTION(BlueprintCallable, Category = "SocketIOFunctions", CustomThunk, meta = (CustomStructureParam = "AnyStruct"))
+ static bool JsonObjectToStruct(USIOJsonObject* JsonObject, UProperty* AnyStruct);
+
+ //Convert property into c++ accessible form
+ DECLARE_FUNCTION(execStructToJsonObject)
+ {
+ //Get properties and pointers from stack
+ Stack.Step(Stack.Object, NULL);
+ UStructProperty* StructProperty = ExactCast(Stack.MostRecentProperty);
+ void* StructPtr = Stack.MostRecentPropertyAddress;
+
+ // We need this to wrap up the stack
+ P_FINISH;
+
+ auto BPJsonObject = NewObject();
+
+ auto JsonObject = USIOJConvert::ToJsonObject(StructProperty->Struct, StructPtr, true);
+ BPJsonObject->SetRootObject(JsonObject);
+
+ *(USIOJsonObject**)RESULT_PARAM = BPJsonObject;
+ }
+
+ DECLARE_FUNCTION(execJsonObjectToStruct)
+ {
+ //Get properties and pointers from stack
+ P_GET_OBJECT(USIOJsonObject, JsonObject);
+
+ Stack.Step(Stack.Object, NULL);
+ UStructProperty* StructProperty = ExactCast(Stack.MostRecentProperty);
+ void* StructPtr = Stack.MostRecentPropertyAddress;
+
+ P_FINISH;
+
+ //Pass in the reference to the json object
+ bool Success = USIOJConvert::JsonObjectToUStruct(JsonObject->GetRootObject(), StructProperty->Struct, StructPtr, true);
+
+ *(bool*)RESULT_PARAM = Success;
+ }
+
+ //Conversion Nodes
+
+ //ToJsonValue
+
+ UFUNCTION(BlueprintPure, meta = (DisplayName = "ToJsonValue (Array)", BlueprintAutocast), Category = "Utilities|SocketIO")
+ static USIOJsonValue* Conv_ArrayToJsonValue(const TArray& InArray);
+
+ UFUNCTION(BlueprintPure, meta = (DisplayName = "ToJsonValue (JsonObject)", BlueprintAutocast), Category = "Utilities|SocketIO")
+ static USIOJsonValue* Conv_JsonObjectToJsonValue(USIOJsonObject* InObject);
+
+ UFUNCTION(BlueprintPure, meta = (DisplayName = "ToJsonValue (Bytes)", BlueprintAutocast), Category = "Utilities|SocketIO")
+ static USIOJsonValue* Conv_BytesToJsonValue(const TArray& InBytes);
+
+ UFUNCTION(BlueprintPure, meta = (DisplayName = "ToJsonValue (String)", BlueprintAutocast), Category = "Utilities|SocketIO")
+ static USIOJsonValue* Conv_StringToJsonValue(const FString& InString);
+
+ UFUNCTION(BlueprintPure, meta = (DisplayName = "ToJsonValue (Integer)", BlueprintAutocast), Category = "Utilities|SocketIO")
+ static USIOJsonValue* Conv_IntToJsonValue(int32 InInt);
+
+ UFUNCTION(BlueprintPure, meta = (DisplayName = "ToJsonValue (Float)", BlueprintAutocast), Category = "Utilities|SocketIO")
+ static USIOJsonValue* Conv_FloatToJsonValue(float InFloat);
+
+ UFUNCTION(BlueprintPure, meta = (DisplayName = "ToJsonValue (Bool)", BlueprintAutocast), Category = "Utilities|SocketIO")
+ static USIOJsonValue* Conv_BoolToJsonValue(bool InBool);
+
+ //To Native Types
+ UFUNCTION(BlueprintPure, meta = (DisplayName = "To Integer (JsonValue)", BlueprintAutocast), Category = "Utilities|SocketIO")
+ static int32 Conv_JsonValueToInt(class USIOJsonValue* InValue);
+
+ UFUNCTION(BlueprintPure, meta = (DisplayName = "To Float (JsonValue)", BlueprintAutocast), Category = "Utilities|SocketIO")
+ static float Conv_JsonValueToFloat(class USIOJsonValue* InValue);
+
+ UFUNCTION(BlueprintPure, meta = (DisplayName = "To Bool (JsonValue)", BlueprintAutocast), Category = "Utilities|SocketIO")
+ static bool Conv_JsonValueToBool(class USIOJsonValue* InValue);
+
+ UFUNCTION(BlueprintPure, meta = (DisplayName = "To Bytes (JsonValue)", BlueprintAutocast), Category = "Utilities|SocketIO")
+ static TArray Conv_JsonValueToBytes(class USIOJsonValue* InValue);
+
+ //ToString - these never get called sadly, kismet library get display name takes priority
+ UFUNCTION(BlueprintPure, meta = (DisplayName = "ToString (SIOJsonObject)", CompactNodeTitle = "->", BlueprintAutocast), Category = "Utilities|SocketIO")
+ static FString Conv_JsonObjectToString(class USIOJsonObject* InObject);
+
+ UFUNCTION(BlueprintPure, meta = (DisplayName = "To Object (JsonValue)", BlueprintAutocast), Category = "Utilities|SocketIO")
+ static USIOJsonObject* Conv_JsonValueToJsonObject(class USIOJsonValue* InValue);
+
+public:
+ /** Easy way to process http requests */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Utility", meta = (WorldContext = "WorldContextObject"))
+ static void CallURL(UObject* WorldContextObject, const FString& URL, ERequestVerb Verb, ERequestContentType ContentType, USIOJsonObject* SIOJJson, const FSIOJCallDelegate& Callback);
+
+ /** Called when URL is processed (one for both success/unsuccess events)*/
+ static void OnCallComplete(USIOJRequestJSON* Request);
+
+private:
+ static TMap RequestMap;
+
+};
diff --git a/Source/SIOJson/Public/SIOJRequestJSON.h b/Source/SIOJson/Public/SIOJRequestJSON.h
new file mode 100644
index 00000000..090d9094
--- /dev/null
+++ b/Source/SIOJson/Public/SIOJRequestJSON.h
@@ -0,0 +1,301 @@
+// Copyright 2014 Vladimir Alyamkin. All Rights Reserved.
+
+#pragma once
+
+#include "Delegate.h"
+#include "Http.h"
+#include "Map.h"
+#include "Json.h"
+
+#include "SIOJTypes.h"
+#include "SIOJRequestJSON.generated.h"
+
+/**
+ * @author Original latent action class by https://github.com/unktomi
+ */
+template class FSIOJLatentAction : public FPendingLatentAction
+{
+public:
+ virtual void Call(const T &Value)
+ {
+ Result = Value;
+ Called = true;
+ }
+
+ void operator()(const T &Value)
+ {
+ Call(Value);
+ }
+
+ void Cancel();
+
+ FSIOJLatentAction(FWeakObjectPtr RequestObj, T& ResultParam, const FLatentActionInfo& LatentInfo) :
+ Called(false),
+ Request(RequestObj),
+ ExecutionFunction(LatentInfo.ExecutionFunction),
+ OutputLink(LatentInfo.Linkage),
+ CallbackTarget(LatentInfo.CallbackTarget),
+ Result(ResultParam)
+ {
+ }
+
+ virtual void UpdateOperation(FLatentResponse& Response) override
+ {
+ Response.FinishAndTriggerIf(Called, ExecutionFunction, OutputLink, CallbackTarget);
+ }
+
+ virtual void NotifyObjectDestroyed()
+ {
+ Cancel();
+ }
+
+ virtual void NotifyActionAborted()
+ {
+ Cancel();
+ }
+
+private:
+ bool Called;
+ FWeakObjectPtr Request;
+
+public:
+ const FName ExecutionFunction;
+ const int32 OutputLink;
+ const FWeakObjectPtr CallbackTarget;
+ T &Result;
+};
+
+
+/** Generate a delegates for callback events */
+DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnRequestComplete, class USIOJRequestJSON*, Request);
+DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnRequestFail, class USIOJRequestJSON*, Request);
+
+DECLARE_MULTICAST_DELEGATE_OneParam(FOnStaticRequestComplete, class USIOJRequestJSON*);
+DECLARE_MULTICAST_DELEGATE_OneParam(FOnStaticRequestFail, class USIOJRequestJSON*);
+
+
+/**
+ * General helper class http requests via blueprints
+ */
+UCLASS(BlueprintType, Blueprintable)
+class SIOJSON_API USIOJRequestJSON : public UObject
+{
+ GENERATED_UCLASS_BODY()
+
+public:
+ //////////////////////////////////////////////////////////////////////////
+ // Construction
+
+ /** Creates new request (totally empty) */
+ UFUNCTION(BlueprintPure, meta = (DisplayName = "Construct Json Request (Empty)", HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject"), Category = "SIOJ|Request")
+ static USIOJRequestJSON* ConstructRequest(UObject* WorldContextObject);
+
+ /** Creates new request with defined verb and content type */
+ UFUNCTION(BlueprintPure, meta = (DisplayName = "Construct Json Request", HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject"), Category = "SIOJ|Request")
+ static USIOJRequestJSON* ConstructRequestExt(UObject* WorldContextObject, ERequestVerb Verb, ERequestContentType ContentType);
+
+ /** Set verb to the request */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Request")
+ void SetVerb(ERequestVerb Verb);
+
+ /** Set custom verb to the request */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Request")
+ void SetCustomVerb(FString Verb);
+
+ /** Set content type to the request. If you're using the x-www-form-urlencoded,
+ * params/constaints should be defined as key=ValueString pairs from Json data */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Request")
+ void SetContentType(ERequestContentType ContentType);
+
+ /** Set content type of the request for binary post data */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Request")
+ void SetBinaryContentType(const FString &ContentType);
+
+ /** Set content of the request for binary post data */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Request")
+ void SetBinaryRequestContent(const TArray &Content);
+
+ /** Sets optional header info */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Request")
+ void SetHeader(const FString& HeaderName, const FString& HeaderValue);
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // Destruction and reset
+
+ /** Reset all internal saved data */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Utility")
+ void ResetData();
+
+ /** Reset saved request data */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Request")
+ void ResetRequestData();
+
+ /** Reset saved response data */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Response")
+ void ResetResponseData();
+
+ /** Cancel latent response waiting */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Response")
+ void Cancel();
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // JSON data accessors
+
+ /** Get the Request Json object */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Request")
+ USIOJsonObject* GetRequestObject();
+
+ /** Set the Request Json object */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Request")
+ void SetRequestObject(USIOJsonObject* JsonObject);
+
+ /** Get the Response Json object */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Response")
+ USIOJsonObject* GetResponseObject();
+
+ /** Set the Response Json object */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Response")
+ void SetResponseObject(USIOJsonObject* JsonObject);
+
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Request/response data access
+
+ /** Get url of http request */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Request")
+ FString GetURL();
+
+ /** Get status of http request */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Request")
+ ERequestStatus GetStatus();
+
+ /** Get the response code of the last query */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Response")
+ int32 GetResponseCode();
+
+ /** Get value of desired response header */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Response")
+ FString GetResponseHeader(const FString HeaderName);
+
+ /** Get list of all response headers */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Response")
+ TArray GetAllResponseHeaders();
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // URL processing
+
+ /** Open URL with current setup */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Request")
+ virtual void ProcessURL(const FString& Url = TEXT("http://alyamkin.com"));
+
+ /** Open URL in latent mode */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Request", meta = (Latent, LatentInfo = "LatentInfo", HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject"))
+ virtual void ApplyURL(const FString& Url, USIOJsonObject *&Result, UObject* WorldContextObject, struct FLatentActionInfo LatentInfo);
+
+ /** Apply current internal setup to request and process it */
+ void ProcessRequest();
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // Request callbacks
+
+private:
+ /** Internal bind function for the IHTTPRequest::OnProcessRequestCompleted() event */
+ void OnProcessRequestComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful);
+
+public:
+ /** Event occured when the request has been completed */
+ UPROPERTY(BlueprintAssignable, Category = "SIOJ|Event")
+ FOnRequestComplete OnRequestComplete;
+
+ /** Event occured when the request wasn't successfull */
+ UPROPERTY(BlueprintAssignable, Category = "SIOJ|Event")
+ FOnRequestFail OnRequestFail;
+
+ /** Event occured when the request has been completed */
+ FOnStaticRequestComplete OnStaticRequestComplete;
+
+ /** Event occured when the request wasn't successfull */
+ FOnStaticRequestFail OnStaticRequestFail;
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // Tags
+
+public:
+ /** Add tag to this request */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Utility")
+ void AddTag(FName Tag);
+
+ /**
+ * Remove tag from this request
+ *
+ * @return Number of removed elements
+ */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Utility")
+ int32 RemoveTag(FName Tag);
+
+ /** See if this request contains the supplied tag */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Utility")
+ bool HasTag(FName Tag) const;
+
+protected:
+ /** Array of tags that can be used for grouping and categorizing */
+ TArray Tags;
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // Data
+
+public:
+ /** Request response stored as a string */
+ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "SIOJ|Response")
+ FString ResponseContent;
+
+ /** Is the response valid JSON? */
+ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "SIOJ|Response")
+ bool bIsValidJsonResponse;
+
+protected:
+ /** Latent action helper */
+ FSIOJLatentAction* ContinueAction;
+
+ /** Internal request data stored as JSON */
+ UPROPERTY()
+ USIOJsonObject* RequestJsonObj;
+
+ UPROPERTY()
+ TArray RequestBytes;
+
+ UPROPERTY()
+ FString BinaryContentType;
+
+ /** Response data stored as JSON */
+ UPROPERTY()
+ USIOJsonObject* ResponseJsonObj;
+
+ /** Verb for making request (GET,POST,etc) */
+ ERequestVerb RequestVerb;
+
+ /** Content type to be applied for request */
+ ERequestContentType RequestContentType;
+
+ /** Mapping of header section to values. Used to generate final header string for request */
+ TMap RequestHeaders;
+
+ /** Cached key/value header pairs. Parsed once request completes */
+ TMap ResponseHeaders;
+
+ /** Http Response code */
+ int32 ResponseCode;
+
+ /** Custom verb that will be used with RequestContentType == CUSTOM */
+ FString CustomVerb;
+
+ /** Request we're currently processing */
+ TSharedRef HttpRequest = FHttpModule::Get().CreateRequest();
+
+};
diff --git a/Source/SIOJson/Public/SIOJTypes.h b/Source/SIOJson/Public/SIOJTypes.h
new file mode 100644
index 00000000..0ba037e2
--- /dev/null
+++ b/Source/SIOJson/Public/SIOJTypes.h
@@ -0,0 +1,41 @@
+// Copyright 2016 Vladimir Alyamkin. All Rights Reserved.
+
+#pragma once
+
+/** Verb (GET, PUT, POST) used by the request */
+UENUM(BlueprintType)
+enum class ERequestVerb : uint8
+{
+ GET,
+ POST,
+ PUT,
+ DEL UMETA(DisplayName = "DELETE"),
+ /** Set CUSTOM verb by SetCustomVerb() function */
+ CUSTOM
+};
+
+/** Content type (json, urlencoded, etc.) used by the request */
+UENUM(BlueprintType)
+enum class ERequestContentType : uint8
+{
+ x_www_form_urlencoded_url UMETA(DisplayName = "x-www-form-urlencoded (URL)"),
+ x_www_form_urlencoded_body UMETA(DisplayName = "x-www-form-urlencoded (Request Body)"),
+ json,
+ binary
+};
+
+/** Enumerates the current state of an Http request */
+UENUM(BlueprintType)
+enum class ERequestStatus : uint8
+{
+ /** Has not been started via ProcessRequest() */
+ NotStarted,
+ /** Currently being ticked and processed */
+ Processing,
+ /** Finished but failed */
+ Failed,
+ /** Failed because it was unable to connect (safe to retry) */
+ Failed_ConnectionError,
+ /** Finished and was successful */
+ Succeeded
+};
diff --git a/Source/SIOJson/Public/SIOJsonObject.h b/Source/SIOJson/Public/SIOJsonObject.h
new file mode 100644
index 00000000..2f76f0a7
--- /dev/null
+++ b/Source/SIOJson/Public/SIOJsonObject.h
@@ -0,0 +1,176 @@
+// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
+// Copyright 2014 Vladimir Alyamkin. All Rights Reserved.
+
+#pragma once
+
+#include "SIOJsonObject.generated.h"
+
+class USIOJsonValue;
+
+/**
+ * Blueprintable FJsonObject wrapper
+ */
+UCLASS(BlueprintType, Blueprintable)
+class SIOJSON_API USIOJsonObject : public UObject
+{
+ GENERATED_UCLASS_BODY()
+
+ /** Create new Json object, cannot be pure */
+ UFUNCTION(BlueprintCallable , meta = (DisplayName = "Construct Json Object", HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject"), Category = "SIOJ|Json")
+ static USIOJsonObject* ConstructJsonObject(UObject* WorldContextObject);
+
+ /** Reset all internal data */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Json")
+ void Reset();
+
+ /** Get the root Json object */
+ TSharedPtr& GetRootObject();
+
+ /** Set the root Json object */
+ void SetRootObject(TSharedPtr& JsonObject);
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // Serialization
+
+ /** Serialize Json to string (formatted with line breaks) */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Json")
+ FString EncodeJson() const;
+
+ /** Serialize Json to string (single string without line breaks) */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Json")
+ FString EncodeJsonToSingleString() const;
+
+ /** Construct Json object from string */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Json")
+ bool DecodeJson(const FString& JsonString);
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // FJsonObject API
+
+ /** Returns a list of field names that exist in the object */
+ UFUNCTION(BlueprintPure, Category = "SIOJ|Json")
+ TArray GetFieldNames();
+
+ /** Checks to see if the FieldName exists in the object */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Json")
+ bool HasField(const FString& FieldName) const;
+
+ /** Remove field named FieldName */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Json")
+ void RemoveField(const FString& FieldName);
+
+ /** Get the field named FieldName as a JsonValue */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Json")
+ USIOJsonValue* GetField(const FString& FieldName) const;
+
+ /** Add a field named FieldName with a Value */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Json")
+ void SetField(const FString& FieldName, USIOJsonValue* JsonValue);
+
+ /** Get the field named FieldName as a Json Array */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Json")
+ TArray GetArrayField(const FString& FieldName);
+
+ /** Set an ObjectField named FieldName and value of Json Array */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Json")
+ void SetArrayField(const FString& FieldName, const TArray& InArray);
+
+ /** Adds all of the fields from one json object to this one */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Json")
+ void MergeJsonObject(USIOJsonObject* InJsonObject, bool Overwrite);
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // FJsonObject API Helpers (easy to use with simple Json objects)
+
+ /** Get the field named FieldName as a number. Ensures that the field is present and is of type Json number.
+ * Attn.!! float used instead of double to make the function blueprintable! */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Json")
+ float GetNumberField(const FString& FieldName) const;
+
+ /** Add a field named FieldName with Number as value
+ * Attn.!! float used instead of double to make the function blueprintable! */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Json")
+ void SetNumberField(const FString& FieldName, float Number);
+
+ /** Get the field named FieldName as a string. */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Json")
+ FString GetStringField(const FString& FieldName) const;
+
+ /** Add a field named FieldName with value of StringValue */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Json")
+ void SetStringField(const FString& FieldName, const FString& StringValue);
+
+ /** Get the field named FieldName as a boolean. */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Json")
+ bool GetBoolField(const FString& FieldName) const;
+
+ /** Set a boolean field named FieldName and value of InValue */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Json")
+ void SetBoolField(const FString& FieldName, bool InValue);
+
+ /** Get the field named FieldName as a Json object. */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Json")
+ USIOJsonObject* GetObjectField(const FString& FieldName) const;
+
+ /** Set an ObjectField named FieldName and value of JsonObject */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Json")
+ void SetObjectField(const FString& FieldName, USIOJsonObject* JsonObject);
+
+ /** Get the field named FieldName as a binary buffer array. */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Json")
+ TArray GetBinaryField(const FString& FieldName) const;
+
+ /** Set an BinaryField named FieldName and binary buffer array */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Json")
+ void SetBinaryField(const FString& FieldName, const TArray& Bytes);
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // Array fields helpers (uniform arrays)
+
+ /** Get the field named FieldName as a Number Array. Use it only if you're sure that array is uniform!
+ * Attn.!! float used instead of double to make the function blueprintable! */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Json")
+ TArray GetNumberArrayField(const FString& FieldName);
+
+ /** Set an ObjectField named FieldName and value of Number Array
+ * Attn.!! float used instead of double to make the function blueprintable! */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Json")
+ void SetNumberArrayField(const FString& FieldName, const TArray& NumberArray);
+
+ /** Get the field named FieldName as a String Array. Use it only if you're sure that array is uniform! */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Json")
+ TArray GetStringArrayField(const FString& FieldName);
+
+ /** Set an ObjectField named FieldName and value of String Array */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Json")
+ void SetStringArrayField(const FString& FieldName, const TArray& StringArray);
+
+ /** Get the field named FieldName as a Bool Array. Use it only if you're sure that array is uniform! */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Json")
+ TArray GetBoolArrayField(const FString& FieldName);
+
+ /** Set an ObjectField named FieldName and value of Bool Array */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Json")
+ void SetBoolArrayField(const FString& FieldName, const TArray& BoolArray);
+
+ /** Get the field named FieldName as an Object Array. Use it only if you're sure that array is uniform! */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Json")
+ TArray GetObjectArrayField(const FString& FieldName);
+
+ /** Set an ObjectField named FieldName and value of Ob Array */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Json")
+ void SetObjectArrayField(const FString& FieldName, const TArray& ObjectArray);
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // Data
+
+private:
+ /** Internal JSON data */
+ TSharedPtr JsonObj;
+
+};
diff --git a/Source/SIOJson/Public/SIOJsonValue.h b/Source/SIOJson/Public/SIOJsonValue.h
new file mode 100644
index 00000000..fc086b33
--- /dev/null
+++ b/Source/SIOJson/Public/SIOJsonValue.h
@@ -0,0 +1,168 @@
+// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
+// Copyright 2014 Vladimir Alyamkin. All Rights Reserved.
+
+#pragma once
+
+#include "SIOJsonValue.generated.h"
+
+class USIOJsonObject;
+
+/**
+ * Represents all the types a Json Value can be.
+ */
+UENUM(BlueprintType)
+namespace ESIOJson
+{
+ enum Type
+ {
+ None,
+ Null,
+ String,
+ Number,
+ Boolean,
+ Array,
+ Object,
+ Binary
+ };
+}
+
+class SIOJSON_API FJsonValueBinary : public FJsonValue
+{
+public:
+ FJsonValueBinary(const TArray& InBinary) : Value(InBinary) { Type = EJson::String; } //pretends to be none
+
+ virtual bool TryGetString(FString& OutString) const override
+ {
+ OutString = FString::FromHexBlob(Value.GetData(), Value.Num()); //encode the binary into the string directly
+ return true;
+ }
+ virtual bool TryGetNumber(double& OutDouble) const override
+ {
+ OutDouble = Value.Num();
+ return true;
+ }
+
+ //hackery: we use this as an indicator we have a binary (strings don't normally do this)
+ virtual bool TryGetBool(bool& OutBool) const override { return false; }
+
+ /** Return our binary data from this value */
+ TArray AsBinary() { return Value; }
+
+ /** Convenience method to determine if passed FJsonValue is a FJsonValueBinary or not. */
+ static bool IsBinary(const TSharedPtr& InJsonValue);
+
+ /** Convenience method to get binary array from unknown JsonValue, test with IsBinary first. */
+ static TArray AsBinary(const TSharedPtr& InJsonValue);
+
+protected:
+ TArray Value;
+
+ virtual FString GetType() const override { return TEXT("Binary"); }
+};
+
+/**
+ * Blueprintable FJsonValue wrapper
+ */
+UCLASS(BlueprintType, Blueprintable)
+class SIOJSON_API USIOJsonValue : public UObject
+{
+ GENERATED_UCLASS_BODY()
+
+ /** Create new Json Number value
+ * Attn.!! float used instead of double to make the function blueprintable! */
+ UFUNCTION(BlueprintPure, meta = (DisplayName = "Construct Json Number Value", HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject"), Category = "SIOJ|Json")
+ static USIOJsonValue* ConstructJsonValueNumber(UObject* WorldContextObject, float Number);
+
+ /** Create new Json String value */
+ UFUNCTION(BlueprintPure, meta = (DisplayName = "Construct Json String Value", HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject"), Category = "SIOJ|Json")
+ static USIOJsonValue* ConstructJsonValueString(UObject* WorldContextObject, const FString& StringValue);
+
+ /** Create new Json Bool value */
+ UFUNCTION(BlueprintPure, meta = (DisplayName = "Construct Json Bool Value", HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject"), Category = "SIOJ|Json")
+ static USIOJsonValue* ConstructJsonValueBool(UObject* WorldContextObject, bool InValue);
+
+ /** Create new Json Array value */
+ UFUNCTION(BlueprintPure, meta = (DisplayName = "Construct Json Array Value", HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject"), Category = "SIOJ|Json")
+ static USIOJsonValue* ConstructJsonValueArray(UObject* WorldContextObject, const TArray& InArray);
+
+ /** Create new Json Object value */
+ UFUNCTION(BlueprintPure, meta = (DisplayName = "Construct Json Object Value", HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject"), Category = "SIOJ|Json")
+ static USIOJsonValue* ConstructJsonValueObject(USIOJsonObject *JsonObject, UObject* WorldContextObject);
+
+ /** Create new Json Binary value */
+ UFUNCTION(BlueprintPure, meta = (DisplayName = "Construct Json Binary Value", HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject"), Category = "SIOJ|Json")
+ static USIOJsonValue* ConstructJsonValueBinary(UObject* WorldContextObject, TArray ByteArray);
+
+ /** Create new Json value from FJsonValue (to be used from USIOJsonObject) */
+ static USIOJsonValue* ConstructJsonValue(UObject* WorldContextObject, const TSharedPtr& InValue);
+
+ /** Create new Json value from JSON encoded string*/
+ UFUNCTION(BlueprintPure, meta = (DisplayName = "Value From Json String", HidePin = "WorldContextObject", DefaultToSelf = "WorldContextObject"), Category = "SIOJ|Json")
+ static USIOJsonValue* ValueFromJsonString(UObject* WorldContextObject, const FString& StringValue);
+
+ /** Get the root Json value */
+ TSharedPtr& GetRootValue();
+
+ /** Set the root Json value */
+ void SetRootValue(TSharedPtr& JsonValue);
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // FJsonValue API
+
+ /** Get type of Json value (Enum) */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Json")
+ ESIOJson::Type GetType() const;
+
+ /** Get type of Json value (String) */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Json")
+ FString GetTypeString() const;
+
+ /** Returns true if this value is a 'null' */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Json")
+ bool IsNull() const;
+
+ /** Returns this value as a double, throwing an error if this is not an Json Number
+ * Attn.!! float used instead of double to make the function blueprintable! */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Json")
+ float AsNumber() const;
+
+ /** Returns this value as a number, throwing an error if this is not an Json String */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Json")
+ FString AsString() const;
+
+ /** Returns this value as a boolean, throwing an error if this is not an Json Bool */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Json")
+ bool AsBool() const;
+
+ /** Returns this value as an array, throwing an error if this is not an Json Array */
+ UFUNCTION(BlueprintCallable, Category = "SIOJ|Json")
+ TArray