diff --git a/README.md b/README.md index 9b5f505b..f9bb5190 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Key features: Check the [Wiki](https://hiazma.atlassian.net/wiki/display/VAR) for plugin usage examples and installation notes. -Current version: **1.1 R 18** (UE 4.16-4.17) +Current version: **1.1 R 20** (UE 4.18) ![SCREENSHOT](SCREENSHOT.jpg) @@ -23,5 +23,5 @@ Legal info Unreal® is a trademark or registered trademark of Epic Games, Inc. in the United States of America and elsewhere. -Unreal® Engine, Copyright 1998 – 2017, Epic Games, Inc. All rights reserved. +Unreal® Engine, Copyright 1998 – 2018, Epic Games, Inc. All rights reserved. diff --git a/Source/VaRestEditorPlugin/Private/VaRestEditorPlugin.cpp b/Source/VaRestEditorPlugin/Private/VaRestEditorPlugin.cpp index 04525c2f..f274a924 100644 --- a/Source/VaRestEditorPlugin/Private/VaRestEditorPlugin.cpp +++ b/Source/VaRestEditorPlugin/Private/VaRestEditorPlugin.cpp @@ -1,7 +1,6 @@ // Copyright 2015 Vladimir Alyamkin. All Rights Reserved. // Original code by https://github.com/unktomi -#include "VaRestEditorPluginPrivatePCH.h" #include "VaRestEditorPlugin.h" #define LOCTEXT_NAMESPACE "FVaRestEditorPluginModule" diff --git a/Source/VaRestEditorPlugin/Private/VaRestEditorPluginPrivatePCH.h b/Source/VaRestEditorPlugin/Private/VaRestEditorPluginPrivatePCH.h deleted file mode 100644 index a76f3887..00000000 --- a/Source/VaRestEditorPlugin/Private/VaRestEditorPluginPrivatePCH.h +++ /dev/null @@ -1,16 +0,0 @@ -// 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/VaRestEditorPlugin/Private/VaRest_BreakJson.cpp b/Source/VaRestEditorPlugin/Private/VaRest_BreakJson.cpp index 76d8024f..8997ba7c 100644 --- a/Source/VaRestEditorPlugin/Private/VaRest_BreakJson.cpp +++ b/Source/VaRestEditorPlugin/Private/VaRest_BreakJson.cpp @@ -1,11 +1,17 @@ // Copyright 2015 Vladimir Alyamkin. All Rights Reserved. // Original code by https://github.com/unktomi -#include "VaRestEditorPluginPrivatePCH.h" #include "VaRest_BreakJson.h" -#include "Runtime/Launch/Resources/Version.h" +#include "KismetCompiler.h" +#include "EditorCategoryUtils.h" #include "EdGraphUtilities.h" +#include "EdGraph/EdGraph.h" +#include "EdGraph/EdGraphNodeUtils.h" // for FNodeTextCache +#include "BlueprintNodeSpawner.h" +#include "BlueprintActionDatabaseRegistrar.h" + +#include "Runtime/Launch/Resources/Version.h" #define LOCTEXT_NAMESPACE "VaRest_BreakJson" diff --git a/Source/VaRestEditorPlugin/VaRestEditorPlugin.Build.cs b/Source/VaRestEditorPlugin/VaRestEditorPlugin.Build.cs index f4280a49..86b57861 100644 --- a/Source/VaRestEditorPlugin/VaRestEditorPlugin.Build.cs +++ b/Source/VaRestEditorPlugin/VaRestEditorPlugin.Build.cs @@ -6,8 +6,9 @@ public class VaRestEditorPlugin : ModuleRules { public VaRestEditorPlugin(ReadOnlyTargetRules Target) : base(Target) { - - PublicIncludePaths.AddRange( + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( new string[] { "VaRestPlugin", "VaRestPlugin/Public" diff --git a/Source/VaRestPlugin/Classes/VaRestJsonObject.h b/Source/VaRestPlugin/Classes/VaRestJsonObject.h index 4d417f87..3d367d6c 100644 --- a/Source/VaRestPlugin/Classes/VaRestJsonObject.h +++ b/Source/VaRestPlugin/Classes/VaRestJsonObject.h @@ -6,6 +6,7 @@ #include "VaRestJsonObject.generated.h" class UVaRestJsonValue; +class FJsonObject; /** * Blueprintable FJsonObject wrapper @@ -166,6 +167,25 @@ class VARESTPLUGIN_API UVaRestJsonObject : public UObject void SetObjectArrayField(const FString& FieldName, const TArray& ObjectArray); + ////////////////////////////////////////////////////////////////////////// + // Deserialize + +public: + /** Deserialize byte content to json */ + int32 DeserializeFromUTF8Bytes(const ANSICHAR* Bytes, int32 Size); + + /** Deserialize byte content to json */ + int32 DeserializeFromTCHARBytes(const TCHAR* Bytes, int32 Size); + + /** Deserialize byte stream from reader */ + void DecodeFromArchive(TUniquePtr& Reader); + + /** Save json to file */ + bool WriteToFile(const FString& Path); + + static bool WriteStringToArchive(FArchive& Ar, const TCHAR* StrPtr, int64 Len); + + ////////////////////////////////////////////////////////////////////////// // Data diff --git a/Source/VaRestPlugin/Classes/VaRestJsonValue.h b/Source/VaRestPlugin/Classes/VaRestJsonValue.h index f13e4564..4b7ad64f 100644 --- a/Source/VaRestPlugin/Classes/VaRestJsonValue.h +++ b/Source/VaRestPlugin/Classes/VaRestJsonValue.h @@ -6,6 +6,7 @@ #include "VaRestJsonValue.generated.h" class UVaRestJsonObject; +class FJsonValue; /** * Represents all the types a Json Value can be. diff --git a/Source/VaRestPlugin/Classes/VaRestLibrary.h b/Source/VaRestPlugin/Classes/VaRestLibrary.h index 304900bd..86722fd9 100644 --- a/Source/VaRestPlugin/Classes/VaRestLibrary.h +++ b/Source/VaRestPlugin/Classes/VaRestLibrary.h @@ -93,6 +93,18 @@ class UVaRestLibrary : public UBlueprintFunctionLibrary static bool Base64DecodeData(const FString& Source, TArray& Dest); + ////////////////////////////////////////////////////////////////////////// + // File system integration + +public: + /** + * Load JSON from formatted text file + * @param Path File name relative to the Content folder + */ + UFUNCTION(BlueprintCallable, Category = "VaRest|Utility", meta = (WorldContext = "WorldContextObject")) + static class UVaRestJsonObject* LoadJsonFromFile(UObject* WorldContextObject, const FString& Path); + + ////////////////////////////////////////////////////////////////////////// // Easy URL processing diff --git a/Source/VaRestPlugin/Classes/VaRestRequestJSON.h b/Source/VaRestPlugin/Classes/VaRestRequestJSON.h index 78cc11b0..63527e45 100644 --- a/Source/VaRestPlugin/Classes/VaRestRequestJSON.h +++ b/Source/VaRestPlugin/Classes/VaRestRequestJSON.h @@ -3,6 +3,7 @@ #pragma once #include "Engine/LatentActionManager.h" +#include "LatentActions.h" #include "Http.h" #include "VaRestTypes.h" @@ -210,6 +211,7 @@ class VARESTPLUGIN_API UVaRestRequestJSON : public UObject /** Apply current internal setup to request and process it */ void ProcessRequest(); + ////////////////////////////////////////////////////////////////////////// // Request callbacks @@ -262,13 +264,29 @@ class VARESTPLUGIN_API UVaRestRequestJSON : public UObject // Data public: - /** Request response stored as a string */ + /** + * Get request response stored as a string + * @param bCacheResponseContent - Set true if you plan to use it few times to prevent deserialization each time + */ + UFUNCTION(BlueprintCallable, Category = "VaRest|Response") + FString GetResponseContentAsString(bool bCacheResponseContent = true); + +public: + /** Response size */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "VaRest|Response") + int32 ResponseSize; + + /** DEPRECATED: Please use GetResponseContentAsString() instead */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "VaRest|Response") FString ResponseContent; /** Is the response valid JSON? */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "VaRest|Response") bool bIsValidJsonResponse; + +protected: + /** Default value for deprecated ResponseContent variable */ + static FString DeprecatedResponseString; protected: /** Latent action helper */ @@ -309,5 +327,9 @@ class VARESTPLUGIN_API UVaRestRequestJSON : public UObject /** Request we're currently processing */ TSharedRef HttpRequest = FHttpModule::Get().CreateRequest(); - + +public: + /** Returns reference to internal request object */ + TSharedRef GetHttpRequest() const { return HttpRequest; }; + }; diff --git a/Source/VaRestPlugin/Private/VaRestJsonObject.cpp b/Source/VaRestPlugin/Private/VaRestJsonObject.cpp index c8b99a04..7d06c1be 100644 --- a/Source/VaRestPlugin/Private/VaRestJsonObject.cpp +++ b/Source/VaRestPlugin/Private/VaRestJsonObject.cpp @@ -1,5 +1,8 @@ // Copyright 2014 Vladimir Alyamkin. All Rights Reserved. +#include "VaRestJsonObject.h" +#include "VaRestJsonParser.h" +#include "VaRestJsonValue.h" #include "VaRestPluginPrivatePCH.h" typedef TJsonWriterFactory< TCHAR, TCondensedJsonPrintPolicy > FCondensedJsonStringWriterFactory; @@ -33,7 +36,15 @@ TSharedPtr& UVaRestJsonObject::GetRootObject() void UVaRestJsonObject::SetRootObject(TSharedPtr& JsonObject) { - JsonObj = JsonObject; + if (JsonObject.IsValid()) + { + JsonObj = JsonObject; + } + else + { + UE_LOG(LogVaRest, Error, TEXT("%s: Trying to set invalid json object as root one. Reset now."), *VA_FUNC_LINE); + Reset(); + } } @@ -69,8 +80,9 @@ FString UVaRestJsonObject::EncodeJsonToSingleString() const bool UVaRestJsonObject::DecodeJson(const FString& JsonString) { - TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create(*JsonString); - if (FJsonSerializer::Deserialize(Reader, JsonObj) && JsonObj.IsValid()) + DeserializeFromTCHARBytes(JsonString.GetCharArray().GetData(), JsonString.Len()); + + if (JsonObj.IsValid()) { return true; } @@ -240,14 +252,15 @@ void UVaRestJsonObject::SetBoolField(const FString& FieldName, bool InValue) TArray UVaRestJsonObject::GetArrayField(const FString& FieldName) const { - if (!JsonObj->HasTypedField(FieldName)) + TArray OutArray; + if (!JsonObj.IsValid() || FieldName.IsEmpty()) { - UE_LOG(LogVaRest, Warning, TEXT("No field with name %s of type Array"), *FieldName); + return OutArray; } - TArray OutArray; - if (!JsonObj.IsValid() || FieldName.IsEmpty()) + if (!JsonObj->HasTypedField(FieldName)) { + UE_LOG(LogVaRest, Warning, TEXT("%s: No field with name %s of type Array"), *VA_FUNC_LINE, *FieldName); return OutArray; } @@ -316,6 +329,11 @@ void UVaRestJsonObject::SetArrayField(const FString& FieldName, const TArrayIsValidLowLevel()) + { + return; + } + TArray Keys = InJsonObject->GetFieldNames(); for (auto Key : Keys) @@ -333,7 +351,7 @@ UVaRestJsonObject* UVaRestJsonObject::GetObjectField(const FString& FieldName) c { if (!JsonObj.IsValid() || !JsonObj->HasTypedField(FieldName)) { - UE_LOG(LogVaRest, Warning, TEXT("No field with name %s of type Object"), *FieldName); + UE_LOG(LogVaRest, Warning, TEXT("%s: No field with name %s of type Object"), *VA_FUNC_LINE, *FieldName); return nullptr; } @@ -347,7 +365,7 @@ UVaRestJsonObject* UVaRestJsonObject::GetObjectField(const FString& FieldName) c void UVaRestJsonObject::SetObjectField(const FString& FieldName, UVaRestJsonObject* JsonObject) { - if (!JsonObj.IsValid() || FieldName.IsEmpty()) + if (!JsonObj.IsValid() || FieldName.IsEmpty() || !JsonObject || !JsonObject->IsValidLowLevel()) { return; } @@ -361,14 +379,10 @@ void UVaRestJsonObject::SetObjectField(const FString& FieldName, UVaRestJsonObje TArray UVaRestJsonObject::GetNumberArrayField(const FString& FieldName) const { - if (!JsonObj->HasTypedField(FieldName)) - { - UE_LOG(LogVaRest, Warning, TEXT("No field with name %s of type Array"), *FieldName); - } - TArray NumberArray; - if (!JsonObj.IsValid() || FieldName.IsEmpty()) + if (!JsonObj.IsValid() || !JsonObj->HasTypedField(FieldName) || FieldName.IsEmpty()) { + UE_LOG(LogVaRest, Warning, TEXT("%s: No field with name %s of type Array"), *VA_FUNC_LINE, *FieldName); return NumberArray; } @@ -406,14 +420,10 @@ void UVaRestJsonObject::SetNumberArrayField(const FString& FieldName, const TArr TArray UVaRestJsonObject::GetStringArrayField(const FString& FieldName) const { - if (!JsonObj->HasTypedField(FieldName)) - { - UE_LOG(LogVaRest, Warning, TEXT("No field with name %s of type Array"), *FieldName); - } - TArray StringArray; - if (!JsonObj.IsValid() || FieldName.IsEmpty()) + if (!JsonObj.IsValid() || !JsonObj->HasTypedField(FieldName) || FieldName.IsEmpty()) { + UE_LOG(LogVaRest, Warning, TEXT("%s: No field with name %s of type Array"), *VA_FUNC_LINE, *FieldName); return StringArray; } @@ -450,14 +460,10 @@ void UVaRestJsonObject::SetStringArrayField(const FString& FieldName, const TArr TArray UVaRestJsonObject::GetBoolArrayField(const FString& FieldName) const { - if (!JsonObj->HasTypedField(FieldName)) - { - UE_LOG(LogVaRest, Warning, TEXT("No field with name %s of type Array"), *FieldName); - } - TArray BoolArray; - if (!JsonObj.IsValid() || FieldName.IsEmpty()) + if (!JsonObj.IsValid() || !JsonObj->HasTypedField(FieldName) || FieldName.IsEmpty()) { + UE_LOG(LogVaRest, Warning, TEXT("%s: No field with name %s of type Array"), *VA_FUNC_LINE, *FieldName); return BoolArray; } @@ -494,14 +500,10 @@ void UVaRestJsonObject::SetBoolArrayField(const FString& FieldName, const TArray TArray UVaRestJsonObject::GetObjectArrayField(const FString& FieldName) const { - if (!JsonObj->HasTypedField(FieldName)) - { - UE_LOG(LogVaRest, Warning, TEXT("No field with name %s of type Array"), *FieldName); - } - TArray OutArray; - if (!JsonObj.IsValid() || FieldName.IsEmpty()) + if (!JsonObj.IsValid() || !JsonObj->HasTypedField(FieldName) || FieldName.IsEmpty()) { + UE_LOG(LogVaRest, Warning, TEXT("%s: No field with name %s of type Array"), *VA_FUNC_LINE, *FieldName); return OutArray; } @@ -539,3 +541,176 @@ void UVaRestJsonObject::SetObjectArrayField(const FString& FieldName, const TArr JsonObj->SetArrayField(FieldName, EntriesArray); } + +////////////////////////////////////////////////////////////////////////// +// Deserialize + +int32 UVaRestJsonObject::DeserializeFromUTF8Bytes(const ANSICHAR* Bytes, int32 Size) +{ + FJSONReader Reader; + + const ANSICHAR* EndByte = Bytes + Size; + while (Bytes < EndByte) + { + TCHAR Char = FUTF8ToTCHAR_Convert::utf8codepoint(&Bytes); + + if (Char > 0xFFFF) + { + Char = UNICODE_BOGUS_CHAR_CODEPOINT; + } + + if (!Reader.Read(Char)) + { + break; + } + } + + SetRootObject(Reader.State.Root); + return Reader.State.Size; +} + +int32 UVaRestJsonObject::DeserializeFromTCHARBytes(const TCHAR* Bytes, int32 Size) +{ + FJSONReader Reader; + + int32 i = 0; + while (i < Size) + { + if (!Reader.Read(Bytes[i++])) + { + break; + } + } + + SetRootObject(Reader.State.Root); + return Reader.State.Size; +} + +void UVaRestJsonObject::DecodeFromArchive(TUniquePtr& Reader) +{ + FArchive& Ar = (*Reader.Get()); + uint8 SymbolBytes[2]; + + // Read first two bytes + Ar << SymbolBytes[0]; + Ar << SymbolBytes[1]; + + bool bIsIntelByteOrder = true; + + if(SymbolBytes[0] == 0xff && SymbolBytes[1] == 0xfe) + { + // Unicode Intel byte order. Less 1 for the FFFE header, additional 1 for null terminator. + bIsIntelByteOrder = true; + } + else if(SymbolBytes[0] == 0xfe && SymbolBytes[1] == 0xff) + { + // Unicode non-Intel byte order. Less 1 for the FFFE header, additional 1 for null terminator. + bIsIntelByteOrder = false; + } + + FJSONReader JsonReader; + TCHAR Char; + + while (!Ar.AtEnd()) + { + Ar << SymbolBytes[0]; + + if (Ar.AtEnd()) + { + break; + } + + Ar << SymbolBytes[1]; + + if (bIsIntelByteOrder) + { + Char = CharCast((UCS2CHAR)((uint16)SymbolBytes[0] + (uint16)SymbolBytes[1] * 256)); + } + else + { + Char = CharCast((UCS2CHAR)((uint16)SymbolBytes[1] + (uint16)SymbolBytes[0] * 256)); + } + + if (!JsonReader.Read(Char)) + { + break; + } + } + + SetRootObject(JsonReader.State.Root); + + if (!Ar.Close()) + { + UE_LOG(LogVaRest, Error, TEXT("UVaRestJsonObject::DecodeFromArchive: Error! Can't close file!")); + } + +} + +bool UVaRestJsonObject::WriteToFile(const FString& Path) +{ + if (JsonObj.IsValid()) + { + TUniquePtr FileWriter(IFileManager::Get().CreateFileWriter(*Path)); + if (!FileWriter) + { + return false; + } + + FArchive& Ar = *FileWriter.Get(); + + UCS2CHAR BOM = UNICODE_BOM; + Ar.Serialize( &BOM, sizeof(UCS2CHAR) ); + + FString Str = FString(TEXT("{")); + WriteStringToArchive(Ar, *Str, Str.Len()); + + int32 ElementCount = 0; + FJSONWriter JsonWriter; + for (auto JsonObjectValuePair : JsonObj->Values) + { + Str = FString(TEXT("\"")); + WriteStringToArchive(Ar, *Str, Str.Len()); + + const TCHAR* BufferPtr = *JsonObjectValuePair.Key; + for (int i = 0; i < JsonObjectValuePair.Key.Len(); ++i) + { + Str = FString(1, &BufferPtr[i]); +#if PLATFORM_WINDOWS + WriteStringToArchive(Ar, *Str, Str.Len() - 1); +#else + WriteStringToArchive(Ar, *Str, Str.Len()); +#endif + } + + Str = FString(TEXT("\"")); + WriteStringToArchive(Ar, *Str, Str.Len()); + + Str = FString(TEXT(":")); + WriteStringToArchive(Ar, *Str, Str.Len()); + + ++ElementCount; + + JsonWriter.Write(JsonObjectValuePair.Value, FileWriter.Get(), ElementCount >= JsonObj->Values.Num()); + } + + Str = FString(TEXT("}")); + WriteStringToArchive(Ar, *Str, Str.Len()); + + FileWriter->Close(); + + return true; + } + else + { + UE_LOG(LogVaRest, Error, TEXT("UVaRestJsonObject::WriteToFile: Root object is invalid!")); + return false; + } +} + +bool UVaRestJsonObject::WriteStringToArchive( FArchive& Ar, const TCHAR* StrPtr, int64 Len) +{ + auto Src = StringCast(StrPtr, Len); + Ar.Serialize( (UCS2CHAR*)Src.Get(), Src.Length() * sizeof(UCS2CHAR) ); + + return true; +} diff --git a/Source/VaRestPlugin/Private/VaRestJsonParser.cpp b/Source/VaRestPlugin/Private/VaRestJsonParser.cpp new file mode 100644 index 00000000..ea669cae --- /dev/null +++ b/Source/VaRestPlugin/Private/VaRestJsonParser.cpp @@ -0,0 +1,933 @@ +// Copyright 2015-2017 Mail.Ru Group. All Rights Reserved. + +#include "VaRestJsonParser.h" +#include "VaRestJsonObject.h" +#include "Dom/JsonObject.h" +#include "Dom/JsonValue.h" +#include "LogMacros.h" + +FJSONState::FJSONState() +: Notation(EJSONNotation::NONE) +, bEscape(false) +, bError(false) +, Quote(UNICODE_BOGUS_CHAR_CODEPOINT) +{ + Key.Reserve(1024); + Data.Reserve(4096); + + Root = TSharedPtr(nullptr); + + Objects.Reserve(64); + Tokens.Reserve(64); + + Tokens.Add(EJSONToken::ROOT); + + Size = 0; +} + +EJSONToken FJSONState::GetToken(int32 Index) +{ + return Tokens.Num() > Index ? Tokens.Last(Index) : EJSONToken::ERROR; +} + +bool FJSONState::CheckTokens(EJSONToken T1) +{ + return T1 == GetToken(0); +} + +bool FJSONState::CheckTokens(EJSONToken T1, EJSONToken T2) +{ + return T1 == GetToken(1) && T2 == GetToken(0); +} + +bool FJSONState::CheckTokens(EJSONToken T1, EJSONToken T2, EJSONToken T3) +{ + return T1 == GetToken(2) && T2 == GetToken(1) && T3 == GetToken(0); +} + +void FJSONState::PopToken(int32 Num) +{ + if (Num > 0) + { + if (Tokens.Num() >= Num) + { + Tokens.RemoveAt(Tokens.Num() - Num, Num, false); + } + else + { + bError = true; + } + } + + Notation = EJSONNotation::NONE; +} + +void FJSONState::PopObject() +{ + if (Objects.Num() > 0) + { + auto Object = Objects.Pop(false); + if (Object->Type == EJson::Object) + { + return; + } + } + + bError = true; +} + +void FJSONState::PopArray() +{ + if (Objects.Num() > 0) + { + auto Object = Objects.Pop(false); + if (Object->Type == EJson::Array) + { + return; + } + } + + bError = true; +} + +void FJSONState::PopValue(bool bCheckType) +{ + if (Objects.Num() > 0) + { + auto Value = Objects.Last(0); + if (Value->Type == EJson::Object || Value->Type == EJson::Array) + { + if (bCheckType) + { + bError = true; + } + } + else + { + Objects.Pop(false); + if (Objects.Num() > 0) + { + switch(Value->Type) + { + case EJson::Null: + { + auto LowerCase = Data.ToLower(); + if (LowerCase != TEXT("null")) + { + bError = true; + } + break; + } + case EJson::String: + { + FJsonValueNonConstString* JsonValueString = ((FJsonValueNonConstString*) Value.Get()); + JsonValueString->AsNonConstString() = Data; + JsonValueString->AsNonConstString().Shrink(); + Size += JsonValueString->AsNonConstString().GetAllocatedSize(); + break; + } + case EJson::Number: + { + FString LowerCase = Data.ToLower(); + int32 ePosition = INDEX_NONE; + LowerCase.FindChar('e', ePosition); + if (ePosition == INDEX_NONE) + { + if (LowerCase.IsNumeric()) + { + ((FJsonValueNonConstNumber*) Value.Get())->AsNonConstNumber() = FCString::Atod(*LowerCase); + } + else + { + bError = true; + } + } + else if (LowerCase.Len() > ePosition + 2) + { + FString Left = LowerCase.Left(ePosition); + FString Rigth = LowerCase.Right(LowerCase.Len() - ePosition - 1); + if (Left.IsNumeric() && Rigth.IsNumeric()) + { + ((FJsonValueNonConstNumber*) Value.Get())->AsNonConstNumber() = FCString::Atod(*Left) * FMath::Pow(10.f, FCString::Atoi(*Rigth)); + } + else + { + bError = true; + } + } + else + { + bError = true; + } + break; + } + case EJson::Boolean: + { + auto LowerCase = Data.ToLower(); + if (LowerCase == TEXT("true")) + { + ((FJsonValueNonConstBoolean*) Value.Get())->AsNonConstBool() = true; + } + else if (LowerCase == TEXT("false")) + { + ((FJsonValueNonConstBoolean*) Value.Get())->AsNonConstBool() = false; + } + else + { + bError = true; + } + break; + } + default: + { + bError = true; + return; + } + } + + ClearData(); + + auto Container = Objects.Last(0); + if (Container->Type == EJson::Object) + { + if (Key.Len() > 0) + { + FString KeyCopy = Key; + KeyCopy.Shrink(); + Container->AsObject()->SetField(KeyCopy, Value); + Size += KeyCopy.GetAllocatedSize(); + ClearKey(); + } + else + { + bError = true; + } + } + else if (Container->Type == EJson::Array) + { + ((FJsonValueNonConstArray*) Container.Get())->AsNonConstArray().Add(Value); + } + else + { + bError = true; + } + } + else + { + bError = true; + } + } + } + else + { + bError = true; + } +} + +FJsonValue* FJSONState::GetLast() +{ + if (Objects.Num() > 0) + { + return Objects.Last(0).Get(); + } + bError = true; + return nullptr; +} + +FJsonValueObject* FJSONState::GetObject() +{ + FJsonValue* Value = GetLast(); + if (Value != nullptr && Value->Type == EJson::Object) + { + return (FJsonValueObject*) Value; + } + bError = true; + return nullptr; +} + +FJsonValueNonConstArray* FJSONState::GetArray() +{ + FJsonValue* Value = GetLast(); + if (Value != nullptr && Value->Type == EJson::Array) + { + return (FJsonValueNonConstArray*) Value; + } + bError = true; + return nullptr; +} + +TSharedPtr FJSONState::PushObject() +{ + TSharedPtr Result(new FJsonValueObject(TSharedPtr(new FJsonObject()))); + Objects.Add(Result); + Size += sizeof(TSharedPtr) + sizeof(FJsonValueObject); + return Result; +} + +TSharedPtr FJSONState::PushObject(TSharedPtr Object) +{ + TSharedPtr Result(new FJsonValueObject(Object)); + Objects.Add(Result); + Size += sizeof(TSharedPtr) + sizeof(FJsonValueObject); + return Result; +} + +TSharedPtr FJSONState::PushArray() +{ + TArray> Empty; + TSharedPtr Result(new FJsonValueNonConstArray(Empty)); + Objects.Add(Result); + Size += sizeof(TSharedPtr) + sizeof(FJsonValueNonConstArray); + return Result; +} + +TSharedPtr FJSONState::PushBoolean() +{ + TSharedPtr Result(new FJsonValueNonConstBoolean(false)); + Objects.Add(Result); + Size += sizeof(TSharedPtr) + sizeof(FJsonValueNonConstBoolean); + return Result; +} + +TSharedPtr FJSONState::PushNull() +{ + TSharedPtr Result(new FJsonValueNull()); + Objects.Add(Result); + Size += sizeof(TSharedPtr) + sizeof(FJsonValueNull); + return Result; +} + +TSharedPtr FJSONState::PushNumber() +{ + TSharedPtr Result(new FJsonValueNonConstNumber(0.f)); + Objects.Add(Result); + Size += sizeof(TSharedPtr) + sizeof(FJsonValueNonConstNumber); + return Result; +} + +TSharedPtr FJSONState::PushString() +{ + TSharedPtr Result(new FJsonValueNonConstString(TEXT(""))); + Objects.Add(Result); + Size += sizeof(TSharedPtr) + sizeof(FJsonValueNonConstString); + return Result; +} + +void FJSONState::ClearData() +{ + Data.Empty(Data.GetCharArray().Max()); +} + +void FJSONState::ClearKey() +{ + Key.Empty(Key.GetCharArray().Max()); +} + +void FJSONState::DataToKey() +{ + ClearKey(); + Key += Data; + ClearData(); +} + +void FJSONState::Error() +{ + bError = true; +} + +FJSONReader::FJSONReader() +{ + +} + +bool FJSONReader::IsNewLine(const TCHAR& Char) +{ + return Char == '\n'; +} + +bool FJSONReader::IsSpace(const TCHAR& Char) +{ + return IsNewLine(Char) || Char == ' ' || Char == '\t' || Char == '\r'; +} + +bool FJSONReader::FindToken(const TCHAR& Char) +{ + if (State.bEscape) + { + return false; + } + + if (State.Notation != EJSONNotation::STRING) + { + switch(Char) + { + case '{': State.Tokens.Add(EJSONToken::CURLY_BEGIN); return true; + case '}': State.Tokens.Add(EJSONToken::CURLY_END); return true; + case '[': State.Tokens.Add(EJSONToken::SQUARE_BEGIN); return true; + case ']': State.Tokens.Add(EJSONToken::SQUARE_END); return true; + case ',': State.Tokens.Add(EJSONToken::COMMA); return true; + case ':': State.Tokens.Add(EJSONToken::COLON); return true; + } + } + return false; +} + +void FJSONReader::UpdateNotation() +{ + switch(State.GetToken()) + { + case EJSONToken::ROOT: + { + return; + } + case EJSONToken::CURLY_BEGIN: + { + if (State.CheckTokens(EJSONToken::SQUARE_BEGIN, EJSONToken::CURLY_BEGIN)) // Object in array "[{" + { + State.Notation = EJSONNotation::OBJECT; + auto Value = State.GetArray(); + if (Value != nullptr) + { + Value->AsNonConstArray().Add(State.PushObject()); + } + else + { + State.Error(); + } + } + else if (State.CheckTokens(EJSONToken::CURLY_BEGIN, EJSONToken::COLON, EJSONToken::CURLY_BEGIN)) // Object in key "{:{" + { + if (State.Key.Len() > 0) + { + State.Notation = EJSONNotation::OBJECT; + auto Value = State.GetObject(); + if (Value != nullptr) + { + Value->AsObject()->SetField(State.Key, State.PushObject()); + State.ClearKey(); + } + else + { + State.Error(); + } + } + else + { + State.Error(); + } + } + else if (State.CheckTokens(EJSONToken::ROOT, EJSONToken::CURLY_BEGIN)) // Root object "{" + { + if (State.Root.IsValid()) + { + State.Error(); + } + else + { + State.Root = TSharedPtr(new FJsonObject()); + State.PushObject(State.Root); // add root object + State.Notation = EJSONNotation::OBJECT; + } + } + else + { + State.Error(); + } + break; + } + case EJSONToken::CURLY_END: + { + if (State.CheckTokens(EJSONToken::CURLY_BEGIN, EJSONToken::CURLY_END)) // Close object "{}" + { + State.PopToken(2); // pop "{}" + State.PopObject(); // remove object + } + else if (State.CheckTokens(EJSONToken::CURLY_BEGIN, EJSONToken::COLON, EJSONToken::CURLY_END)) // Close object "{:}" + { + State.PopToken(3); // pop "{:}" + State.PopValue(); // remove value + State.PopObject(); // remove object + } + else + { + State.Error(); + } + + if (State.CheckTokens(EJSONToken::COLON)) // Object in object ":" + { + State.PopToken(1); // pop ":" + } + + State.Notation = EJSONNotation::SKIP; + + break; + } + case EJSONToken::SQUARE_BEGIN: + { + if (State.CheckTokens(EJSONToken::SQUARE_BEGIN, EJSONToken::SQUARE_BEGIN)) // Array in array "[[" + { + State.Notation = EJSONNotation::ARRAY; + auto Value = State.GetArray(); + if (Value != nullptr) + { + Value->AsNonConstArray().Add(State.PushArray()); + } + else + { + State.Error(); + } + } + else if (State.CheckTokens(EJSONToken::CURLY_BEGIN, EJSONToken::COLON, EJSONToken::SQUARE_BEGIN)) // Array in key "{:[" + { + State.Notation = EJSONNotation::ARRAY; + if (State.Key.Len() > 0) + { + auto Value = State.GetObject(); + if (Value != nullptr) + { + Value->AsObject()->SetField(State.Key, State.PushArray()); + State.ClearKey(); + } + else + { + State.Error(); + } + } + else + { + State.Error(); + } + } + else if (State.CheckTokens(EJSONToken::ROOT, EJSONToken::SQUARE_BEGIN)) // Root array "{" + { + State.Error(); // Not support + } + else + { + State.Error(); + } + break; + } + case EJSONToken::SQUARE_END: + { + if (State.CheckTokens(EJSONToken::SQUARE_BEGIN, EJSONToken::SQUARE_END)) // Close array "[]" + { + State.PopToken(2); // remove token "[]" + State.PopValue(false); // remove value if exists + State.PopArray(); // remove array + + if (State.CheckTokens(EJSONToken::COLON)) // Array in object ":" + { + State.PopToken(1); // pop ":" + } + } + else + { + State.Error(); + } + + State.Notation = EJSONNotation::SKIP; + + break; + } + case EJSONToken::COMMA: + { + if (State.CheckTokens(EJSONToken::CURLY_BEGIN, EJSONToken::COLON, EJSONToken::COMMA)) // Next record in object "{:," + { + State.PopToken(2); // remove token ":," + State.PopValue(false); // remove value + State.Notation = EJSONNotation::OBJECT; + } + else if (State.CheckTokens(EJSONToken::CURLY_BEGIN, EJSONToken::COMMA)) // Next record in object "{," + { + State.PopToken(1); // remove token "," + State.Notation = EJSONNotation::OBJECT; + } + else if (State.CheckTokens(EJSONToken::SQUARE_BEGIN, EJSONToken::COMMA)) // Next record in array "[," + { + State.PopToken(1); // remove token "," + State.PopValue(false); // remove value + State.Notation = EJSONNotation::ARRAY; + } + else + { + State.Error(); + } + break; + } + case EJSONToken::COLON: + { + if (State.CheckTokens(EJSONToken::CURLY_BEGIN, EJSONToken::COLON)) // Object key close "{:" + { + State.Notation = EJSONNotation::OBJECT; + if (State.Data.Len() > 0) + { + State.DataToKey(); + } + else + { + State.Error(); + } + } + else + { + State.Error(); + } + break; + } + case EJSONToken::ERROR: + { + State.Error(); + break; + } + } + + if (!State.bError && State.Notation == EJSONNotation::NONE) + { + UpdateNotation(); + } +} + +void FJSONReader::ReadAsString(const TCHAR& Char) +{ + if (IsNewLine(Char)) + { + State.Error(); + return; + } + + if (!State.bEscape && State.Quote == Char) + { + State.Quote = UNICODE_BOGUS_CHAR_CODEPOINT; + State.Notation = EJSONNotation::SKIP; + } + else + { + if (State.bEscape) + { + switch(Char) + { + case 'n': State.Data.AppendChar('\n'); break; + case 't': State.Data.AppendChar('\t'); break; + default: State.Data.AppendChar(Char); break; + } + } + else + { + State.Data.AppendChar(Char); + } + } +} + +void FJSONReader::ReadAsStringSpecial(const TCHAR& Char) +{ + if (IsSpace(Char) && State.Data.Len() > 0) + { + State.Notation = EJSONNotation::SKIP; + return; + } + + State.Data.AppendChar(Char); +} + +void FJSONReader::ReadAsNumber(const TCHAR& Char) +{ + if (IsSpace(Char) && State.Data.Len() > 0) + { + State.Notation = EJSONNotation::SKIP; + return; + } + + if ((Char >= '0' && Char <= '9') || Char == '-' || Char == '.' || Char == '+' || Char == 'e' || Char == 'E') + { + State.Data.AppendChar(Char); + } + else + { + State.Error(); + } +} + +void FJSONReader::ReadBasicValue(const TCHAR& Char) +{ + switch(Char) + { + case 'T': + case 't': + case 'F': + case 'f': + { + State.PushBoolean(); + State.Notation = EJSONNotation::STRING_SPECIAL; + ReadAsStringSpecial(Char); + return; + } + case 'N': + case 'n': + { + State.PushNull(); + State.Notation = EJSONNotation::STRING_SPECIAL; + ReadAsStringSpecial(Char); + return; + } + case '\'': + case '"': + { + State.PushString(); + State.Notation = EJSONNotation::STRING; + State.Quote = Char; + return; + } + } + + if ((Char >= '0' && Char <= '9') || Char == '-') + { + State.PushNumber(); + State.Notation = EJSONNotation::NUMBER; + ReadAsNumber(Char); + return; + } +} + +void FJSONReader::ReadAsArray(const TCHAR& Char) +{ + if (IsSpace(Char)) + { + return; + } + ReadBasicValue(Char); +} + +void FJSONReader::ReadAsObject(const TCHAR& Char) +{ + if (IsSpace(Char)) + { + return; + } + + if (State.CheckTokens(EJSONToken::CURLY_BEGIN)) // read key "{" + { + if (Char == '\'' || Char == '"') + { + State.Notation = EJSONNotation::STRING; + State.Quote = Char; + } + else + { + State.Notation = EJSONNotation::STRING_SPECIAL; + ReadAsStringSpecial(Char); + } + } + else if (State.CheckTokens(EJSONToken::CURLY_BEGIN, EJSONToken::COLON)) // read value "{:" + { + ReadBasicValue(Char); + } +} + +void FJSONReader::Skip(const TCHAR& Char) +{ + if (!IsSpace(Char)) + { + State.Error(); + } +} + +bool FJSONReader::Read(const TCHAR Char) +{ + if (Char == '\\' && !State.bEscape) + { + State.bEscape = true; + return true; + } + + if (FindToken(Char)) + { + State.Notation = EJSONNotation::NONE; + UpdateNotation(); + return true; + } + + switch(State.Notation) + { + case EJSONNotation::NONE: UpdateNotation(); break; + + case EJSONNotation::STRING: ReadAsString(Char); break; + case EJSONNotation::STRING_SPECIAL: ReadAsStringSpecial(Char); break; + case EJSONNotation::NUMBER: ReadAsNumber(Char); break; + case EJSONNotation::ARRAY: ReadAsArray(Char); break; + case EJSONNotation::OBJECT: ReadAsObject(Char); break; + + case EJSONNotation::SKIP: Skip(Char); break; + } + + if (State.bError) + { + State.Root = TSharedPtr(nullptr); + State.Size = 0; + return false; + } + + State.bEscape = false; + + return true; +} + +FJSONWriter::FJSONWriter() +{ + +} + +bool FJSONWriter::GetStartChar(const TSharedPtr& JsonValue, FString& Str) +{ + switch (JsonValue->Type) + { + case EJson::Object: + Str = FString(TEXT("{")); + break; + case EJson::Array: + Str = FString(TEXT("[")); + break; + case EJson::String: + Str = FString(TEXT("\"")); + break; + default: + return false; + break; + } + + return true; +} + +bool FJSONWriter::GetEndChar(const TSharedPtr& JsonValue, FString& Str) +{ + switch (JsonValue->Type) + { + case EJson::Object: + Str = FString(TEXT("}")); + break; + case EJson::Array: + Str = FString(TEXT("]")); + break; + case EJson::String: + Str = FString(TEXT("\"")); + break; + default: + return false; + break; + } + + return true; +} + +void FJSONWriter::Write(TSharedPtr JsonValue, FArchive* Writer, bool IsLastElement) +{ + FString Str; + FArchive& Ar = *Writer; + + if (GetStartChar(JsonValue, Str)) + { + UVaRestJsonObject::WriteStringToArchive(Ar, *Str, Str.Len()); + } + + switch (JsonValue->Type) + { + case EJson::Object: + { + int ElementsCount = 0; + auto Values = JsonValue->AsObject()->Values; + + for (auto& ChildJsonPair : Values) + { + Str = FString(TEXT("\"")); + UVaRestJsonObject::WriteStringToArchive(Ar, *Str, Str.Len()); + + const TCHAR* BufferPtr = *ChildJsonPair.Key; + for (int i = 0; i < ChildJsonPair.Key.Len(); ++i) + { + Str = FString(1, &ChildJsonPair.Key[i]); +#if PLATFORM_WINDOWS + UVaRestJsonObject::WriteStringToArchive(Ar, *Str, Str.Len() - 1); +#else + UVaRestJsonObject::WriteStringToArchive(Ar, *Str, Str.Len()); +#endif + } + + Str = FString(TEXT("\"")); + UVaRestJsonObject::WriteStringToArchive(Ar, *Str, Str.Len()); + + Str = FString(TEXT(":")); + UVaRestJsonObject::WriteStringToArchive(Ar, *Str, Str.Len()); + + ++ElementsCount; + + Write(ChildJsonPair.Value, Writer, ElementsCount >= Values.Num()); + } + break; + } + case EJson::Array: + { + int ElementsCount = 0; + auto Array = JsonValue->AsArray(); + + for (auto& ChildJsonValue : Array) + { + ++ElementsCount; + Write(ChildJsonValue, Writer, ElementsCount >= Array.Num()); + } + break; + } + default: + { + FString Value = JsonValue->AsString(); + + const TCHAR* BufferPtr = *Value; + for (int i = 0; i < Value.Len(); ++i) + { + Str = FString(1, &BufferPtr[i]); + if (Str == TEXT("\"")) + { + Str = FString(TEXT("\\")); + UVaRestJsonObject::WriteStringToArchive(Ar, *Str, Str.Len()); + Str = FString(1, &BufferPtr[i]); + } + if (Str == TEXT("\n")) + { + Str = FString(TEXT("\\")); + UVaRestJsonObject::WriteStringToArchive(Ar, *Str, Str.Len()); + Str = FString(TEXT("n")); + UVaRestJsonObject::WriteStringToArchive(Ar, *Str, Str.Len()); + Str = FString(1, &BufferPtr[i]); + } + else if (Str == TEXT("\t")) + { + Str = FString(TEXT("\\")); + UVaRestJsonObject::WriteStringToArchive(Ar, *Str, Str.Len()); + Str = FString(TEXT("t")); + UVaRestJsonObject::WriteStringToArchive(Ar, *Str, Str.Len()); + Str = FString(1, &BufferPtr[i]); + } + else + { +#if PLATFORM_WINDOWS + UVaRestJsonObject::WriteStringToArchive(Ar, *Str, Str.Len() - 1); +#else + UVaRestJsonObject::WriteStringToArchive(Ar, *Str, Str.Len()); +#endif + } + } + + break; + } + } + + if (GetEndChar(JsonValue, Str)) + { + UVaRestJsonObject::WriteStringToArchive(Ar, *Str, Str.Len()); + } + + if (!IsLastElement) + { + Str = FString(TEXT(",")); + UVaRestJsonObject::WriteStringToArchive(Ar, *Str, Str.Len()); + } +} diff --git a/Source/VaRestPlugin/Private/VaRestJsonParser.h b/Source/VaRestPlugin/Private/VaRestJsonParser.h new file mode 100644 index 00000000..2595158d --- /dev/null +++ b/Source/VaRestPlugin/Private/VaRestJsonParser.h @@ -0,0 +1,195 @@ +// Copyright 2015-2017 Mail.Ru Group. All Rights Reserved. + +#pragma once + +#include "Json.h" + +class FJsonValueNonConstArray : public FJsonValueArray +{ +public: + FJsonValueNonConstArray(const TArray>& InArray) : FJsonValueArray(InArray) {} + + /** return non const array */ + TArray>& AsNonConstArray() { return Value; } +}; + +class FJsonValueNonConstBoolean : public FJsonValueBoolean +{ +public: + FJsonValueNonConstBoolean(bool InBool) : FJsonValueBoolean(InBool) {} + + /** return non const bool */ + bool& AsNonConstBool() { return Value; } +}; + +class FJsonValueNonConstString : public FJsonValueString +{ +public: + FJsonValueNonConstString(const FString& InString) : FJsonValueString(InString) {} + + /** return non const string */ + FString& AsNonConstString() { return Value; } +}; + +class FJsonValueNonConstNumber : public FJsonValueNumber +{ +public: + FJsonValueNonConstNumber(double InNumber) : FJsonValueNumber(InNumber) {} + + /** return non const number */ + double& AsNonConstNumber() { return Value; } +}; + +enum class EJSONNotation +{ + NONE, + STRING, + STRING_SPECIAL, + SKIP, + NUMBER, + ARRAY, + OBJECT, +}; + +enum class EJSONToken +{ + CURLY_BEGIN, + CURLY_END, + SQUARE_BEGIN, + SQUARE_END, + COMMA, + COLON, + ROOT, + ERROR, +}; + +struct FJSONState +{ + /** Key */ + FString Key; + + /** Data */ + FString Data; + + /** Root object */ + TSharedPtr Root; + + /** Object list */ + TArray> Objects; + + /** Tokens */ + TArray Tokens; + + /** Notation */ + EJSONNotation Notation; + + /** Current char has escape */ + bool bEscape; + + /** Has error */ + int32 bError; + + /** Las quote for string */ + TCHAR Quote; + + /** Size */ + int32 Size; + + /** Default constructor */ + FJSONState(); + + EJSONToken GetToken(int32 Index = 0); + + FORCEINLINE bool CheckTokens(EJSONToken T1); + + FORCEINLINE bool CheckTokens(EJSONToken T1, EJSONToken T2); + + FORCEINLINE bool CheckTokens(EJSONToken T1, EJSONToken T2, EJSONToken T3); + + FORCEINLINE void PopToken(int32 Num); + + FORCEINLINE void PopObject(); + + FORCEINLINE void PopArray(); + + FORCEINLINE void PopValue(bool bCheckType = true); + + FORCEINLINE FJsonValue* GetLast(); + + FORCEINLINE FJsonValueObject* GetObject(); + + FORCEINLINE FJsonValueNonConstArray* GetArray(); + + FORCEINLINE TSharedPtr PushObject(); + + FORCEINLINE TSharedPtr PushObject(TSharedPtr Object); + + FORCEINLINE TSharedPtr PushArray(); + + FORCEINLINE TSharedPtr PushBoolean(); + + FORCEINLINE TSharedPtr PushNull(); + + FORCEINLINE TSharedPtr PushNumber(); + + FORCEINLINE TSharedPtr PushString(); + + FORCEINLINE void ClearData(); + + FORCEINLINE void ClearKey(); + + FORCEINLINE void DataToKey(); + + FORCEINLINE void Error(); + +}; + +struct FJSONReader +{ + /** State */ + FJSONState State; + + /** Default constructor */ + FJSONReader(); + +private: + + FORCEINLINE bool IsNewLine(const TCHAR& Char); + + FORCEINLINE bool IsSpace(const TCHAR& Char); + + FORCEINLINE bool FindToken(const TCHAR& Char); + + FORCEINLINE void UpdateNotation(); + + FORCEINLINE void ReadAsString(const TCHAR& Char); + + FORCEINLINE void ReadAsStringSpecial(const TCHAR& Char); + + FORCEINLINE void ReadAsNumber(const TCHAR& Char); + + FORCEINLINE void ReadBasicValue(const TCHAR& Char); + + FORCEINLINE void ReadAsArray(const TCHAR& Char); + + FORCEINLINE void ReadAsObject(const TCHAR& Char); + + FORCEINLINE void Skip(const TCHAR& Char); + +public: + + bool Read(const TCHAR Char); // @Pushkin + +}; + +struct FJSONWriter +{ + FJSONWriter(); + + FORCEINLINE bool GetStartChar(const TSharedPtr& JsonValue, FString& Char); + + FORCEINLINE bool GetEndChar(const TSharedPtr& JsonValue, FString& Char); + +public: + void Write(TSharedPtr JsonValue, FArchive* Writer, bool IsLastElement); // @Pushkin +}; diff --git a/Source/VaRestPlugin/Private/VaRestJsonValue.cpp b/Source/VaRestPlugin/Private/VaRestJsonValue.cpp index fd767f9f..a4d5c887 100644 --- a/Source/VaRestPlugin/Private/VaRestJsonValue.cpp +++ b/Source/VaRestPlugin/Private/VaRestJsonValue.cpp @@ -1,5 +1,7 @@ // Copyright 2014 Vladimir Alyamkin. All Rights Reserved. +#include "VaRestJsonValue.h" +#include "VaRestJsonObject.h" #include "VaRestPluginPrivatePCH.h" UVaRestJsonValue::UVaRestJsonValue(const class FObjectInitializer& PCIP) diff --git a/Source/VaRestPlugin/Private/VaRestLibrary.cpp b/Source/VaRestPlugin/Private/VaRestLibrary.cpp index 1a532d65..89be8925 100644 --- a/Source/VaRestPlugin/Private/VaRestLibrary.cpp +++ b/Source/VaRestPlugin/Private/VaRestLibrary.cpp @@ -1,5 +1,8 @@ // Copyright 2016 Vladimir Alyamkin. All Rights Reserved. +#include "VaRestLibrary.h" +#include "VaRestRequestJSON.h" +#include "VaRestJsonObject.h" #include "VaRestPluginPrivatePCH.h" #include "Base64.h" @@ -63,6 +66,34 @@ bool UVaRestLibrary::Base64DecodeData(const FString& Source, TArray& Dest } +////////////////////////////////////////////////////////////////////////// +// File system integration + +class UVaRestJsonObject* UVaRestLibrary::LoadJsonFromFile(UObject* WorldContextObject, const FString& Path) +{ + UVaRestJsonObject* Json = UVaRestJsonObject::ConstructJsonObject(WorldContextObject); + + FString JSONString; + if (FFileHelper::LoadFileToString(JSONString, *(FPaths::ProjectContentDir() + Path))) + { + if (Json->DecodeJson(JSONString)) + { + return Json; + } + else + { + UE_LOG(LogVaRest, Error, TEXT("%s: Can't decode json from file %s"), *VA_FUNC_LINE, *Path); + } + } + else + { + UE_LOG(LogVaRest, Error, TEXT("%s: Can't open file %s"), *VA_FUNC_LINE, *Path); + } + + return nullptr; +} + + ////////////////////////////////////////////////////////////////////////// // Easy URL processing @@ -70,7 +101,7 @@ TMap UVaRestLibrary::RequestMap; void UVaRestLibrary::CallURL(UObject* WorldContextObject, const FString& URL, ERequestVerb Verb, ERequestContentType ContentType, UVaRestJsonObject* VaRestJson, const FVaRestCallDelegate& Callback) { - UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject); + UWorld* World = GEngine->GetWorldFromContextObjectChecked(WorldContextObject); if (World == nullptr) { UE_LOG(LogVaRest, Error, TEXT("UVaRestLibrary: Wrong world context")) diff --git a/Source/VaRestPlugin/Private/VaRestPlugin.cpp b/Source/VaRestPlugin/Private/VaRestPlugin.cpp index 3f22758f..f6386199 100644 --- a/Source/VaRestPlugin/Private/VaRestPlugin.cpp +++ b/Source/VaRestPlugin/Private/VaRestPlugin.cpp @@ -1,5 +1,9 @@ // Copyright 2014 Vladimir Alyamkin. All Rights Reserved. +#include "VaRestPlugin.h" +#include "VaRestJsonObject.h" +#include "VaRestJsonValue.h" +#include "VaRestRequestJSON.h" #include "VaRestPluginPrivatePCH.h" class FVaRestPlugin : public IVaRestPlugin diff --git a/Source/VaRestPlugin/Private/VaRestPluginPrivatePCH.h b/Source/VaRestPlugin/Private/VaRestPluginPrivatePCH.h index 85f6f2d6..7e97aaac 100644 --- a/Source/VaRestPlugin/Private/VaRestPluginPrivatePCH.h +++ b/Source/VaRestPlugin/Private/VaRestPluginPrivatePCH.h @@ -28,6 +28,6 @@ DECLARE_LOG_CATEGORY_EXTERN(LogVaRest, Log, All); -#include "IVaRestPlugin.h" - -#include "VaRestPluginClasses.h" +#define VA_FUNC (FString(__FUNCTION__)) // Current Class Name + Function Name where this is called +#define VA_LINE (FString::FromInt(__LINE__)) // Current Line Number in the code where this is called +#define VA_FUNC_LINE (VA_FUNC + "(" + VA_LINE + ")") // Current Class and Line Number where this is called! diff --git a/Source/VaRestPlugin/Private/VaRestRequestJSON.cpp b/Source/VaRestPlugin/Private/VaRestRequestJSON.cpp index 3e9d6d36..5f4e3faa 100644 --- a/Source/VaRestPlugin/Private/VaRestRequestJSON.cpp +++ b/Source/VaRestPlugin/Private/VaRestRequestJSON.cpp @@ -1,7 +1,14 @@ // Copyright 2014 Vladimir Alyamkin. All Rights Reserved. +#include "VaRestRequestJSON.h" +#include "VaRestJsonObject.h" +#include "VaRestLibrary.h" #include "VaRestPluginPrivatePCH.h" + #include "CoreMisc.h" +#include "Runtime/Launch/Resources/Version.h" + +FString UVaRestRequestJSON::DeprecatedResponseString(TEXT("DEPRECATED: Please use GetResponseContentAsString() instead")); template void FVaRestLatentAction::Cancel() { @@ -118,8 +125,12 @@ void UVaRestRequestJSON::ResetResponseData() ResponseHeaders.Empty(); ResponseCode = -1; + ResponseSize = 0; bIsValidJsonResponse = false; + + // #127 Reset string to deprecated state + ResponseContent = DeprecatedResponseString; } void UVaRestRequestJSON::Cancel() @@ -203,8 +214,14 @@ void UVaRestRequestJSON::SetURL(const FString& Url) { // Be sure to trim URL because it can break links on iOS FString TrimmedUrl = Url; + +#if ENGINE_MINOR_VERSION >= 18 + TrimmedUrl.TrimStartInline(); + TrimmedUrl.TrimEndInline(); +#else TrimmedUrl.Trim(); TrimmedUrl.TrimTrailing(); +#endif HttpRequest->SetURL(TrimmedUrl); } @@ -219,13 +236,19 @@ void UVaRestRequestJSON::ApplyURL(const FString& Url, UVaRestJsonObject *&Result { // Be sure to trim URL because it can break links on iOS FString TrimmedUrl = Url; + +#if ENGINE_MINOR_VERSION >= 18 + TrimmedUrl.TrimStartInline(); + TrimmedUrl.TrimEndInline(); +#else TrimmedUrl.Trim(); TrimmedUrl.TrimTrailing(); +#endif HttpRequest->SetURL(TrimmedUrl); // Prepare latent action - if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject)) + if (UWorld* World = GEngine->GetWorldFromContextObjectChecked(WorldContextObject)) { FLatentActionManager& LatentActionManager = World->GetLatentActionManager(); FVaRestLatentAction *Kont = LatentActionManager.FindExistingAction>(LatentInfo.CallbackTarget, LatentInfo.UUID); @@ -419,11 +442,10 @@ void UVaRestRequestJSON::OnProcessRequestComplete(FHttpRequestPtr Request, FHttp return; } - // Save response data as a string - ResponseContent = Response->GetContentAsString(); - +#if !(PLATFORM_IOS || PLATFORM_ANDROID) // Log response state - UE_LOG(LogVaRest, Log, TEXT("Response (%d): %sJSON(%s%s%s)JSON"), ResponseCode, LINE_TERMINATOR, LINE_TERMINATOR, *ResponseContent, LINE_TERMINATOR); + UE_LOG(LogVaRest, Log, TEXT("Response (%d): %sJSON(%s%s%s)JSON"), ResponseCode, LINE_TERMINATOR, LINE_TERMINATOR, *Response->GetContentAsString(), LINE_TERMINATOR); +#endif // Process response headers TArray Headers = Response->GetAllHeaders(); @@ -436,14 +458,21 @@ void UVaRestRequestJSON::OnProcessRequestComplete(FHttpRequestPtr Request, FHttp ResponseHeaders.Add(Key, Value); } } - + // Try to deserialize data to JSON - TSharedRef> JsonReader = TJsonReaderFactory::Create(ResponseContent); - FJsonSerializer::Deserialize(JsonReader, ResponseJsonObj->GetRootObject()); - + const TArray& Bytes = Response->GetContent(); + ResponseSize = ResponseJsonObj->DeserializeFromUTF8Bytes((const ANSICHAR*) Bytes.GetData(), Bytes.Num()); + // Decide whether the request was successful bIsValidJsonResponse = bWasSuccessful && ResponseJsonObj->GetRootObject().IsValid(); - + + if (!bIsValidJsonResponse) + { + // Save response data as a string + ResponseContent = Response->GetContentAsString(); + ResponseSize = ResponseContent.GetAllocatedSize(); + } + // Log errors if (!bIsValidJsonResponse) { @@ -490,3 +519,36 @@ bool UVaRestRequestJSON::HasTag(FName Tag) const { return (Tag != NAME_None) && Tags.Contains(Tag); } + + +////////////////////////////////////////////////////////////////////////// +// Data + +FString UVaRestRequestJSON::GetResponseContentAsString(bool bCacheResponseContent) +{ + // Check we have valide response + if (!bIsValidJsonResponse || !ResponseJsonObj || !ResponseJsonObj->IsValidLowLevel()) + { + // Discard previous cached string if we had one + ResponseContent = DeprecatedResponseString; + + return TEXT("Invalid response"); + } + + // Check if we should re-genetate it in runtime + if (!bCacheResponseContent) + { + UE_LOG(LogVaRest, Warning, TEXT("%s: Use of uncashed getter could be slow"), *VA_FUNC_LINE); + return ResponseJsonObj->EncodeJson(); + } + + // Check that we haven't cached content yet + if (ResponseContent == DeprecatedResponseString) + { + UE_LOG(LogVaRest, Warning, TEXT("%s: Response content string is cached"), *VA_FUNC_LINE); + ResponseContent = ResponseJsonObj->EncodeJson(); + } + + // Return previously cached content now + return ResponseContent; +} diff --git a/Source/VaRestPlugin/Public/IVaRestPlugin.h b/Source/VaRestPlugin/Public/VaRestPlugin.h similarity index 100% rename from Source/VaRestPlugin/Public/IVaRestPlugin.h rename to Source/VaRestPlugin/Public/VaRestPlugin.h diff --git a/Source/VaRestPlugin/VaRestPlugin.Build.cs b/Source/VaRestPlugin/VaRestPlugin.Build.cs index 83ef5ff9..b16ef0c4 100644 --- a/Source/VaRestPlugin/VaRestPlugin.Build.cs +++ b/Source/VaRestPlugin/VaRestPlugin.Build.cs @@ -8,6 +8,8 @@ public class VaRestPlugin : ModuleRules { public VaRestPlugin(ReadOnlyTargetRules Target) : base(Target) { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + PrivateIncludePaths.AddRange( new string[] { "VaRestPlugin/Private", diff --git a/VaRestPlugin.uplugin b/VaRestPlugin.uplugin index c7c3875d..c135da31 100644 --- a/VaRestPlugin.uplugin +++ b/VaRestPlugin.uplugin @@ -2,11 +2,11 @@ "FileVersion" : 3, "FriendlyName" : "VaRest", - "Version" : 18, - "VersionName" : "1.1-r18", + "Version" : 20, + "VersionName" : "1.1-r20", "CreatedBy" : "Vladimir Alyamkin", "CreatedByURL" : "http://alyamkin.com", - "EngineVersion" : "4.17.0", + "EngineVersion" : "4.18.0", "Description" : "Plugin that makes REST (JSON) server communication easy to use", "Category" : "Network", "MarketplaceURL" : "com.epicgames.launcher://ue/marketplace/content/e47be161e7a24e928560290abd5dcc4f",