// Copyright Epic Games, Inc. All Rights Reserved. #if WITH_TESTS #include "CoreMinimal.h" #include "Containers/AnsiString.h" #include "Misc/AutomationTest.h" #include "Policies/CondensedJsonPrintPolicy.h" #include "Policies/PrettyJsonPrintPolicy.h" #include "Serialization/JsonReader.h" #include "Serialization/JsonSerializable.h" #include "Serialization/JsonSerializer.h" #include "Serialization/JsonSerializerMacros.h" #include "Serialization/JsonTypes.h" #include "Tests/TestHarnessAdapter.h" #if WITH_LOW_LEVEL_TESTS #include "TestCommon/Expectations.h" #endif typedef TJsonWriterFactory< TCHAR, TCondensedJsonPrintPolicy > FCondensedJsonStringWriterFactory; typedef TJsonWriter< TCHAR, TCondensedJsonPrintPolicy > FCondensedJsonStringWriter; typedef TJsonWriterFactory< ANSICHAR, TCondensedJsonPrintPolicy > FCondensedJsonAnsiStringWriterFactory; typedef TJsonWriter< ANSICHAR, TCondensedJsonPrintPolicy > FCondensedJsonAnsiStringWriter; typedef TJsonWriterFactory< UTF8CHAR, TCondensedJsonPrintPolicy > FCondensedJsonUtf8StringWriterFactory; typedef TJsonWriter< UTF8CHAR, TCondensedJsonPrintPolicy > FCondensedJsonUtf8StringWriter; typedef TJsonWriterFactory< TCHAR, TPrettyJsonPrintPolicy > FPrettyJsonStringWriterFactory; typedef TJsonWriter< TCHAR, TPrettyJsonPrintPolicy > FPrettyJsonStringWriter; TEST_CASE_NAMED(FJsonAutomationTest, "System::Engine::FileSystem::JSON", "[ApplicationContextMask][SmokeFilter]") { // Null Case { const FString InputString = TEXT(""); TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create( InputString ); TSharedPtr Object; REQUIRE(FJsonSerializer::Deserialize( Reader, Object ) == false); REQUIRE(!Object.IsValid()); } // Empty Object Case { const FString InputString = TEXT("{}"); TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create( InputString ); TSharedPtr Object; REQUIRE(FJsonSerializer::Deserialize( Reader, Object )); REQUIRE(Object.IsValid()); FString OutputString; TSharedRef< FCondensedJsonStringWriter > Writer = FCondensedJsonStringWriterFactory::Create( &OutputString ); REQUIRE(FJsonSerializer::Serialize( Object.ToSharedRef(), Writer )); REQUIRE(InputString == OutputString); } // Empty Array Case { const FString InputString = TEXT("[]"); TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create( InputString ); TArray< TSharedPtr > Array; REQUIRE(FJsonSerializer::Deserialize( Reader, Array )); REQUIRE(Array.Num() == 0); FString OutputString; TSharedRef< FCondensedJsonStringWriter > Writer = FCondensedJsonStringWriterFactory::Create( &OutputString ); REQUIRE(FJsonSerializer::Serialize( Array, Writer )); REQUIRE(InputString == OutputString); } // Empty Array with Empty Identifier Case { const FString ExpectedString = TEXT("[]"); FString OutputString; TSharedRef EmptyValuesArray = MakeShared(TArray>()); TSharedRef JsonWriter = FCondensedJsonStringWriterFactory::Create( &OutputString ); REQUIRE(FJsonSerializer::Serialize(EmptyValuesArray, FString(), JsonWriter)); REQUIRE(ExpectedString == OutputString); } // Serializing Object Value with Empty Identifier Case { const FString ExpectedString = TEXT("{\"\":\"foo\"}"); FString OutputString; TSharedRef FooValue = MakeShared("foo"); TSharedRef JsonWriter = FCondensedJsonStringWriterFactory::Create(&OutputString); JsonWriter->WriteObjectStart(); REQUIRE(FJsonSerializer::Serialize(FooValue, FString(), JsonWriter, false)); JsonWriter->WriteObjectEnd(); JsonWriter->Close(); REQUIRE(ExpectedString == OutputString); } // Simple Array Case { const FString InputString = TEXT( "[" "{" "\"Value\":\"Some String\"" "}" "]" ); TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create( InputString ); TArray< TSharedPtr > Array; bool bSuccessful = FJsonSerializer::Deserialize(Reader, Array); REQUIRE(bSuccessful); REQUIRE(Array.Num() == 1); REQUIRE(Array[0].IsValid()); TSharedPtr< FJsonObject > Object = Array[0]->AsObject(); REQUIRE(Object.IsValid()); REQUIRE(Object->GetStringField( TEXT("Value") ) == TEXT("Some String")); FString OutputString; TSharedRef< FCondensedJsonStringWriter > Writer = FCondensedJsonStringWriterFactory::Create( &OutputString ); REQUIRE(FJsonSerializer::Serialize( Array, Writer )); REQUIRE(InputString == OutputString); } // Object Array Case { const FString InputString = TEXT( "[" "{" "\"Value\":\"Some String1\"" "}," "{" "\"Value\":\"Some String2\"" "}," "{" "\"Value\":\"Some String3\"" "}" "]" ); TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create(InputString); TArray< TSharedPtr > Array; bool bSuccessful = FJsonSerializer::Deserialize(Reader, Array); REQUIRE(bSuccessful); REQUIRE(Array.Num() == 3); REQUIRE(Array[0].IsValid()); REQUIRE(Array[1].IsValid()); REQUIRE(Array[2].IsValid()); TSharedPtr< FJsonObject > Object = Array[0]->AsObject(); REQUIRE(Object.IsValid()); REQUIRE(Object->GetStringField(TEXT("Value")) == TEXT("Some String1")); Object = Array[1]->AsObject(); REQUIRE(Object.IsValid()); REQUIRE(Object->GetStringField(TEXT("Value")) == TEXT("Some String2")); Object = Array[2]->AsObject(); REQUIRE(Object.IsValid()); REQUIRE(Object->GetStringField(TEXT("Value")) == TEXT("Some String3")); FString OutputString; TSharedRef< FCondensedJsonStringWriter > Writer = FCondensedJsonStringWriterFactory::Create(&OutputString); REQUIRE(FJsonSerializer::Serialize(Array, Writer)); REQUIRE(InputString == OutputString); } // FJsonValue operator== Comparison Equality Test { /* comparing: "Type1_Type2_#" */ const FString StoredAsType1 = TEXT( "{" "\"bool_string_0\" : false," "\"bool_string_1\" : true," "\"bool_string_2\" : false," "\"bool_string_3\" : true," "\"int_string_0\" : 10," "\"int_string_1\" : 100," "\"float_string_0\" : 10.123," "\"float_string_1\" : 100.34," "\"string_string_0\" : \"foo1\"," "\"string_string_1\" : \"foo2\"," "\"bool_int_0\" : true," "\"bool_int_1\" : false," "\"int_float_0\" : 10," "\"int_float_1\" : 100.00," "\"int_float_2\" : 10," "\"float_bool_0\" : 1.0," "\"float_bool_1\" : 0.0," "\"float_bool_2\" : 1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001234," "\"float_bool_3\" : 0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001234" "}" ); const FString StoredAsType2 = TEXT( "{" "\"bool_string_0\" : \"false\"," "\"bool_string_1\" : \"true\"," "\"bool_string_2\" : \"0\"," "\"bool_string_3\" : \"1\"," "\"int_string_0\" : \"10\"," "\"int_string_1\" : \"100\"," "\"float_string_0\" : \"10.123\"," "\"float_string_1\" : \"100.34\"," "\"string_string_0\" : \"foo1\"," "\"string_string_1\" : \"foo2\"," "\"bool_int_0\" : 1," "\"bool_int_1\" : 0," "\"int_float_0\" : 10.0," "\"int_float_1\" : 100.00," "\"int_float_2\" : 10.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001234," "\"float_bool_0\" : true," "\"float_bool_1\" : false," "\"float_bool_2\" : true," "\"float_bool_3\" : false" "}" ); TSharedRef< TJsonReader<> > TypeReader_1 = TJsonReaderFactory<>::Create(StoredAsType1); TSharedRef< TJsonReader<> > TypeReader_2 = TJsonReaderFactory<>::Create(StoredAsType2); TSharedPtr TypedObject_1; REQUIRE(FJsonSerializer::Deserialize(TypeReader_1, TypedObject_1)); REQUIRE(TypedObject_1.IsValid()); TSharedPtr TypedObject_2; REQUIRE(FJsonSerializer::Deserialize(TypeReader_2, TypedObject_2)); REQUIRE(TypedObject_2.IsValid()); REQUIRE(TypedObject_1->Values.Num() == TypedObject_2->Values.Num()); for (const TPair>& KV : TypedObject_1->Values) { REQUIRE(TypedObject_2->Values.Contains(KV.Key)); TSharedPtr Typed1_FieldValue = KV.Value; TSharedPtr Typed2_FieldValue = TypedObject_2->Values[KV.Key]; REQUIRE(*Typed1_FieldValue == *Typed2_FieldValue); REQUIRE(*Typed2_FieldValue == *Typed1_FieldValue); } } // FJsonValue operator!= Comparison Inequality Test { /* comparing: "Type1_Type2_#" */ const FString StoredAsType1 = TEXT( "{" "\"bool_string_0\" : false," "\"bool_string_1\" : true," "\"int_string_0\" : 10," "\"int_string_1\" : 100," "\"float_string_0\" : 10.123," "\"float_string_1\" : 100.34," // `FJsonValue operator==` uses `FString::operator==` which uses `ESearchCase::IgnoreCase` //"\"string_string_0\" : \"foo1\"," //"\"string_string_1\" : \"foo2\"," "\"bool_int_0\" : true," "\"bool_int_1\" : false," "\"int_float_0\" : 10," "\"int_float_1\" : 100.00," "\"int_float_2\" : 10," "\"float_bool_0\" : 1.0," "\"float_bool_1\" : 0.0," "\"float_bool_2\" : 2.5," "\"float_bool_3\" : 3.5" "}" ); const FString StoredAsType2 = TEXT( "{" "\"bool_string_0\" : \"not_true\"," "\"bool_string_1\" : \"not_false\"," "\"int_string_0\" : \"20\"," "\"int_string_1\" : \"200\"," "\"float_string_0\" : \"20.123\"," "\"float_string_1\" : \"200.34\"," // `FJsonValue operator==` uses `FString::operator==` which uses `ESearchCase::IgnoreCase` //"\"string_string_0\" : \"Foo1\"," //"\"string_string_1\" : \"Foo2\"," "\"bool_int_0\" : 2," "\"bool_int_1\" : 3," "\"int_float_0\" : 20.0," "\"int_float_1\" : 200.00," "\"int_float_2\" : 10.5," "\"float_bool_0\" : false," "\"float_bool_1\" : true," "\"float_bool_2\" : true," "\"float_bool_3\" : false" "}" ); TSharedRef< TJsonReader<> > TypeReader_1 = TJsonReaderFactory<>::Create(StoredAsType1); TSharedRef< TJsonReader<> > TypeReader_2 = TJsonReaderFactory<>::Create(StoredAsType2); TSharedPtr TypedObject_1; REQUIRE(FJsonSerializer::Deserialize(TypeReader_1, TypedObject_1)); REQUIRE(TypedObject_1.IsValid()); TSharedPtr TypedObject_2; REQUIRE(FJsonSerializer::Deserialize(TypeReader_2, TypedObject_2)); REQUIRE(TypedObject_2.IsValid()); REQUIRE(TypedObject_1->Values.Num() == TypedObject_2->Values.Num()); for (const TPair>& KV : TypedObject_1->Values) { REQUIRE(TypedObject_2->Values.Contains(KV.Key)); TSharedPtr Typed1_FieldValue = KV.Value; TSharedPtr Typed2_FieldValue = TypedObject_2->Values[KV.Key]; REQUIRE(*Typed1_FieldValue != *Typed2_FieldValue); REQUIRE(*Typed2_FieldValue != *Typed1_FieldValue); } } // JsonSimpleValueVariant operator== Comparison Equality Test { /* comparing: "Type1_Type2_#" */ const FString StoredAsType1 = TEXT( "{" "\"bool_string_0\" : false," "\"bool_string_1\" : true," "\"bool_string_2\" : false," "\"bool_string_3\" : true," "\"int_string_0\" : 10," "\"int_string_1\" : 100," "\"float_string_0\" : 10.123," "\"float_string_1\" : 100.34," "\"string_string_0\" : \"foo1\"," "\"string_string_1\" : \"foo2\"," "\"bool_int_0\" : true," "\"bool_int_1\" : false," "\"int_float_0\" : 10," "\"int_float_1\" : 100.00," "\"int_float_2\" : 10," "\"float_bool_0\" : 1.0," "\"float_bool_1\" : 0.0," "\"float_bool_2\" : 1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001234," "\"float_bool_3\" : 0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001234," "\"float_bool_4\" : 0.9999999999999999999999999999999999999999999999999999999999999999999999999876" "}" ); const FString StoredAsType2 = TEXT( "{" "\"bool_string_0\" : \"false\"," "\"bool_string_1\" : \"true\"," "\"bool_string_2\" : \"0\"," "\"bool_string_3\" : \"1\"," "\"int_string_0\" : \"10\"," "\"int_string_1\" : \"100\"," "\"float_string_0\" : \"10.123\"," "\"float_string_1\" : \"100.34\"," "\"string_string_0\" : \"foo1\"," "\"string_string_1\" : \"foo2\"," "\"bool_int_0\" : 1," "\"bool_int_1\" : 0," "\"int_float_0\" : 10.0," "\"int_float_1\" : 100.00," "\"int_float_2\" : 10.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001234," "\"float_bool_0\" : true," "\"float_bool_1\" : false," "\"float_bool_2\" : true," "\"float_bool_3\" : false," "\"float_bool_4\" : true" "}" ); TSharedRef< TJsonReader<> > TypeReader_1 = TJsonReaderFactory<>::Create(StoredAsType1); TSharedRef< TJsonReader<> > TypeReader_2 = TJsonReaderFactory<>::Create(StoredAsType2); TSharedPtr TypedObject_1; REQUIRE(FJsonSerializer::Deserialize(TypeReader_1, TypedObject_1)); REQUIRE(TypedObject_1.IsValid()); TSharedPtr TypedObject_2; REQUIRE(FJsonSerializer::Deserialize(TypeReader_2, TypedObject_2)); REQUIRE(TypedObject_2.IsValid()); REQUIRE(TypedObject_1->Values.Num() == TypedObject_2->Values.Num()); for (const TPair>& KV : TypedObject_1->Values) { REQUIRE(TypedObject_2->Values.Contains(KV.Key)); TSharedPtr Typed1_FieldValue = KV.Value; TSharedPtr Typed2_FieldValue = TypedObject_2->Values[KV.Key]; REQUIRE(UE::Json::ToSimpleJsonVariant(*Typed1_FieldValue) == UE::Json::ToSimpleJsonVariant(*Typed2_FieldValue)); REQUIRE(UE::Json::ToSimpleJsonVariant(*Typed2_FieldValue) == UE::Json::ToSimpleJsonVariant(*Typed1_FieldValue)); } } // JsonSimpleValueVariant operator!= Comparison Inequality Test { /* comparing: "Type1_Type2_#" */ const FString StoredAsType1 = TEXT( "{" "\"bool_string_0\" : false," "\"bool_string_1\" : true," "\"int_string_0\" : 10," "\"int_string_1\" : 100," "\"float_string_0\" : 10.123," "\"float_string_1\" : 100.34," "\"string_string_0\" : \"foo1\"," "\"string_string_1\" : \"foo2\"," "\"bool_int_0\" : true," "\"bool_int_1\" : false," "\"int_float_0\" : 10," "\"int_float_1\" : 100.00," "\"int_float_2\" : 10," "\"float_bool_0\" : 1.0," "\"float_bool_1\" : 0.0," "\"float_bool_2\" : 2.5," "\"float_bool_3\" : 3.5" "}" ); const FString StoredAsType2 = TEXT( "{" "\"bool_string_0\" : \"not_true\"," "\"bool_string_1\" : \"not_false\"," "\"int_string_0\" : \"20\"," "\"int_string_1\" : \"200\"," "\"float_string_0\" : \"20.123\"," "\"float_string_1\" : \"200.34\"," "\"string_string_0\" : \"Foo1\"," "\"string_string_1\" : \"Foo2\"," "\"bool_int_0\" : 2," "\"bool_int_1\" : 3," "\"int_float_0\" : 20.0," "\"int_float_1\" : 200.00," "\"int_float_2\" : 10.5," "\"float_bool_0\" : false," "\"float_bool_1\" : true," "\"float_bool_2\" : true," "\"float_bool_3\" : false" "}" ); TSharedRef< TJsonReader<> > TypeReader_1 = TJsonReaderFactory<>::Create(StoredAsType1); TSharedRef< TJsonReader<> > TypeReader_2 = TJsonReaderFactory<>::Create(StoredAsType2); TSharedPtr TypedObject_1; REQUIRE(FJsonSerializer::Deserialize(TypeReader_1, TypedObject_1)); REQUIRE(TypedObject_1.IsValid()); TSharedPtr TypedObject_2; REQUIRE(FJsonSerializer::Deserialize(TypeReader_2, TypedObject_2)); REQUIRE(TypedObject_2.IsValid()); REQUIRE(TypedObject_1->Values.Num() == TypedObject_2->Values.Num()); for (const TPair>& KV : TypedObject_1->Values) { REQUIRE(TypedObject_2->Values.Contains(KV.Key)); TSharedPtr Typed1_FieldValue = KV.Value; TSharedPtr Typed2_FieldValue = TypedObject_2->Values[KV.Key]; REQUIRE(UE::Json::ToSimpleJsonVariant(*Typed1_FieldValue) != UE::Json::ToSimpleJsonVariant(*Typed2_FieldValue)); REQUIRE(UE::Json::ToSimpleJsonVariant(*Typed2_FieldValue) != UE::Json::ToSimpleJsonVariant(*Typed1_FieldValue)); } } // Number Array Case { const FString InputString = TEXT( "[" "10," "20," "30," "40" "]" ); TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create(InputString); TArray< TSharedPtr > Array; bool bSuccessful = FJsonSerializer::Deserialize(Reader, Array); REQUIRE(bSuccessful); REQUIRE(Array.Num() == 4); REQUIRE(Array[0].IsValid()); REQUIRE(Array[1].IsValid()); REQUIRE(Array[2].IsValid()); REQUIRE(Array[3].IsValid()); double Number = Array[0]->AsNumber(); REQUIRE(Number == 10); Number = Array[1]->AsNumber(); REQUIRE(Number == 20); Number = Array[2]->AsNumber(); REQUIRE(Number == 30); Number = Array[3]->AsNumber(); REQUIRE(Number == 40); FString OutputString; TSharedRef< FCondensedJsonStringWriter > Writer = FCondensedJsonStringWriterFactory::Create(&OutputString); REQUIRE(FJsonSerializer::Serialize(Array, Writer)); REQUIRE(InputString == OutputString); } // String Array Case { const FString InputString = TEXT( "[" "\"Some String1\"," "\"Some String2\"," "\"Some String3\"," "\"Some String4\"" "]" ); TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create(InputString); TArray< TSharedPtr > Array; bool bSuccessful = FJsonSerializer::Deserialize(Reader, Array); REQUIRE(bSuccessful); REQUIRE(Array.Num() == 4); REQUIRE(Array[0].IsValid()); REQUIRE(Array[1].IsValid()); REQUIRE(Array[2].IsValid()); REQUIRE(Array[3].IsValid()); FString Text = Array[0]->AsString(); REQUIRE(Text == TEXT("Some String1")); Text = Array[1]->AsString(); REQUIRE(Text == TEXT("Some String2")); Text = Array[2]->AsString(); REQUIRE(Text == TEXT("Some String3")); Text = Array[3]->AsString(); REQUIRE(Text == TEXT("Some String4")); FString OutputString; TSharedRef< FCondensedJsonStringWriter > Writer = FCondensedJsonStringWriterFactory::Create(&OutputString); REQUIRE(FJsonSerializer::Serialize(Array, Writer)); REQUIRE(InputString == OutputString); } // Complex Array Case { const FString InputString = TEXT( "[" "\"Some String1\"," "10," "{" "\"\":\"Empty Key\"," "\"Value\":\"Some String3\"" "}," "[" "\"Some String4\"," "\"Some String5\"" "]," "true," "null" "]" ); TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create(InputString); TArray< TSharedPtr > Array; bool bSuccessful = FJsonSerializer::Deserialize(Reader, Array); REQUIRE(bSuccessful); REQUIRE(Array.Num() == 6); REQUIRE(Array[0].IsValid()); REQUIRE(Array[1].IsValid()); REQUIRE(Array[2].IsValid()); REQUIRE(Array[3].IsValid()); REQUIRE(Array[4].IsValid()); REQUIRE(Array[5].IsValid()); FString Text = Array[0]->AsString(); REQUIRE(Text == TEXT("Some String1")); double Number = Array[1]->AsNumber(); REQUIRE(Number == 10); TSharedPtr< FJsonObject > Object = Array[2]->AsObject(); REQUIRE(Object.IsValid()); REQUIRE(Object->GetStringField(TEXT("Value")) == TEXT("Some String3")); REQUIRE(Object->GetStringField(TEXT("")) == TEXT("Empty Key")); const TArray>& InnerArray = Array[3]->AsArray(); REQUIRE(InnerArray.Num() == 2); REQUIRE(Array[0].IsValid()); REQUIRE(Array[1].IsValid()); Text = InnerArray[0]->AsString(); REQUIRE(Text == TEXT("Some String4")); Text = InnerArray[1]->AsString(); REQUIRE(Text == TEXT("Some String5")); bool Boolean = Array[4]->AsBool(); REQUIRE(Boolean == true); REQUIRE(Array[5]->IsNull() == true); FString OutputString; TSharedRef< FCondensedJsonStringWriter > Writer = FCondensedJsonStringWriterFactory::Create(&OutputString); REQUIRE(FJsonSerializer::Serialize(Array, Writer)); REQUIRE(InputString == OutputString); } // String Test { const FString InputString = TEXT( "{" "\"Value\":\"Some String, Escape Chars: \\\\, \\\", \\/, \\b, \\f, \\n, \\r, \\t, \\u002B\"" "}" ); TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create( InputString ); TSharedPtr Object; bool bSuccessful = FJsonSerializer::Deserialize(Reader, Object); REQUIRE(bSuccessful); REQUIRE(Object.IsValid()); const TSharedPtr* Value = Object->Values.Find(TEXT("Value")); REQUIRE(Value); REQUIRE((*Value)->Type == EJson::String); const FString String = (*Value)->AsString(); REQUIRE(String == TEXT("Some String, Escape Chars: \\, \", /, \b, \f, \n, \r, \t, +")); FString OutputString; TSharedRef< FCondensedJsonStringWriter > Writer = FCondensedJsonStringWriterFactory::Create( &OutputString ); REQUIRE(FJsonSerializer::Serialize( Object.ToSharedRef(), Writer )); const FString TestOutput = TEXT( "{" "\"Value\":\"Some String, Escape Chars: \\\\, \\\", /, \\b, \\f, \\n, \\r, \\t, +\"" "}" ); REQUIRE(OutputString == TestOutput); } // String Test ANSI { const ANSICHAR* InputString = "{" \ "\"Value\":\"Some String, Escape Chars: \\\\, \\\", \\/, \\b, \\f, \\n, \\r, \\t, \\u002B\\uD83D\\uDE10\""\ "}"; TSharedRef< TJsonReader > Reader = TJsonReaderFactory::CreateFromView(InputString); TSharedPtr Object; bool bSuccessful = FJsonSerializer::Deserialize(Reader, Object); REQUIRE(bSuccessful); REQUIRE(Object.IsValid()); { const TSharedPtr* Value = Object->Values.Find(TEXT("Value")); REQUIRE((Value && (*Value)->Type == EJson::String)); const FString String = (*Value)->AsString(); REQUIRE(String == TEXT("Some String, Escape Chars: \\, \", /, \b, \f, \n, \r, \t, +😐")); const FUtf8String Utf8String = (*Value)->AsUtf8String(); REQUIRE(Utf8String == UTF8TEXT("Some String, Escape Chars: \\, \", /, \b, \f, \n, \r, \t, +😐")); } FAnsiString AnsiOutputString; TSharedRef< FCondensedJsonAnsiStringWriter > AnsiWriter = FCondensedJsonAnsiStringWriterFactory::Create(&AnsiOutputString); REQUIRE(FJsonSerializer::Serialize(Object.ToSharedRef(), AnsiWriter)); const FAnsiString AnsiTestOutput = "{" \ "\"Value\":\"Some String, Escape Chars: \\\\, \\\", /, \\b, \\f, \\n, \\r, \\t, +\\ud83d\\ude10\""\ "}"; REQUIRE(AnsiOutputString == AnsiTestOutput); } // String Test UTF8 { // UTF8TEXT will prepend the first u8 literal specifier // UTF8TEXT does a cast, so we can't add it each line and still get the literals to concatenate const UTF8CHAR* InputString = UTF8TEXT( "{" u8"\"Value\":\"Some String, Escape Chars: \\\\, \\\", \\/, \\b, \\f, \\n, \\r, \\t, \\u002B\\uD83D\\uDE10\"," u8"\"Value1\":\"Greek String, Σὲ γνωρίζω ἀπὸ τὴν κόψη\"," u8"\"Value2\":\"Thai String, สิบสองกษัตริย์ก่อนหน้าแลถัดไป\"," u8"\"Value3\":\"Hello world, Καλημέρα κόσμε, コンニチハ\"" u8"}" ); TSharedRef< TJsonReader > Reader = TJsonReaderFactory::CreateFromView( InputString ); TSharedPtr Object; bool bSuccessful = FJsonSerializer::Deserialize(Reader, Object); REQUIRE(bSuccessful); REQUIRE(Object.IsValid()); { const TSharedPtr* Value = Object->Values.Find(TEXT("Value")); REQUIRE((Value && (*Value)->Type == EJson::String)); const FString String = (*Value)->AsString(); REQUIRE(String == TEXT("Some String, Escape Chars: \\, \", /, \b, \f, \n, \r, \t, +😐")); const FUtf8String Utf8String = (*Value)->AsUtf8String(); REQUIRE(Utf8String == UTF8TEXT("Some String, Escape Chars: \\, \", /, \b, \f, \n, \r, \t, +😐")); } { const TSharedPtr* Value = Object->Values.Find(TEXT("Value1")); REQUIRE((Value && (*Value)->Type == EJson::String)); const FString String = (*Value)->AsString(); REQUIRE(String == TEXT("Greek String, Σὲ γνωρίζω ἀπὸ τὴν κόψη")); const FUtf8String Utf8String = (*Value)->AsUtf8String(); REQUIRE(Utf8String == UTF8TEXT("Greek String, Σὲ γνωρίζω ἀπὸ τὴν κόψη")); } { const TSharedPtr* Value = Object->Values.Find(TEXT("Value2")); REQUIRE((Value && (*Value)->Type == EJson::String)); const FString String = (*Value)->AsString(); REQUIRE(String == TEXT("Thai String, สิบสองกษัตริย์ก่อนหน้าแลถัดไป")); const FUtf8String Utf8String = (*Value)->AsUtf8String(); REQUIRE(Utf8String == UTF8TEXT("Thai String, สิบสองกษัตริย์ก่อนหน้าแลถัดไป")); } { const TSharedPtr* Value = Object->Values.Find(TEXT("Value3")); REQUIRE((Value && (*Value)->Type == EJson::String)); const FString String = (*Value)->AsString(); REQUIRE(String == TEXT("Hello world, Καλημέρα κόσμε, コンニチハ")); const FUtf8String Utf8String = (*Value)->AsUtf8String(); REQUIRE(Utf8String == UTF8TEXT("Hello world, Καλημέρα κόσμε, コンニチハ")); } FString OutputString; TSharedRef< FCondensedJsonStringWriter > Writer = FCondensedJsonStringWriterFactory::Create( &OutputString ); REQUIRE(FJsonSerializer::Serialize( Object.ToSharedRef(), Writer )); // Note: The literal prefix for the string (u8, L), must be present for every concatenated string, not just the first one const FString TestOutput = TEXT("{") TEXT("\"Value\":\"Some String, Escape Chars: \\\\, \\\", /, \\b, \\f, \\n, \\r, \\t, +😐\",") TEXT("\"Value1\":\"Greek String, Σὲ γνωρίζω ἀπὸ τὴν κόψη\",") TEXT("\"Value2\":\"Thai String, สิบสองกษัตริย์ก่อนหน้าแลถัดไป\",") TEXT("\"Value3\":\"Hello world, Καλημέρα κόσμε, コンニチハ\"") TEXT("}"); REQUIRE(OutputString == TestOutput); FUtf8String Utf8OutputString; TSharedRef< FCondensedJsonUtf8StringWriter > Utf8Writer = FCondensedJsonUtf8StringWriterFactory::Create(&Utf8OutputString); REQUIRE(FJsonSerializer::Serialize(Object.ToSharedRef(), Utf8Writer)); // Note: The literal prefix for the string (u8, L), must be present for every concatenated string, not just the first one const FUtf8String Utf8TestOutput = UTF8TEXT("{"\ "\"Value\":\"Some String, Escape Chars: \\\\, \\\", /, \\b, \\f, \\n, \\r, \\t, +😐\","\ "\"Value1\":\"Greek String, Σὲ γνωρίζω ἀπὸ τὴν κόψη\","\ "\"Value2\":\"Thai String, สิบสองกษัตริย์ก่อนหน้าแลถัดไป\","\ "\"Value3\":\"Hello world, Καλημέρα κόσμε, コンニチハ\""\ "}"); REQUIRE(Utf8OutputString == Utf8TestOutput); } // Number Test { const FString InputString = TEXT( "{" "\"Value1\":2.544e+15," "\"Value2\":-0.544E-2," "\"Value3\":251e3," "\"Value4\":-0.0," "\"Value5\":843" "}" ); TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create( InputString ); TSharedPtr Object; bool bSuccessful = FJsonSerializer::Deserialize(Reader, Object); REQUIRE(bSuccessful); REQUIRE(Object.IsValid()); double TestValues[] = {2.544e+15, -0.544e-2, 251e3, -0.0, 843}; for (int32 i = 0; i < 5; ++i) { const TSharedPtr* Value = Object->Values.Find(FString::Printf(TEXT("Value%i"), i + 1)); REQUIRE((Value && (*Value)->Type == EJson::Number)); const double Number = (*Value)->AsNumber(); REQUIRE(Number == TestValues[i]); } FString OutputString; TSharedRef< FCondensedJsonStringWriter > Writer = FCondensedJsonStringWriterFactory::Create( &OutputString ); REQUIRE(FJsonSerializer::Serialize( Object.ToSharedRef(), Writer )); // %g isn't standardized, so we use the same %g format that is used inside PrintJson instead of hardcoding the values here const FString TestOutput = FString::Printf( TEXT( "{" "\"Value1\":%.17g," "\"Value2\":%.17g," "\"Value3\":%.17g," "\"Value4\":%.17g," "\"Value5\":%.17g" "}" ), TestValues[0], TestValues[1], TestValues[2], TestValues[3], TestValues[4]); REQUIRE(OutputString == TestOutput); } // Test Nan { const FString TestNanInd = FString::Printf(TEXT("%.17g"), std::numeric_limits::quiet_NaN()); CHECK(TestNanInd == TEXT("nan")); // Make sure code will not run on standard library impl which outputs nan(ind) const FString InputString = TEXT( "{" "\"Value0\":nan," "\"Value1\":NaN," "\"Value2\":-nan," "\"Value3\":-nan(ind)" "}" ); TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create( InputString ); TSharedPtr Object; bool bSuccessful = FJsonSerializer::Deserialize(Reader, Object); REQUIRE(bSuccessful); REQUIRE(Object.IsValid()); const TSharedPtr* Value0 = Object->Values.Find(FString::Printf(TEXT("Value%i"), 0)); const TSharedPtr* Value1 = Object->Values.Find(FString::Printf(TEXT("Value%i"), 1)); const TSharedPtr* Value2 = Object->Values.Find(FString::Printf(TEXT("Value%i"), 2)); const TSharedPtr* Value3 = Object->Values.Find(FString::Printf(TEXT("Value%i"), 3)); const double Number0 = (*Value0)->AsNumber(); const double Number1 = (*Value1)->AsNumber(); const double Number2 = (*Value2)->AsNumber(); const double Number3 = (*Value3)->AsNumber(); REQUIRE(FMath::IsNaN(Number0)); REQUIRE(FMath::IsNaN(Number1)); REQUIRE(FMath::IsNaN(Number2)); REQUIRE(FMath::IsNaN(Number3)); FString OutputString; TSharedRef< FCondensedJsonStringWriter > Writer = FCondensedJsonStringWriterFactory::Create( &OutputString ); REQUIRE(FJsonSerializer::Serialize( Object.ToSharedRef(), Writer )); // %g isn't standardized, so we use the same %g format that is used inside PrintJson instead of hardcoding the values here const FString TestOutput = FString::Printf( TEXT( "{" "\"Value0\":%.17g," "\"Value1\":%.17g," "\"Value2\":%.17g," "\"Value3\":%.17g" "}" ), std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN(), -std::numeric_limits::quiet_NaN(), -std::numeric_limits::quiet_NaN()); REQUIRE(OutputString == TestOutput); } // Boolean/Null Test { const FString InputString = TEXT( "{" "\"Value1\":true," "\"Value2\":true," "\"Value3\":faLsE," "\"Value4\":null," "\"Value5\":NULL" "}" ); TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create( InputString ); TSharedPtr Object; bool bSuccessful = FJsonSerializer::Deserialize(Reader, Object); REQUIRE(bSuccessful); REQUIRE(Object.IsValid()); bool TestValues[] = {true, true, false}; for (int32 i = 0; i < 5; ++i) { const TSharedPtr* Value = Object->Values.Find(FString::Printf(TEXT("Value%i"), i + 1)); REQUIRE(Value); if (i < 3) { REQUIRE((*Value)->Type == EJson::Boolean); const bool Bool = (*Value)->AsBool(); REQUIRE(Bool == TestValues[i]); } else { REQUIRE((*Value)->Type == EJson::Null); REQUIRE((*Value)->IsNull()); } } FString OutputString; TSharedRef< FCondensedJsonStringWriter > Writer = FCondensedJsonStringWriterFactory::Create( &OutputString ); REQUIRE(FJsonSerializer::Serialize( Object.ToSharedRef(), Writer )); const FString TestOutput = TEXT( "{" "\"Value1\":true," "\"Value2\":true," "\"Value3\":false," "\"Value4\":null," "\"Value5\":null" "}" ); REQUIRE(OutputString == TestOutput); } // Object Test && extra whitespace test { const FString InputStringWithExtraWhitespace = TEXT( " \n\r\n {" "\"Object\":" "{" "\"NestedValue\":null," "\"NestedObject\":{}" "}," "\"Value\":true" "} \n\r\n " ); const FString InputString = TEXT( "{" "\"Object\":" "{" "\"NestedValue\":null," "\"NestedObject\":{}" "}," "\"Value\":true" "}" ); TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create( InputStringWithExtraWhitespace ); TSharedPtr Object; bool bSuccessful = FJsonSerializer::Deserialize(Reader, Object); REQUIRE(bSuccessful); REQUIRE(Object.IsValid()); const TSharedPtr* InnerValueFail = Object->Values.Find(TEXT("InnerValue")); REQUIRE(!InnerValueFail); const TSharedPtr* ObjectValue = Object->Values.Find(TEXT("Object")); REQUIRE((ObjectValue && (*ObjectValue)->Type == EJson::Object)); const TSharedPtr InnerObject = (*ObjectValue)->AsObject(); REQUIRE(InnerObject.IsValid()); { const TSharedPtr* NestedValueValue = InnerObject->Values.Find(TEXT("NestedValue")); REQUIRE((NestedValueValue && (*NestedValueValue)->Type == EJson::Null)); REQUIRE((*NestedValueValue)->IsNull()); const TSharedPtr* NestedObjectValue = InnerObject->Values.Find(TEXT("NestedObject")); REQUIRE((NestedObjectValue && (*NestedObjectValue)->Type == EJson::Object)); const TSharedPtr InnerInnerObject = (*NestedObjectValue)->AsObject(); REQUIRE(InnerInnerObject.IsValid()); { const TSharedPtr* NestedValueValueFail = InnerInnerObject->Values.Find(TEXT("NestedValue")); REQUIRE(!NestedValueValueFail); } } const TSharedPtr* ValueValue = Object->Values.Find(TEXT("Value")); REQUIRE((ValueValue && (*ValueValue)->Type == EJson::Boolean)); const bool Bool = (*ValueValue)->AsBool(); REQUIRE(Bool); FString OutputString; TSharedRef< FCondensedJsonStringWriter > Writer = FCondensedJsonStringWriterFactory::Create( &OutputString ); REQUIRE(FJsonSerializer::Serialize( Object.ToSharedRef(), Writer )); REQUIRE(OutputString == InputString); } // Array Test { const FString InputString = TEXT( "{" "\"Array\":" "[" "[]," "\"Some String\"," "\"Another String\"," "null," "true," "false," "45," "{}" "]" "}" ); TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create( InputString ); TSharedPtr Object; bool bSuccessful = FJsonSerializer::Deserialize(Reader, Object); REQUIRE(bSuccessful); REQUIRE(Object.IsValid()); const TSharedPtr* InnerValueFail = Object->Values.Find(TEXT("InnerValue")); REQUIRE(!InnerValueFail); const TSharedPtr* ArrayValue = Object->Values.Find(TEXT("Array")); REQUIRE((ArrayValue && (*ArrayValue)->Type == EJson::Array)); const TArray< TSharedPtr > Array = (*ArrayValue)->AsArray(); REQUIRE(Array.Num() == 8); EJson ValueTypes[] = {EJson::Array, EJson::String, EJson::String, EJson::Null, EJson::Boolean, EJson::Boolean, EJson::Number, EJson::Object}; for (int32 i = 0; i < Array.Num(); ++i) { const TSharedPtr& Value = Array[i]; REQUIRE(Value.IsValid()); REQUIRE(Value->Type == ValueTypes[i]); } const TArray< TSharedPtr >& InnerArray = Array[0]->AsArray(); REQUIRE(InnerArray.Num() == 0); REQUIRE(Array[1]->AsString() == TEXT("Some String")); REQUIRE(Array[2]->AsString() == TEXT("Another String")); REQUIRE(Array[3]->IsNull()); REQUIRE(Array[4]->AsBool()); REQUIRE(!Array[5]->AsBool()); REQUIRE(FMath::Abs(Array[6]->AsNumber() - 45.f) < KINDA_SMALL_NUMBER); const TSharedPtr InnerObject = Array[7]->AsObject(); REQUIRE(InnerObject.IsValid()); FString OutputString; TSharedRef< FCondensedJsonStringWriter > Writer = FCondensedJsonStringWriterFactory::Create( &OutputString ); REQUIRE(FJsonSerializer::Serialize( Object.ToSharedRef(), Writer )); REQUIRE(OutputString == InputString); } // Pretty Print Test { const FString InputString = TEXT( "{" LINE_TERMINATOR_ANSI " \"Data1\": \"value\"," LINE_TERMINATOR_ANSI " \"Data2\": \"value\"," LINE_TERMINATOR_ANSI " \"Array\": [" LINE_TERMINATOR_ANSI " {" LINE_TERMINATOR_ANSI " \"InnerData1\": \"value\"" LINE_TERMINATOR_ANSI " }," LINE_TERMINATOR_ANSI " []," LINE_TERMINATOR_ANSI " [ 1, 2, 3, 4 ]," LINE_TERMINATOR_ANSI " {" LINE_TERMINATOR_ANSI " }," LINE_TERMINATOR_ANSI " \"value\"," LINE_TERMINATOR_ANSI " \"value\"" LINE_TERMINATOR_ANSI " ]," LINE_TERMINATOR_ANSI " \"Object\":" LINE_TERMINATOR_ANSI " {" LINE_TERMINATOR_ANSI " }" LINE_TERMINATOR_ANSI "}" ); TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create( InputString ); TSharedPtr Object; REQUIRE(FJsonSerializer::Deserialize( Reader, Object )); REQUIRE(Object.IsValid()); FString OutputString; TSharedRef< FPrettyJsonStringWriter > Writer = FPrettyJsonStringWriterFactory::Create( &OutputString ); REQUIRE(FJsonSerializer::Serialize( Object.ToSharedRef(), Writer )); REQUIRE(OutputString == InputString); } // Line and Character # test { const FString InputString = TEXT( "{" LINE_TERMINATOR_ANSI " \"Data1\": \"value\"," LINE_TERMINATOR_ANSI " \"Array\":" LINE_TERMINATOR_ANSI " [" LINE_TERMINATOR_ANSI " 12345," LINE_TERMINATOR_ANSI " True" LINE_TERMINATOR_ANSI " ]," LINE_TERMINATOR_ANSI " \"Object\":" LINE_TERMINATOR_ANSI " {" LINE_TERMINATOR_ANSI " }" LINE_TERMINATOR_ANSI "}" ); TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create( InputString ); EJsonNotation Notation = EJsonNotation::Null; REQUIRE(( Reader->ReadNext( Notation ) && Notation == EJsonNotation::ObjectStart )); REQUIRE(( Reader->GetLineNumber() == 1 && Reader->GetCharacterNumber() == 1 )); REQUIRE(( Reader->ReadNext( Notation ) && Notation == EJsonNotation::String )); REQUIRE(( Reader->GetLineNumber() == 2 && Reader->GetCharacterNumber() == 17 )); REQUIRE(( Reader->ReadNext( Notation ) && Notation == EJsonNotation::ArrayStart )); REQUIRE(( Reader->GetLineNumber() == 4 && Reader->GetCharacterNumber() == 2 )); REQUIRE(( Reader->ReadNext( Notation ) && Notation == EJsonNotation::Number )); REQUIRE(( Reader->GetLineNumber() == 5 && Reader->GetCharacterNumber() == 7 )); REQUIRE(( Reader->ReadNext( Notation ) && Notation == EJsonNotation::Boolean )); REQUIRE(( Reader->GetLineNumber() == 6 && Reader->GetCharacterNumber() == 6 )); } // Failure Cases TArray FailureInputs; // Unclosed Object FailureInputs.Add( TEXT("{")); // Values in Object without identifiers FailureInputs.Add( TEXT( "{" "\"Value1\"," "\"Value2\"," "43" "}" ) ); // Unexpected End Of Input Found FailureInputs.Add( TEXT( "{" "\"Object\":" "{" "\"NestedValue\":null,") ); // Missing first brace FailureInputs.Add( TEXT( "\"Object\":" "{" "\"NestedValue\":null," "\"NestedObject\":{}" "}," "\"Value\":true" "}" ) ); // Missing last character FailureInputs.Add( TEXT( "{" "\"Object\":" "{" "\"NestedValue\":null," "\"NestedObject\":{}" "}," "\"Value\":true" ) ); // Missing curly brace FailureInputs.Add(TEXT("}")); // Missing bracket FailureInputs.Add(TEXT("]")); // Extra last character FailureInputs.Add( TEXT( "{" "\"Object\":" "{" "\"NestedValue\":null," "\"NestedObject\":{}" "}," "\"Value\":true" "}0" ) ); // Missing comma FailureInputs.Add( TEXT( "{" "\"Value1\":null," "\"Value2\":\"string\"" "\"Value3\":65.3" "}" ) ); // Extra comma FailureInputs.Add( TEXT( "{" "\"Value1\":null," "\"Value2\":\"string\"," "\"Value3\":65.3," "}" ) ); // Badly formed true/false/null FailureInputs.Add(TEXT("{\"Value\":tru}")); FailureInputs.Add(TEXT("{\"Value\":full}")); FailureInputs.Add(TEXT("{\"Value\":nulle}")); FailureInputs.Add(TEXT("{\"Value\":n%ll}")); // Floating Point Failures FailureInputs.Add(TEXT("{\"Value\":65.3e}")); FailureInputs.Add(TEXT("{\"Value\":65.}")); FailureInputs.Add(TEXT("{\"Value\":.7}")); FailureInputs.Add(TEXT("{\"Value\":+6}")); FailureInputs.Add(TEXT("{\"Value\":01}")); FailureInputs.Add(TEXT("{\"Value\":00.56}")); FailureInputs.Add(TEXT("{\"Value\":-1.e+4}")); FailureInputs.Add(TEXT("{\"Value\":2e+}")); // Bad Escape Characters FailureInputs.Add(TEXT("{\"Value\":\"Hello\\xThere\"}")); FailureInputs.Add(TEXT("{\"Value\":\"Hello\\u123There\"}")); FailureInputs.Add(TEXT("{\"Value\":\"Hello\\RThere\"}")); for (int32 i = 0; i < FailureInputs.Num(); ++i) { TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create( FailureInputs[i] ); TSharedPtr Object; REQUIRE(FJsonSerializer::Deserialize( Reader, Object ) == false); REQUIRE(!Object.IsValid()); } // TryGetNumber tests { auto JsonNumberToInt64 = [](double Val, int64& OutVal) -> bool { FJsonValueNumber JsonVal(Val); return ((FJsonValue&)JsonVal).TryGetNumber(OutVal); }; auto JsonNumberToInt32 = [](double Val, int32& OutVal) -> bool { FJsonValueNumber JsonVal(Val); return ((FJsonValue&)JsonVal).TryGetNumber(OutVal); }; auto JsonNumberToUInt32 = [](double Val, uint32& OutVal) -> bool { FJsonValueNumber JsonVal(Val); return ((FJsonValue&)JsonVal).TryGetNumber(OutVal); }; // TryGetNumber-Int64 tests { int64 IntVal; bool bOk = JsonNumberToInt64(9007199254740991.0, IntVal); TestTrue(TEXT("TryGetNumber-Int64 Big Float64 succeeds"), bOk); TestEqual(TEXT("TryGetNumber-Int64 Big Float64"), IntVal, 9007199254740991LL); } { int64 IntVal; bool bOk = JsonNumberToInt64(-9007199254740991.0, IntVal); TestTrue(TEXT("TryGetNumber-Int64 Small Float64 succeeds"), bOk); TestEqual(TEXT("TryGetNumber-Int64 Small Float64"), IntVal, -9007199254740991LL); } { int64 IntVal; bool bOk = JsonNumberToInt64(0.4999999999999997, IntVal); TestTrue(TEXT("TryGetNumber-Int64 Lesser than near half succeeds"), bOk); TestEqual(TEXT("TryGetNumber-Int64 Lesser than near half rounds to zero"), IntVal, 0LL); } { int64 IntVal; bool bOk = JsonNumberToInt64(-0.4999999999999997, IntVal); TestTrue(TEXT("TryGetNumber-Int64 Greater than near negative half succeeds"), bOk); TestEqual(TEXT("TryGetNumber-Int64 Greater than near negative half rounds to zero"), IntVal, 0LL); } { int64 IntVal; bool bOk = JsonNumberToInt64(0.5, IntVal); TestTrue(TEXT("TryGetNumber-Int64 Half rounds to next integer succeeds"), bOk); TestEqual(TEXT("TryGetNumber-Int64 Half rounds to next integer"), IntVal, 1LL); } { int64 IntVal; bool bOk = JsonNumberToInt64(-0.5, IntVal); TestTrue(TEXT("TryGetNumber-Int64 Negative half rounds to next negative integer succeeds"), bOk); TestEqual(TEXT("TryGetNumber-Int64 Negative half rounds to next negative integer succeeds"), IntVal, -1LL); } // TryGetNumber-Int32 tests { int32 IntVal; bool bOk = JsonNumberToInt32(2147483647.000001, IntVal); TestFalse(TEXT("TryGetNumber-Int32 Number greater than max Int32 fails"), bOk); } { int32 IntVal; bool bOk = JsonNumberToInt32(-2147483648.000001, IntVal); TestFalse(TEXT("TryGetNumber-Int32 Number lesser than min Int32 fails"), bOk); } { int32 IntVal; bool bOk = JsonNumberToInt32(2147483647.0, IntVal); TestTrue(TEXT("TryGetNumber-Int32 Max Int32 succeeds"), bOk); TestEqual(TEXT("TryGetNumber-Int32 Max Int32"), IntVal, INT_MAX); } { int32 IntVal; bool bOk = JsonNumberToInt32(2147483646.5, IntVal); TestTrue(TEXT("TryGetNumber-Int32 Round up to max Int32 succeeds"), bOk); TestEqual(TEXT("TryGetNumber-Int32 Round up to max Int32"), IntVal, INT_MAX); } { int32 IntVal; bool bOk = JsonNumberToInt32(-2147483648.0, IntVal); TestTrue(TEXT("TryGetNumber-Int32 Min Int32 succeeds"), bOk); TestEqual(TEXT("TryGetNumber-Int32 Min Int32"), IntVal, INT_MIN); } { int32 IntVal; bool bOk = JsonNumberToInt32(-2147483647.5, IntVal); TestTrue(TEXT("TryGetNumber-Int32 Round down to min Int32 succeeds"), bOk); TestEqual(TEXT("TryGetNumber-Int32 Round down to min Int32"), IntVal, INT_MIN); } { int32 IntVal; bool bOk = JsonNumberToInt32(0.4999999999999997, IntVal); TestTrue(TEXT("TryGetNumber-Int32 Lesser than near half succeeds"), bOk); TestEqual(TEXT("TryGetNumber-Int32 Lesser than near half rounds to zero"), IntVal, 0); } { int32 IntVal; bool bOk = JsonNumberToInt32(-0.4999999999999997, IntVal); TestTrue(TEXT("TryGetNumber-Int32 Greater than near negative half succeeds"), bOk); TestEqual(TEXT("TryGetNumber-Int32 Greater than near negative half rounds to zero"), IntVal, 0); } { int32 IntVal; bool bOk = JsonNumberToInt32(0.5, IntVal); TestTrue(TEXT("TryGetNumber-Int32 Half rounds to next integer succeeds"), bOk); TestEqual(TEXT("TryGetNumber-Int32 Half rounds to next integer"), IntVal, 1); } { int32 IntVal; bool bOk = JsonNumberToInt32(-0.5, IntVal); TestTrue(TEXT("TryGetNumber-Int32 Negative half rounds to next negative integer succeeds"), bOk); TestEqual(TEXT("TryGetNumber-Int32 Negative half rounds to next negative integer succeeds"), IntVal, -1); } // TryGetNumber-UInt32 tests { uint32 IntVal; bool bOk = JsonNumberToUInt32(4294967295.000001, IntVal); TestFalse(TEXT("TryGetNumber-UInt32 Number greater than max Uint32 fails"), bOk); } { uint32 IntVal; bool bOk = JsonNumberToUInt32(-0.000000000000001, IntVal); TestFalse(TEXT("TryGetNumber-UInt32 Negative number fails"), bOk); } { uint32 IntVal; bool bOk = JsonNumberToUInt32(4294967295.0, IntVal); TestTrue(TEXT("TryGetNumber-UInt32 Max UInt32 succeeds"), bOk); TestEqual(TEXT("TryGetNumber-UInt32 Max UInt32"), IntVal, UINT_MAX); } { uint32 IntVal; bool bOk = JsonNumberToUInt32(4294967294.5, IntVal); TestTrue(TEXT("TryGetNumber-UInt32 Round up to max UInt32 succeeds"), bOk); TestEqual(TEXT("TryGetNumber-UInt32 Round up to max UInt32"), IntVal, UINT_MAX); } { uint32 IntVal; bool bOk = JsonNumberToUInt32(0.4999999999999997, IntVal); TestTrue(TEXT("TryGetNumber-UInt32 Lesser than near half succeeds"), bOk); TestEqual(TEXT("TryGetNumber-UInt32 Lesser than near half rounds to zero"), IntVal, 0U); } { uint32 IntVal; bool bOk = JsonNumberToUInt32(0.5, IntVal); TestTrue(TEXT("TryGetNumber-UInt32 Half rounds to next integer succeeds"), bOk); TestEqual(TEXT("TryGetNumber-UInt32 Half rounds to next integer"), IntVal, 1U); } } } TEST_CASE_NAMED(FJsonSerializerMacrosTest, "System::Engine::FileSystem::JSON::MacroToSerializeMembers", "[ApplicationContextMask][SmokeFilter]") { struct FJsonStruct : public FJsonSerializable { struct FSubJsonStruct : public FJsonSerializable { int32 SubVarInt; FString SubVarString; FSubJsonStruct(int32 InSubVarInt = 0, FStringView InSubVarString = TEXTVIEW("")) : SubVarInt(InSubVarInt) , SubVarString(InSubVarString) { } BEGIN_JSON_SERIALIZER JSON_SERIALIZE("sub_var_int", SubVarInt); JSON_SERIALIZE("sub_var_string", SubVarString); END_JSON_SERIALIZER }; int32 VarInt = 0; FSubJsonStruct VarSerializable; BEGIN_JSON_SERIALIZER JSON_SERIALIZE("var_int", VarInt); JSON_SERIALIZE_MEMBERS_OF(VarSerializable); END_JSON_SERIALIZER; }; const TCHAR* SourceJsonString = TEXT("{\"var_int\":2,\"sub_var_int\":10,\"sub_var_string\":\"abc\"}"); FJsonStruct JsonStructTarget; JsonStructTarget.FromJson(SourceJsonString); CHECK(JsonStructTarget.VarInt == 2); CHECK(JsonStructTarget.VarSerializable.SubVarInt == 10); CHECK(JsonStructTarget.VarSerializable.SubVarString == TEXT("abc")); FString ToJsonResult = JsonStructTarget.ToJson(/*bPrettyPrint*/false); CHECK(ToJsonResult == SourceJsonString); } #endif // WITH_TESTS