// Copyright Epic Games, Inc. All Rights Reserved. #include "Dom/JsonValue.h" #include "Dom/JsonObject.h" FJsonValue::FJsonValue() : Type(EJson::None) { } FJsonValue::~FJsonValue() = default; double FJsonValue::AsNumber() const { double Number = 0.0; if (!TryGetNumber(Number)) { ErrorMessage(TEXT("Number")); } return Number; } FString FJsonValue::AsString() const { FString String; if (!TryGetString(String)) { ErrorMessage(TEXT("String")); } return String; } FUtf8String FJsonValue::AsUtf8String() const { FUtf8String String; if (!TryGetUtf8String(String)) { ErrorMessage(TEXT("Utf8String")); } return String; } bool FJsonValue::AsBool() const { bool Bool = false; if (!TryGetBool(Bool)) { ErrorMessage(TEXT("Boolean")); } return Bool; } const TArray>& FJsonValue::AsArray() const { const TArray>* Array = nullptr; if (!TryGetArray(Array)) { static const TArray> EmptyArray; Array = &EmptyArray; ErrorMessage(TEXT("Array")); } return *Array; } const TSharedPtr& FJsonValue::AsObject() const { const TSharedPtr* Object = nullptr; if (!TryGetObject(Object)) { static const TSharedPtr EmptyObject = MakeShared(); Object = &EmptyObject; ErrorMessage(TEXT("Object")); } return *Object; } // ----------------------------------- template bool TryConvertNumber(const FJsonValue& InValue, T& OutNumber) { double Double; if (InValue.TryGetNumber(Double) && (Double >= TNumericLimits::Min()) && (Double <= static_cast(TNumericLimits::Max()))) { OutNumber = static_cast(FMath::RoundHalfFromZero(Double)); return true; } return false; } // Need special handling for int64/uint64, due to overflow in the numeric limits. // 2^63-1 and 2^64-1 cannot be exactly represented as a double, so TNumericLimits<>::Max() gets rounded up to exactly 2^63 or 2^64 by the compiler's implicit cast to double. // This breaks the overflow check in TryConvertNumber. We use "<" rather than "<=" along with the exact power-of-two double literal to fix this. template <> bool TryConvertNumber(const FJsonValue& InValue, uint64& OutNumber) { double Double; if (InValue.TryGetNumber(Double) && Double >= 0.0 && Double < 18446744073709551616.0) { OutNumber = static_cast(FMath::RoundHalfFromZero(Double)); return true; } return false; } template <> bool TryConvertNumber(const FJsonValue& InValue, int64& OutNumber) { double Double; if (InValue.TryGetNumber(Double) && Double >= -9223372036854775808.0 && Double < 9223372036854775808.0) { OutNumber = static_cast(FMath::RoundHalfFromZero(Double)); return true; } return false; } // ----------------------------------- bool FJsonValue::TryGetNumber(double& OutNumber) const { return false; } bool FJsonValue::TryGetNumber(float& OutNumber) const { double Double; if (TryGetNumber(Double)) { OutNumber = static_cast(Double); return true; } return false; } bool FJsonValue::TryGetNumber(uint8& OutNumber) const { return TryConvertNumber(*this, OutNumber); } bool FJsonValue::TryGetNumber(uint16& OutNumber) const { return TryConvertNumber(*this, OutNumber); } bool FJsonValue::TryGetNumber(uint32& OutNumber) const { return TryConvertNumber(*this, OutNumber); } bool FJsonValue::TryGetNumber(uint64& OutNumber) const { return TryConvertNumber(*this, OutNumber); } bool FJsonValue::TryGetNumber(int8& OutNumber) const { return TryConvertNumber(*this, OutNumber); } bool FJsonValue::TryGetNumber(int16& OutNumber) const { return TryConvertNumber(*this, OutNumber); } bool FJsonValue::TryGetNumber(int32& OutNumber) const { return TryConvertNumber(*this, OutNumber); } bool FJsonValue::TryGetNumber(int64& OutNumber) const { return TryConvertNumber(*this, OutNumber); } bool FJsonValue::TryGetString(FString& OutString) const { return false; } bool FJsonValue::TryGetUtf8String(FUtf8String& OutString) const { return false; } bool FJsonValue::TryGetBool(bool& OutBool) const { return false; } bool FJsonValue::TryGetArray(const TArray>*& OutArray) const { return false; } bool FJsonValue::TryGetArray(TArray>*& OutArray) { return false; } bool FJsonValue::TryGetObject(const TSharedPtr*& Object) const { return false; } bool FJsonValue::TryGetObject(TSharedPtr*& Object) { return false; } bool FJsonValue::PreferStringRepresentation() const { return false; } SIZE_T FJsonValue::GetMemoryFootprint() const { return sizeof(*this); } //static bool FJsonValue::CompareEqual( const FJsonValue& Lhs, const FJsonValue& Rhs ) { if (Lhs.Type != Rhs.Type) { const bool bLhsIsSimpleVariant = Lhs.Type == EJson::Boolean || Lhs.Type == EJson::Number || Lhs.Type == EJson::String; const bool bRhsIsSimpleVariant = Rhs.Type == EJson::Boolean || Rhs.Type == EJson::Number || Rhs.Type == EJson::String; if (bLhsIsSimpleVariant && bRhsIsSimpleVariant) { return UE::Json::ToSimpleJsonVariant(Lhs) == UE::Json::ToSimpleJsonVariant(Rhs); } else { return false; } } switch (Lhs.Type) { case EJson::None: case EJson::Null: return true; case EJson::String: return Lhs.AsString() == Rhs.AsString(); case EJson::Number: return Lhs.AsNumber() == Rhs.AsNumber(); case EJson::Boolean: return Lhs.AsBool() == Rhs.AsBool(); case EJson::Array: { const TArray>& LhsArray = Lhs.AsArray(); const TArray>& RhsArray = Rhs.AsArray(); if (LhsArray.Num() != RhsArray.Num()) { return false; } // compare each element for (int32 i = 0; i < LhsArray.Num(); ++i) { if (!CompareEqual(*LhsArray[i], *RhsArray[i])) { return false; } } } return true; case EJson::Object: { const TSharedPtr& LhsObject = Lhs.AsObject(); const TSharedPtr& RhsObject = Rhs.AsObject(); if (LhsObject.IsValid() != RhsObject.IsValid()) { return false; } if (LhsObject.IsValid()) { if (LhsObject->Values.Num() != RhsObject->Values.Num()) { return false; } // compare each element for (const auto& It : LhsObject->Values) { const FString& Key = It.Key; const TSharedPtr* RhsValue = RhsObject->Values.Find(Key); if (RhsValue == NULL) { // not found in both objects return false; } const TSharedPtr& LhsValue = It.Value; if (LhsValue.IsValid() != RhsValue->IsValid()) { return false; } if (LhsValue.IsValid()) { if (!CompareEqual(*LhsValue.Get(), *RhsValue->Get())) { return false; } } } } } return true; default: return false; } } static void DuplicateJsonArray(const TArray>& Source, TArray>& Dest) { for (const TSharedPtr& Value : Source) { Dest.Add(FJsonValue::Duplicate(Value)); } } TSharedPtr FJsonValue::Duplicate(const TSharedPtr& Src) { return Duplicate(ConstCastSharedPtr(Src)); } TSharedPtr FJsonValue::Duplicate(const TSharedPtr& Src) { switch (Src->Type) { case EJson::Boolean: { bool BoolValue; if (Src->TryGetBool(BoolValue)) { return MakeShared(BoolValue); } } case EJson::Number: { double NumberValue; if (Src->TryGetNumber(NumberValue)) { return MakeShared(NumberValue); } } case EJson::String: { FString StringValue; if (Src->TryGetString(StringValue)) { return MakeShared(StringValue); } } case EJson::Object: { const TSharedPtr* ObjectValue; if (Src->TryGetObject(ObjectValue)) { TSharedPtr NewObject = MakeShared(); FJsonObject::Duplicate(*ObjectValue, NewObject); return MakeShared(NewObject); } } case EJson::Array: { const TArray>* ArrayValue; if (Src->TryGetArray(ArrayValue)) { TArray> NewArray; DuplicateJsonArray(*ArrayValue, NewArray); return MakeShared(NewArray); } } } return TSharedPtr(); } void FJsonValue::ErrorMessage(const FString& InType) const { if (IsNull()) { UE_LOG(LogJson, Warning, TEXT("Json Value of type '%s' used as a '%s'."), *GetType(), *InType); } else { UE_LOG(LogJson, Error, TEXT("Json Value of type '%s' used as a '%s'."), *GetType(), *InType); } } FJsonValueNumber::FJsonValueNumber(double InNumber) : Value(InNumber) { Type = EJson::Number; } FJsonValueNumber::~FJsonValueNumber() = default; bool FJsonValueNumber::TryGetNumber(double& OutNumber) const { OutNumber = Value; return true; } bool FJsonValueNumber::TryGetBool(bool& OutBool) const { OutBool = (Value != 0.0); return true; } bool FJsonValueNumber::TryGetString(FString& OutString) const { OutString = FString::SanitizeFloat(Value, 0); return true; } bool FJsonValueNumber::TryGetUtf8String(FUtf8String& OutString) const { OutString = FUtf8String::SanitizeFloat(Value, 0); return true; } SIZE_T FJsonValueNumber::GetMemoryFootprint() const { return sizeof(*this); } FString FJsonValueNumber::GetType() const { return TEXT("Number"); } FJsonValueBoolean::FJsonValueBoolean(bool InBool) : Value(InBool) { Type = EJson::Boolean; } FJsonValueBoolean::~FJsonValueBoolean() = default; bool FJsonValueBoolean::TryGetNumber(double& OutNumber) const { OutNumber = Value ? 1 : 0; return true; } bool FJsonValueBoolean::TryGetBool(bool& OutBool) const { OutBool = Value; return true; } bool FJsonValueBoolean::TryGetString(FString& OutString) const { OutString = Value ? TEXT("true") : TEXT("false"); return true; } bool FJsonValueBoolean::TryGetUtf8String(FUtf8String& OutString) const { OutString = Value ? UTF8TEXT("true") : UTF8TEXT("false"); return true; } SIZE_T FJsonValueBoolean::GetMemoryFootprint() const { return sizeof(*this); } FString FJsonValueBoolean::GetType() const { return TEXT("Boolean"); } FJsonValueArray::FJsonValueArray(const TArray>& InArray) : Value(InArray) { Type = EJson::Array; } FJsonValueArray::FJsonValueArray(TArray>&& InArray) : Value(MoveTemp(InArray)) { Type = EJson::Array; } FJsonValueArray::~FJsonValueArray() = default; bool FJsonValueArray::TryGetArray(const TArray>*& OutArray) const { OutArray = &Value; return true; } bool FJsonValueArray::TryGetArray(TArray>*& OutArray) { OutArray = &Value; return true; } SIZE_T FJsonValueArray::GetMemoryFootprint() const { return sizeof(*this) + GetAllocatedSize(); } SIZE_T FJsonValueArray::GetAllocatedSize() const { SIZE_T SizeBytes = 0; SizeBytes += Value.GetAllocatedSize(); for (const TSharedPtr& Element : Value) { SizeBytes += Element.IsValid() ? Element->GetMemoryFootprint() : 0; } return SizeBytes; } FString FJsonValueArray::GetType() const { return TEXT("Array"); } FJsonValueObject::FJsonValueObject(TSharedPtr InObject) : Value(MoveTemp(InObject)) { Type = EJson::Object; } FJsonValueObject::~FJsonValueObject() = default; bool FJsonValueObject::TryGetObject(const TSharedPtr*& OutObject) const { OutObject = &Value; return true; } bool FJsonValueObject::TryGetObject(TSharedPtr*& OutObject) { OutObject = &Value; return true; } SIZE_T FJsonValueObject::GetMemoryFootprint() const { return sizeof(*this) + GetAllocatedSize(); } SIZE_T FJsonValueObject::GetAllocatedSize() const { return Value.IsValid() ? Value->GetMemoryFootprint() : 0; } FString FJsonValueObject::GetType() const { return TEXT("Object"); } FJsonValueNull::FJsonValueNull() { Type = EJson::Null; } FJsonValueNull::~FJsonValueNull() = default; SIZE_T FJsonValueNull::GetMemoryFootprint() const { return sizeof(*this); } FString FJsonValueNull::GetType() const { return TEXT("Null"); } namespace UE::Json { JsonSimpleValueVariant ToSimpleJsonVariant(const FJsonValue& InJsonValue) { if (!InJsonValue.PreferStringRepresentation()) { if (InJsonValue.Type == EJson::Boolean) { return JsonSimpleValueVariant(TInPlaceType(), InJsonValue.AsBool()); } else if (InJsonValue.Type == EJson::Number) { const double JsonNumber_v = InJsonValue.AsNumber(); /* If the Json Number Value requires a decimal point, then we read in the value as a double, otherwise, we read it in as an int */ if (FString::SanitizeFloat(JsonNumber_v, 0).Contains(TEXT("."))) { return ToSimpleJsonVariant(JsonNumber_v); } else { return ToSimpleJsonVariant(FMath::RoundToInt64(JsonNumber_v)); } } } return JsonSimpleValueVariant(TInPlaceType(), InJsonValue.AsString()); } } bool operator==(const JsonNumberValueVariants& Lhs, const FString& Rhs) { return Rhs.IsNumeric() && ::Visit([Rhs](const auto& StoredNumber) { using StoredNumberType = std::decay_t; if constexpr (std::is_same_v || std::is_same_v) { return FString::SanitizeFloat(StoredNumber, 0) == Rhs; } else { return StoredNumber == FCString::Atoi64(*Rhs); } }, Lhs); } FString ToString(const JsonNumberValueVariants& InNumberVariant) { return ::Visit([](auto& StoredNumber) { using StoredNumberType = std::decay_t; if constexpr (std::is_same_v || std::is_same_v) { return FString::SanitizeFloat(StoredNumber, 0); } else { return FString::Printf(TEXT("%lld"), static_cast(StoredNumber)); } }, InNumberVariant); } bool operator==(const JsonNumberValueVariants& Lhs, const JsonNumberValueVariants& Rhs) { const bool bLhsIsFloat = Lhs.IsType() || Lhs.IsType(); const bool bRhsIsFloat = Rhs.IsType() || Rhs.IsType(); if (bLhsIsFloat || bRhsIsFloat) { return ToString(Lhs) == ToString(Rhs); } else { auto CastToInt64Functor = [](auto& StoredNumber) { return static_cast(StoredNumber); }; const int64 LhsValue = ::Visit(CastToInt64Functor, Lhs); const int64 RhsValue = ::Visit(CastToInt64Functor, Rhs); return LhsValue == RhsValue; } } bool operator==(const JsonSimpleValueVariant& Lhs, const JsonSimpleValueVariant& Rhs) { if (Lhs.IsType()) { if (Rhs.IsType()) { return Lhs.Get() == Rhs.Get(); } else if (Rhs.IsType()) { if (Lhs.Get()) { return Rhs.Get().Equals(TEXT("true"), ESearchCase::IgnoreCase) || Rhs.Get().Equals(TEXT("1"), ESearchCase::IgnoreCase); } else { return Rhs.Get().Equals(TEXT("false"), ESearchCase::IgnoreCase) || Rhs.Get().Equals(TEXT("0"), ESearchCase::IgnoreCase); } } else // RhsType.IsType() { return ::Visit([&Lhs](const auto& RhsStoredNumber) { using RhsStoredNumberType = std::decay_t; if constexpr (std::is_same_v || std::is_same_v) { if (!FString::SanitizeFloat(RhsStoredNumber, 0).Contains(TEXT("."))) { const int64 RhsStoredNumberAsInt = FMath::RoundToInt64(RhsStoredNumber); if (Lhs.Get()) { return RhsStoredNumberAsInt == 1; } else { return RhsStoredNumberAsInt == 0; } } else { return false; } } else { if (Lhs.Get()) { return RhsStoredNumber == 1; } else { return RhsStoredNumber == 0; } } }, Rhs.Get()); } } else if (Lhs.IsType()) { if (Rhs.IsType()) { return Lhs.Get() == Rhs.Get(); } else // RhsType.IsType() || RhsType.IsType() { // Swapping args to avoid code duplication return Rhs == Lhs; } } else // Lhs.IsType() { if (Rhs.IsType()) { return Lhs.Get().Equals(Rhs.Get(), ESearchCase::CaseSensitive); } else if (Rhs.IsType()) { // Swapping args to avoid code duplication return Rhs == Lhs; } else // RhsType.IsType() { return Lhs.Get() == Rhs.Get(); } } }