aboutsummaryrefslogtreecommitdiff
path: root/src/zencore/compactbinary.cpp
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2023-05-02 10:01:47 +0200
committerGitHub <[email protected]>2023-05-02 10:01:47 +0200
commit075d17f8ada47e990fe94606c3d21df409223465 (patch)
treee50549b766a2f3c354798a54ff73404217b4c9af /src/zencore/compactbinary.cpp
parentfix: bundle shouldn't append content zip to zen (diff)
downloadzen-075d17f8ada47e990fe94606c3d21df409223465.tar.xz
zen-075d17f8ada47e990fe94606c3d21df409223465.zip
moved source directories into `/src` (#264)
* moved source directories into `/src` * updated bundle.lua for new `src` path * moved some docs, icon * removed old test trees
Diffstat (limited to 'src/zencore/compactbinary.cpp')
-rw-r--r--src/zencore/compactbinary.cpp2299
1 files changed, 2299 insertions, 0 deletions
diff --git a/src/zencore/compactbinary.cpp b/src/zencore/compactbinary.cpp
new file mode 100644
index 000000000..0db9f02ea
--- /dev/null
+++ b/src/zencore/compactbinary.cpp
@@ -0,0 +1,2299 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "zencore/compactbinary.h"
+
+#include <zencore/base64.h>
+#include <zencore/compactbinarybuilder.h>
+#include <zencore/compactbinaryvalidation.h>
+#include <zencore/compactbinaryvalue.h>
+#include <zencore/compress.h>
+#include <zencore/endian.h>
+#include <zencore/fmtutils.h>
+#include <zencore/stream.h>
+#include <zencore/string.h>
+#include <zencore/testing.h>
+#include <zencore/uid.h>
+
+#include <fmt/format.h>
+#include <string_view>
+
+#if ZEN_PLATFORM_WINDOWS
+# include <zencore/windows.h>
+#else
+# include <time.h>
+#endif
+
+ZEN_THIRD_PARTY_INCLUDES_START
+#include <json11.hpp>
+ZEN_THIRD_PARTY_INCLUDES_END
+
+namespace zen {
+
+const int DaysToMonth[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};
+
+double
+GetJulianDay(uint64_t Ticks)
+{
+ return (double)(1721425.5 + Ticks / TimeSpan::TicksPerDay);
+}
+
+bool
+IsLeapYear(int Year)
+{
+ if ((Year % 4) == 0)
+ {
+ return (((Year % 100) != 0) || ((Year % 400) == 0));
+ }
+
+ return false;
+}
+
+static constexpr uint64_t
+GetPlatformToDateTimeBiasInSeconds()
+{
+#if ZEN_PLATFORM_WINDOWS
+ const uint64_t PlatformEpochYear = 1601;
+#else
+ const uint64_t PlatformEpochYear = 1970;
+#endif
+ const uint64_t DateTimeEpochYear = 1;
+ return uint64_t(double(PlatformEpochYear - DateTimeEpochYear) * 365.2425) * 86400;
+}
+
+uint64_t
+DateTime::NowTicks()
+{
+ static constexpr uint64_t EpochBias = GetPlatformToDateTimeBiasInSeconds();
+
+#if ZEN_PLATFORM_WINDOWS
+ FILETIME SysTime;
+ GetSystemTimePreciseAsFileTime(&SysTime);
+ return (EpochBias * TimeSpan::TicksPerSecond) + ((uint64_t(SysTime.dwHighDateTime) << 32) | SysTime.dwLowDateTime);
+#else
+ int64_t SecondsSinceUnixEpoch = time(nullptr);
+ return (EpochBias + SecondsSinceUnixEpoch) * TimeSpan::TicksPerSecond;
+#endif
+}
+
+DateTime
+DateTime::Now()
+{
+ return DateTime{NowTicks()};
+}
+
+void
+DateTime::Set(int Year, int Month, int Day, int Hour, int Minute, int Second, int MilliSecond)
+{
+ int TotalDays = 0;
+
+ if ((Month > 2) && IsLeapYear(Year))
+ {
+ ++TotalDays;
+ }
+
+ --Year; // the current year is not a full year yet
+ --Month; // the current month is not a full month yet
+
+ TotalDays += Year * 365;
+ TotalDays += Year / 4; // leap year day every four years...
+ TotalDays -= Year / 100; // ...except every 100 years...
+ TotalDays += Year / 400; // ...but also every 400 years
+ TotalDays += DaysToMonth[Month]; // days in this year up to last month
+ TotalDays += Day - 1; // days in this month minus today
+
+ Ticks = TotalDays * TimeSpan::TicksPerDay + Hour * TimeSpan::TicksPerHour + Minute * TimeSpan::TicksPerMinute +
+ Second * TimeSpan::TicksPerSecond + MilliSecond * TimeSpan::TicksPerMillisecond;
+}
+
+int
+DateTime::GetYear() const
+{
+ int Year, Month, Day;
+ GetDate(Year, Month, Day);
+
+ return Year;
+}
+
+int
+DateTime::GetMonth() const
+{
+ int Year, Month, Day;
+ GetDate(Year, Month, Day);
+
+ return Month;
+}
+
+int
+DateTime::GetDay() const
+{
+ int Year, Month, Day;
+ GetDate(Year, Month, Day);
+
+ return Day;
+}
+
+int
+DateTime::GetHour() const
+{
+ return (int)((Ticks / TimeSpan::TicksPerHour) % 24);
+}
+
+int
+DateTime::GetHour12() const
+{
+ int Hour = GetHour();
+
+ if (Hour < 1)
+ {
+ return 12;
+ }
+
+ if (Hour > 12)
+ {
+ return (Hour - 12);
+ }
+
+ return Hour;
+}
+
+int
+DateTime::GetMinute() const
+{
+ return (int)((Ticks / TimeSpan::TicksPerMinute) % 60);
+}
+
+int
+DateTime::GetSecond() const
+{
+ return (int)((Ticks / TimeSpan::TicksPerSecond) % 60);
+}
+
+int
+DateTime::GetMillisecond() const
+{
+ return (int)((Ticks / TimeSpan::TicksPerMillisecond) % 1000);
+}
+
+void
+DateTime::GetDate(int& Year, int& Month, int& Day) const
+{
+ // Based on FORTRAN code in:
+ // Fliegel, H. F. and van Flandern, T. C.,
+ // Communications of the ACM, Vol. 11, No. 10 (October 1968).
+
+ int i, j, k, l, n;
+
+ l = int(GetJulianDay(Ticks) + 0.5) + 68569;
+ n = 4 * l / 146097;
+ l = l - (146097 * n + 3) / 4;
+ i = 4000 * (l + 1) / 1461001;
+ l = l - 1461 * i / 4 + 31;
+ j = 80 * l / 2447;
+ k = l - 2447 * j / 80;
+ l = j / 11;
+ j = j + 2 - 12 * l;
+ i = 100 * (n - 49) + i + l;
+
+ Year = i;
+ Month = j;
+ Day = k;
+}
+
+std::string
+DateTime::ToString(const char* Format) const
+{
+ ExtendableStringBuilder<32> Result;
+ int Year, Month, Day;
+
+ GetDate(Year, Month, Day);
+
+ if (Format != nullptr)
+ {
+ while (*Format != '\0')
+ {
+ if ((*Format == '%') && (*(++Format) != '\0'))
+ {
+ switch (*Format)
+ {
+ // case 'a': Result.Append(IsMorning() ? TEXT("am") : TEXT("pm")); break;
+ // case 'A': Result.Append(IsMorning() ? TEXT("AM") : TEXT("PM")); break;
+ case 'd':
+ Result.Append(fmt::format("{:02}", Day));
+ break;
+ // case 'D': Result.Appendf(TEXT("%03i"), GetDayOfYear()); break;
+ case 'm':
+ Result.Append(fmt::format("{:02}", Month));
+ break;
+ case 'y':
+ Result.Append(fmt::format("{:02}", Year % 100));
+ break;
+ case 'Y':
+ Result.Append(fmt::format("{:04}", Year));
+ break;
+ case 'h':
+ Result.Append(fmt::format("{:02}", GetHour12()));
+ break;
+ case 'H':
+ Result.Append(fmt::format("{:02}", GetHour()));
+ break;
+ case 'M':
+ Result.Append(fmt::format("{:02}", GetMinute()));
+ break;
+ case 'S':
+ Result.Append(fmt::format("{:02}", GetSecond()));
+ break;
+ case 's':
+ Result.Append(fmt::format("{:03}", GetMillisecond()));
+ break;
+ default:
+ Result.Append(*Format);
+ }
+ }
+ else
+ {
+ Result.Append(*Format);
+ }
+
+ // move to the next one
+ Format++;
+ }
+ }
+
+ return Result.ToString();
+}
+
+std::string
+DateTime::ToIso8601() const
+{
+ return ToString("%Y-%m-%dT%H:%M:%S.%sZ");
+}
+
+void
+TimeSpan::Set(int Days, int Hours, int Minutes, int Seconds, int FractionNano)
+{
+ int64_t TotalTicks = 0;
+
+ TotalTicks += Days * TicksPerDay;
+ TotalTicks += Hours * TicksPerHour;
+ TotalTicks += Minutes * TicksPerMinute;
+ TotalTicks += Seconds * TicksPerSecond;
+ TotalTicks += FractionNano / NanosecondsPerTick;
+
+ Ticks = TotalTicks;
+}
+
+std::string
+TimeSpan::ToString(const char* Format) const
+{
+ StringBuilder<128> Result;
+
+ Result.Append((int64_t(Ticks) < 0) ? '-' : '+');
+
+ while (*Format != '\0')
+ {
+ if ((*Format == '%') && (*++Format != '\0'))
+ {
+ switch (*Format)
+ {
+ case 'd':
+ Result.Append(fmt::format("{}", GetDays()));
+ break;
+ case 'D':
+ Result.Append(fmt::format("{:08}", GetDays()));
+ break;
+ case 'h':
+ Result.Append(fmt::format("{:02}", GetHours()));
+ break;
+ case 'm':
+ Result.Append(fmt::format("{:02}", GetMinutes()));
+ break;
+ case 's':
+ Result.Append(fmt::format("{:02}", GetSeconds()));
+ break;
+ case 'f':
+ Result.Append(fmt::format("{:03}", GetFractionMilli()));
+ break;
+ case 'u':
+ Result.Append(fmt::format("{:06}", GetFractionMicro()));
+ break;
+ case 't':
+ Result.Append(fmt::format("{:07}", GetFractionTicks()));
+ break;
+ case 'n':
+ Result.Append(fmt::format("{:09}", GetFractionNano()));
+ break;
+ default:
+ Result.Append(*Format);
+ }
+ }
+ else
+ {
+ Result.Append(*Format);
+ }
+
+ ++Format;
+ }
+
+ return Result.ToString();
+}
+
+std::string
+TimeSpan::ToString() const
+{
+ if (GetDays() == 0)
+ {
+ return ToString("%h:%m:%s.%f");
+ }
+
+ return ToString("%d.%h:%m:%s.%f");
+}
+
+StringBuilderBase&
+Guid::ToString(StringBuilderBase& Sb) const
+{
+ char Buf[128];
+ snprintf(Buf, sizeof Buf, "%08x-%04x-%04x-%04x-%04x%08x", A, B >> 16, B & 0xFFFF, C >> 16, C & 0xFFFF, D);
+ Sb << Buf;
+
+ return Sb;
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+namespace CompactBinaryPrivate {
+ static constexpr const uint8_t GEmptyObjectPayload[] = {uint8_t(CbFieldType::Object), 0x00};
+ static constexpr const uint8_t GEmptyArrayPayload[] = {uint8_t(CbFieldType::Array), 0x01, 0x00};
+} // namespace CompactBinaryPrivate
+
+//////////////////////////////////////////////////////////////////////////
+
+CbFieldView::CbFieldView(const void* DataPointer, CbFieldType FieldType)
+{
+ const uint8_t* Bytes = static_cast<const uint8_t*>(DataPointer);
+ const CbFieldType LocalType = CbFieldTypeOps::HasFieldType(FieldType) ? (CbFieldType(*Bytes++) | CbFieldType::HasFieldType) : FieldType;
+
+ uint32_t NameLenByteCount = 0;
+ const uint64_t NameLen64 = CbFieldTypeOps::HasFieldName(LocalType) ? ReadVarUInt(Bytes, NameLenByteCount) : 0;
+ Bytes += NameLen64 + NameLenByteCount;
+
+ Type = LocalType;
+ NameLen = uint32_t(std::clamp<uint64_t>(NameLen64, 0, ~uint32_t(0)));
+ Payload = Bytes;
+}
+
+void
+CbFieldView::IterateAttachments(std::function<void(CbFieldView)> Visitor) const
+{
+ switch (CbFieldTypeOps::GetType(Type))
+ {
+ case CbFieldType::Object:
+ case CbFieldType::UniformObject:
+ return CbObjectView::FromFieldView(*this).IterateAttachments(Visitor);
+ case CbFieldType::Array:
+ case CbFieldType::UniformArray:
+ return CbArrayView::FromFieldView(*this).IterateAttachments(Visitor);
+ case CbFieldType::ObjectAttachment:
+ case CbFieldType::BinaryAttachment:
+ return Visitor(*this);
+ default:
+ return;
+ }
+}
+
+CbObjectView
+CbFieldView::AsObjectView()
+{
+ if (CbFieldTypeOps::IsObject(Type))
+ {
+ Error = CbFieldError::None;
+ return CbObjectView::FromFieldView(*this);
+ }
+ else
+ {
+ Error = CbFieldError::TypeError;
+ return CbObjectView();
+ }
+}
+
+CbArrayView
+CbFieldView::AsArrayView()
+{
+ if (CbFieldTypeOps::IsArray(Type))
+ {
+ Error = CbFieldError::None;
+ return CbArrayView::FromFieldView(*this);
+ }
+ else
+ {
+ Error = CbFieldError::TypeError;
+ return CbArrayView();
+ }
+}
+
+MemoryView
+CbFieldView::AsBinaryView(const MemoryView Default)
+{
+ if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsBinary(Accessor.GetType()))
+ {
+ Error = CbFieldError::None;
+ return Accessor.AsBinary();
+ }
+ else
+ {
+ Error = CbFieldError::TypeError;
+ return Default;
+ }
+}
+
+std::string_view
+CbFieldView::AsString(const std::string_view Default)
+{
+ if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsString(Accessor.GetType()))
+ {
+ Error = CbFieldError::None;
+ return Accessor.AsString();
+ }
+ else
+ {
+ Error = CbFieldError::TypeError;
+ return Default;
+ }
+}
+
+std::u8string_view
+CbFieldView::AsU8String(const std::u8string_view Default)
+{
+ if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsString(Accessor.GetType()))
+ {
+ Error = CbFieldError::None;
+ return Accessor.AsU8String();
+ }
+ else
+ {
+ Error = CbFieldError::TypeError;
+ return Default;
+ }
+}
+
+uint64_t
+CbFieldView::AsInteger(const uint64_t Default, const CompactBinaryPrivate::IntegerParams Params)
+{
+ if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsInteger(Accessor.GetType()))
+ {
+ return Accessor.AsInteger(Params, &Error, Default);
+ }
+ else
+ {
+ Error = CbFieldError::TypeError;
+ return Default;
+ }
+}
+
+float
+CbFieldView::AsFloat(const float Default)
+{
+ switch (CbValue Accessor = GetValue(); Accessor.GetType())
+ {
+ case CbFieldType::IntegerPositive:
+ case CbFieldType::IntegerNegative:
+ {
+ const uint64_t IsNegative = uint8_t(Accessor.GetType()) & 1;
+ constexpr uint64_t OutOfRangeMask = ~((uint64_t(1) << /*FLT_MANT_DIG*/ 24) - 1);
+
+ uint32_t MagnitudeByteCount;
+ const int64_t Magnitude = ReadVarUInt(Accessor.GetData(), MagnitudeByteCount) + IsNegative;
+ const uint64_t IsInRange = !(Magnitude & OutOfRangeMask);
+ Error = IsInRange ? CbFieldError::None : CbFieldError::RangeError;
+ return IsInRange ? float(IsNegative ? -Magnitude : Magnitude) : Default;
+ }
+ case CbFieldType::Float32:
+ {
+ Error = CbFieldError::None;
+ return Accessor.AsFloat32();
+ }
+ case CbFieldType::Float64:
+ Error = CbFieldError::RangeError;
+ return Default;
+ default:
+ Error = CbFieldError::TypeError;
+ return Default;
+ }
+}
+
+double
+CbFieldView::AsDouble(const double Default)
+{
+ switch (CbValue Accessor = GetValue(); Accessor.GetType())
+ {
+ case CbFieldType::IntegerPositive:
+ case CbFieldType::IntegerNegative:
+ {
+ const uint64_t IsNegative = uint8_t(Accessor.GetType()) & 1;
+ constexpr uint64_t OutOfRangeMask = ~((uint64_t(1) << /*DBL_MANT_DIG*/ 53) - 1);
+
+ uint32_t MagnitudeByteCount;
+ const int64_t Magnitude = ReadVarUInt(Accessor.GetData(), MagnitudeByteCount) + IsNegative;
+ const uint64_t IsInRange = !(Magnitude & OutOfRangeMask);
+ Error = IsInRange ? CbFieldError::None : CbFieldError::RangeError;
+ return IsInRange ? double(IsNegative ? -Magnitude : Magnitude) : Default;
+ }
+ case CbFieldType::Float32:
+ {
+ Error = CbFieldError::None;
+ return Accessor.AsFloat32();
+ }
+ case CbFieldType::Float64:
+ {
+ Error = CbFieldError::None;
+ return Accessor.AsFloat64();
+ }
+ default:
+ Error = CbFieldError::TypeError;
+ return Default;
+ }
+}
+
+bool
+CbFieldView::AsBool(const bool bDefault)
+{
+ CbValue Accessor = GetValue();
+ const bool IsBool = CbFieldTypeOps::IsBool(Accessor.GetType());
+ Error = IsBool ? CbFieldError::None : CbFieldError::TypeError;
+ return (uint8_t(IsBool) & Accessor.AsBool()) | ((!IsBool) & bDefault);
+}
+
+IoHash
+CbFieldView::AsObjectAttachment(const IoHash& Default)
+{
+ if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsObjectAttachment(Accessor.GetType()))
+ {
+ Error = CbFieldError::None;
+ return Accessor.AsObjectAttachment();
+ }
+ else
+ {
+ Error = CbFieldError::TypeError;
+ return Default;
+ }
+}
+
+IoHash
+CbFieldView::AsBinaryAttachment(const IoHash& Default)
+{
+ if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsBinaryAttachment(Accessor.GetType()))
+ {
+ Error = CbFieldError::None;
+ return Accessor.AsBinaryAttachment();
+ }
+ else
+ {
+ Error = CbFieldError::TypeError;
+ return Default;
+ }
+}
+
+IoHash
+CbFieldView::AsAttachment(const IoHash& Default)
+{
+ if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsAttachment(Accessor.GetType()))
+ {
+ Error = CbFieldError::None;
+ return Accessor.AsAttachment();
+ }
+ else
+ {
+ Error = CbFieldError::TypeError;
+ return Default;
+ }
+}
+
+IoHash
+CbFieldView::AsHash(const IoHash& Default)
+{
+ if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsHash(Accessor.GetType()))
+ {
+ Error = CbFieldError::None;
+ return Accessor.AsHash();
+ }
+ else
+ {
+ Error = CbFieldError::TypeError;
+ return Default;
+ }
+}
+
+Guid
+CbFieldView::AsUuid()
+{
+ return AsUuid(Guid());
+}
+
+Guid
+CbFieldView::AsUuid(const Guid& Default)
+{
+ if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsUuid(Accessor.GetType()))
+ {
+ Error = CbFieldError::None;
+ return Accessor.AsUuid();
+ }
+ else
+ {
+ Error = CbFieldError::TypeError;
+ return Default;
+ }
+}
+
+Oid
+CbFieldView::AsObjectId()
+{
+ return AsObjectId(Oid());
+}
+
+Oid
+CbFieldView::AsObjectId(const Oid& Default)
+{
+ if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsObjectId(Accessor.GetType()))
+ {
+ Error = CbFieldError::None;
+ return Accessor.AsObjectId();
+ }
+ else
+ {
+ Error = CbFieldError::TypeError;
+ return Default;
+ }
+}
+
+CbCustomById
+CbFieldView::AsCustomById(CbCustomById Default)
+{
+ if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsCustomById(Accessor.GetType()))
+ {
+ Error = CbFieldError::None;
+ return Accessor.AsCustomById();
+ }
+ else
+ {
+ Error = CbFieldError::TypeError;
+ return Default;
+ }
+}
+
+CbCustomByName
+CbFieldView::AsCustomByName(CbCustomByName Default)
+{
+ if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsCustomByName(Accessor.GetType()))
+ {
+ Error = CbFieldError::None;
+ return Accessor.AsCustomByName();
+ }
+ else
+ {
+ Error = CbFieldError::TypeError;
+ return Default;
+ }
+}
+
+int64_t
+CbFieldView::AsDateTimeTicks(const int64_t Default)
+{
+ if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsDateTime(Accessor.GetType()))
+ {
+ Error = CbFieldError::None;
+ return Accessor.AsDateTimeTicks();
+ }
+ else
+ {
+ Error = CbFieldError::TypeError;
+ return Default;
+ }
+}
+
+DateTime
+CbFieldView::AsDateTime()
+{
+ return DateTime(AsDateTimeTicks(0));
+}
+
+DateTime
+CbFieldView::AsDateTime(DateTime Default)
+{
+ return DateTime(AsDateTimeTicks(Default.GetTicks()));
+}
+
+int64_t
+CbFieldView::AsTimeSpanTicks(const int64_t Default)
+{
+ if (CbValue Accessor = GetValue(); CbFieldTypeOps::IsTimeSpan(Accessor.GetType()))
+ {
+ Error = CbFieldError::None;
+ return Accessor.AsTimeSpanTicks();
+ }
+ else
+ {
+ Error = CbFieldError::TypeError;
+ return Default;
+ }
+}
+
+TimeSpan
+CbFieldView::AsTimeSpan()
+{
+ return TimeSpan(AsTimeSpanTicks(0));
+}
+
+TimeSpan
+CbFieldView::AsTimeSpan(TimeSpan Default)
+{
+ return TimeSpan(AsTimeSpanTicks(Default.GetTicks()));
+}
+
+uint64_t
+CbFieldView::GetSize() const
+{
+ return sizeof(CbFieldType) + GetViewNoType().GetSize();
+}
+
+uint64_t
+CbFieldView::GetPayloadSize() const
+{
+ switch (CbFieldTypeOps::GetType(Type))
+ {
+ case CbFieldType::None:
+ case CbFieldType::Null:
+ return 0;
+ case CbFieldType::Object:
+ case CbFieldType::UniformObject:
+ case CbFieldType::Array:
+ case CbFieldType::UniformArray:
+ case CbFieldType::Binary:
+ case CbFieldType::String:
+ {
+ uint32_t PayloadSizeByteCount;
+ const uint64_t PayloadSize = ReadVarUInt(Payload, PayloadSizeByteCount);
+ return PayloadSize + PayloadSizeByteCount;
+ }
+ case CbFieldType::IntegerPositive:
+ case CbFieldType::IntegerNegative:
+ return MeasureVarUInt(Payload);
+ case CbFieldType::Float32:
+ return 4;
+ case CbFieldType::Float64:
+ return 8;
+ case CbFieldType::BoolFalse:
+ case CbFieldType::BoolTrue:
+ return 0;
+ case CbFieldType::ObjectAttachment:
+ case CbFieldType::BinaryAttachment:
+ case CbFieldType::Hash:
+ return 20;
+ case CbFieldType::Uuid:
+ return 16;
+ case CbFieldType::ObjectId:
+ return 12;
+ case CbFieldType::DateTime:
+ case CbFieldType::TimeSpan:
+ return 8;
+ default:
+ return 0;
+ }
+}
+
+IoHash
+CbFieldView::GetHash() const
+{
+ IoHashStream HashStream;
+ GetHash(HashStream);
+ return HashStream.GetHash();
+}
+
+void
+CbFieldView::GetHash(IoHashStream& Hash) const
+{
+ const CbFieldType SerializedType = CbFieldTypeOps::GetSerializedType(Type);
+ Hash.Append(&SerializedType, sizeof(SerializedType));
+ auto View = GetViewNoType();
+ Hash.Append(View.GetData(), View.GetSize());
+}
+
+bool
+CbFieldView::Equals(const CbFieldView& Other) const
+{
+ return CbFieldTypeOps::GetSerializedType(Type) == CbFieldTypeOps::GetSerializedType(Other.Type) &&
+ GetViewNoType().EqualBytes(Other.GetViewNoType());
+}
+
+void
+CbFieldView::CopyTo(MutableMemoryView Buffer) const
+{
+ const MemoryView Source = GetViewNoType();
+ ZEN_ASSERT(Buffer.GetSize() == sizeof(CbFieldType) + Source.GetSize());
+ // TEXT("A buffer of %" UINT64_FMT " bytes was provided when %" UINT64_FMT " bytes are required"),
+ // Buffer.GetSize(),
+ // sizeof(CbFieldType) + Source.GetSize());
+ *static_cast<CbFieldType*>(Buffer.GetData()) = CbFieldTypeOps::GetSerializedType(Type);
+ Buffer.RightChopInline(sizeof(CbFieldType));
+ memcpy(Buffer.GetData(), Source.GetData(), Source.GetSize());
+}
+
+void
+CbFieldView::CopyTo(BinaryWriter& Ar) const
+{
+ const MemoryView SourceView = GetViewNoType();
+ CbFieldType SerializedType = CbFieldTypeOps::GetSerializedType(Type);
+ const MemoryView TypeView(reinterpret_cast<const uint8_t*>(&SerializedType), sizeof(SerializedType));
+ Ar.Write({TypeView, SourceView});
+}
+
+MemoryView
+CbFieldView::GetView() const
+{
+ const uint32_t TypeSize = CbFieldTypeOps::HasFieldType(Type) ? sizeof(CbFieldType) : 0;
+ const uint32_t NameSize = CbFieldTypeOps::HasFieldName(Type) ? NameLen + MeasureVarUInt(NameLen) : 0;
+ const uint64_t PayloadSize = GetPayloadSize();
+ return MemoryView(static_cast<const uint8_t*>(Payload) - TypeSize - NameSize, TypeSize + NameSize + PayloadSize);
+}
+
+MemoryView
+CbFieldView::GetViewNoType() const
+{
+ const uint32_t NameSize = CbFieldTypeOps::HasFieldName(Type) ? NameLen + MeasureVarUInt(NameLen) : 0;
+ const uint64_t PayloadSize = GetPayloadSize();
+ return MemoryView(static_cast<const uint8_t*>(Payload) - NameSize, NameSize + PayloadSize);
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+CbArrayView::CbArrayView() : CbFieldView(CompactBinaryPrivate::GEmptyArrayPayload)
+{
+}
+
+uint64_t
+CbArrayView::Num() const
+{
+ const uint8_t* PayloadBytes = static_cast<const uint8_t*>(GetPayload());
+ PayloadBytes += MeasureVarUInt(PayloadBytes);
+ uint32_t NumByteCount;
+ return ReadVarUInt(PayloadBytes, NumByteCount);
+}
+
+CbFieldViewIterator
+CbArrayView::CreateViewIterator() const
+{
+ const uint8_t* PayloadBytes = static_cast<const uint8_t*>(GetPayload());
+ uint32_t PayloadSizeByteCount;
+ const uint64_t PayloadSize = ReadVarUInt(PayloadBytes, PayloadSizeByteCount);
+ PayloadBytes += PayloadSizeByteCount;
+ const uint64_t NumByteCount = MeasureVarUInt(PayloadBytes);
+ if (PayloadSize > NumByteCount)
+ {
+ const void* const PayloadEnd = PayloadBytes + PayloadSize;
+ PayloadBytes += NumByteCount;
+ const CbFieldType UniformType =
+ CbFieldTypeOps::GetType(GetType()) == CbFieldType::UniformArray ? CbFieldType(*PayloadBytes++) : CbFieldType::HasFieldType;
+ return CbFieldViewIterator::MakeRange(MemoryView(PayloadBytes, PayloadEnd), UniformType);
+ }
+ return CbFieldViewIterator();
+}
+
+void
+CbArrayView::VisitFields(ICbVisitor&)
+{
+}
+
+uint64_t
+CbArrayView::GetSize() const
+{
+ return sizeof(CbFieldType) + GetPayloadSize();
+}
+
+IoHash
+CbArrayView::GetHash() const
+{
+ IoHashStream Hash;
+ GetHash(Hash);
+ return Hash.GetHash();
+}
+
+void
+CbArrayView::GetHash(IoHashStream& HashStream) const
+{
+ const CbFieldType SerializedType = CbFieldTypeOps::GetType(GetType());
+ HashStream.Append(&SerializedType, sizeof(SerializedType));
+ auto _ = GetPayloadView();
+ HashStream.Append(_.GetData(), _.GetSize());
+}
+
+bool
+CbArrayView::Equals(const CbArrayView& Other) const
+{
+ return CbFieldTypeOps::GetType(GetType()) == CbFieldTypeOps::GetType(Other.GetType()) &&
+ GetPayloadView().EqualBytes(Other.GetPayloadView());
+}
+
+void
+CbArrayView::CopyTo(MutableMemoryView Buffer) const
+{
+ const MemoryView Source = GetPayloadView();
+ ZEN_ASSERT(Buffer.GetSize() == sizeof(CbFieldType) + Source.GetSize());
+ // TEXT("Buffer is %" UINT64_FMT " bytes but %" UINT64_FMT " is required."),
+ // Buffer.GetSize(),
+ // sizeof(CbFieldType) + Source.GetSize());
+
+ *static_cast<CbFieldType*>(Buffer.GetData()) = CbFieldTypeOps::GetType(GetType());
+ Buffer.RightChopInline(sizeof(CbFieldType));
+ memcpy(Buffer.GetData(), Source.GetData(), Source.GetSize());
+}
+
+void
+CbArrayView::CopyTo(BinaryWriter& Ar) const
+{
+ const MemoryView SourceView = GetPayloadView();
+ CbFieldType SerializedType = CbFieldTypeOps::GetSerializedType(GetType());
+ const MemoryView TypeView(reinterpret_cast<const uint8_t*>(&SerializedType), sizeof(SerializedType));
+ Ar.Write({TypeView, SourceView});
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+CbObjectView::CbObjectView() : CbFieldView(CompactBinaryPrivate::GEmptyObjectPayload)
+{
+}
+
+CbFieldViewIterator
+CbObjectView::CreateViewIterator() const
+{
+ const uint8_t* PayloadBytes = static_cast<const uint8_t*>(GetPayload());
+ uint32_t PayloadSizeByteCount;
+ const uint64_t PayloadSize = ReadVarUInt(PayloadBytes, PayloadSizeByteCount);
+
+ PayloadBytes += PayloadSizeByteCount;
+
+ if (PayloadSize)
+ {
+ const void* const PayloadEnd = PayloadBytes + PayloadSize;
+ const CbFieldType UniformType =
+ CbFieldTypeOps::GetType(GetType()) == CbFieldType::UniformObject ? CbFieldType(*PayloadBytes++) : CbFieldType::HasFieldType;
+ return CbFieldViewIterator::MakeRange(MemoryView(PayloadBytes, PayloadEnd), UniformType);
+ }
+
+ return CbFieldViewIterator();
+}
+
+void
+CbObjectView::VisitFields(ICbVisitor&)
+{
+}
+
+CbFieldView
+CbObjectView::FindView(const std::string_view Name) const
+{
+ for (const CbFieldView& Field : *this)
+ {
+ if (Name == Field.GetName())
+ {
+ return Field;
+ }
+ }
+ return CbFieldView();
+}
+
+CbFieldView
+CbObjectView::FindViewIgnoreCase(const std::string_view Name) const
+{
+ for (const CbFieldView& Field : *this)
+ {
+ if (Name == Field.GetName())
+ {
+ return Field;
+ }
+ }
+ return CbFieldView();
+}
+
+CbObjectView::operator bool() const
+{
+ return GetSize() > sizeof(CompactBinaryPrivate::GEmptyObjectPayload);
+}
+
+uint64_t
+CbObjectView::GetSize() const
+{
+ return sizeof(CbFieldType) + GetPayloadSize();
+}
+
+IoHash
+CbObjectView::GetHash() const
+{
+ IoHashStream Hash;
+ GetHash(Hash);
+ return Hash.GetHash();
+}
+
+void
+CbObjectView::GetHash(IoHashStream& HashStream) const
+{
+ const CbFieldType SerializedType = CbFieldTypeOps::GetType(GetType());
+ HashStream.Append(&SerializedType, sizeof(SerializedType));
+ HashStream.Append(GetPayloadView());
+}
+
+bool
+CbObjectView::Equals(const CbObjectView& Other) const
+{
+ return CbFieldTypeOps::GetType(GetType()) == CbFieldTypeOps::GetType(Other.GetType()) &&
+ GetPayloadView().EqualBytes(Other.GetPayloadView());
+}
+
+void
+CbObjectView::CopyTo(MutableMemoryView Buffer) const
+{
+ const MemoryView Source = GetPayloadView();
+ ZEN_ASSERT(Buffer.GetSize() == (sizeof(CbFieldType) + Source.GetSize()));
+ // TEXT("Buffer is %" UINT64_FMT " bytes but %" UINT64_FMT " is required."),
+ // Buffer.GetSize(),
+ // sizeof(CbFieldType) + Source.GetSize());
+ *static_cast<CbFieldType*>(Buffer.GetData()) = CbFieldTypeOps::GetType(GetType());
+ Buffer.RightChopInline(sizeof(CbFieldType));
+ memcpy(Buffer.GetData(), Source.GetData(), Source.GetSize());
+}
+
+void
+CbObjectView::CopyTo(BinaryWriter& Ar) const
+{
+ const MemoryView SourceView = GetPayloadView();
+ CbFieldType SerializedType = CbFieldTypeOps::GetSerializedType(GetType());
+ const MemoryView TypeView(reinterpret_cast<const uint8_t*>(&SerializedType), sizeof(SerializedType));
+ Ar.Write({TypeView, SourceView});
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+template<typename FieldType>
+uint64_t
+TCbFieldIterator<FieldType>::GetRangeSize() const
+{
+ MemoryView View;
+ if (TryGetSerializedRangeView(View))
+ {
+ return View.GetSize();
+ }
+ else
+ {
+ uint64_t Size = 0;
+ for (CbFieldViewIterator It(*this); It; ++It)
+ {
+ Size += It.GetSize();
+ }
+ return Size;
+ }
+}
+
+template<typename FieldType>
+IoHash
+TCbFieldIterator<FieldType>::GetRangeHash() const
+{
+ IoHashStream Hash;
+ GetRangeHash(Hash);
+ return IoHash(Hash.GetHash());
+}
+
+template<typename FieldType>
+void
+TCbFieldIterator<FieldType>::GetRangeHash(IoHashStream& Hash) const
+{
+ MemoryView View;
+ if (TryGetSerializedRangeView(View))
+ {
+ Hash.Append(View.GetData(), View.GetSize());
+ }
+ else
+ {
+ for (CbFieldViewIterator It(*this); It; ++It)
+ {
+ It.GetHash(Hash);
+ }
+ }
+}
+
+template<typename FieldType>
+void
+TCbFieldIterator<FieldType>::CopyRangeTo(MutableMemoryView InBuffer) const
+{
+ MemoryView Source;
+ if (TryGetSerializedRangeView(Source))
+ {
+ ZEN_ASSERT(InBuffer.GetSize() == Source.GetSize());
+ // TEXT("Buffer is %" UINT64_FMT " bytes but %" UINT64_FMT " is required."),
+ // InBuffer.GetSize(),
+ // Source.GetSize());
+ memcpy(InBuffer.GetData(), Source.GetData(), Source.GetSize());
+ }
+ else
+ {
+ for (CbFieldViewIterator It(*this); It; ++It)
+ {
+ const uint64_t Size = It.GetSize();
+ It.CopyTo(InBuffer.Left(Size));
+ InBuffer.RightChopInline(Size);
+ }
+ }
+}
+
+template class TCbFieldIterator<CbFieldView>;
+template class TCbFieldIterator<CbField>;
+
+template<typename FieldType>
+void
+TCbFieldIterator<FieldType>::IterateRangeAttachments(std::function<void(CbFieldView)> Visitor) const
+{
+ if (CbFieldTypeOps::HasFieldType(FieldType::GetType()))
+ {
+ // Always iterate over non-uniform ranges because we do not know if they contain an attachment.
+ for (CbFieldViewIterator It(*this); It; ++It)
+ {
+ if (CbFieldTypeOps::MayContainAttachments(It.GetType()))
+ {
+ It.IterateAttachments(Visitor);
+ }
+ }
+ }
+ else
+ {
+ // Only iterate over uniform ranges if the uniform type may contain an attachment.
+ if (CbFieldTypeOps::MayContainAttachments(FieldType::GetType()))
+ {
+ for (CbFieldViewIterator It(*this); It; ++It)
+ {
+ It.IterateAttachments(Visitor);
+ }
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+CbFieldIterator
+CbFieldIterator::CloneRange(const CbFieldViewIterator& It)
+{
+ MemoryView View;
+ if (It.TryGetSerializedRangeView(View))
+ {
+ return MakeRange(SharedBuffer::Clone(View));
+ }
+ else
+ {
+ UniqueBuffer Buffer = UniqueBuffer::Alloc(It.GetRangeSize());
+ It.CopyRangeTo(MutableMemoryView(Buffer.GetData(), Buffer.GetSize()));
+ return MakeRange(SharedBuffer(std::move(Buffer)));
+ }
+}
+
+SharedBuffer
+CbFieldIterator::GetRangeBuffer() const
+{
+ const MemoryView RangeView = GetRangeView();
+ const SharedBuffer& OuterBuffer = GetOuterBuffer();
+ return OuterBuffer.GetView() == RangeView ? OuterBuffer : SharedBuffer::MakeView(RangeView, OuterBuffer);
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+uint64_t
+MeasureCompactBinary(MemoryView View, CbFieldType Type)
+{
+ uint64_t Size;
+ return TryMeasureCompactBinary(View, Type, Size, Type) ? Size : 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool
+TryMeasureCompactBinary(MemoryView View, CbFieldType& OutType, uint64_t& OutSize, CbFieldType Type)
+{
+ uint64_t Size = 0;
+
+ if (CbFieldTypeOps::HasFieldType(Type))
+ {
+ if (View.GetSize() == 0)
+ {
+ OutType = CbFieldType::None;
+ OutSize = 1;
+ return false;
+ }
+
+ Type = *static_cast<const CbFieldType*>(View.GetData());
+ View.RightChopInline(1);
+ Size += 1;
+ }
+
+ bool bDynamicSize = false;
+ uint64_t FixedSize = 0;
+ switch (CbFieldTypeOps::GetType(Type))
+ {
+ case CbFieldType::Null:
+ break;
+ case CbFieldType::Object:
+ case CbFieldType::UniformObject:
+ case CbFieldType::Array:
+ case CbFieldType::UniformArray:
+ case CbFieldType::Binary:
+ case CbFieldType::String:
+ case CbFieldType::IntegerPositive:
+ case CbFieldType::IntegerNegative:
+ bDynamicSize = true;
+ break;
+ case CbFieldType::Float32:
+ FixedSize = 4;
+ break;
+ case CbFieldType::Float64:
+ FixedSize = 8;
+ break;
+ case CbFieldType::BoolFalse:
+ case CbFieldType::BoolTrue:
+ break;
+ case CbFieldType::ObjectAttachment:
+ case CbFieldType::BinaryAttachment:
+ case CbFieldType::Hash:
+ FixedSize = 20;
+ break;
+ case CbFieldType::Uuid:
+ FixedSize = 16;
+ break;
+ case CbFieldType::ObjectId:
+ FixedSize = 12;
+ break;
+ case CbFieldType::DateTime:
+ case CbFieldType::TimeSpan:
+ FixedSize = 8;
+ break;
+ case CbFieldType::None:
+ default:
+ OutType = CbFieldType::None;
+ OutSize = 0;
+ return false;
+ }
+
+ OutType = Type;
+
+ if (CbFieldTypeOps::HasFieldName(Type))
+ {
+ if (View.GetSize() == 0)
+ {
+ OutSize = Size + 1;
+ return false;
+ }
+
+ uint32_t NameLenByteCount = MeasureVarUInt(View.GetData());
+ if (View.GetSize() < NameLenByteCount)
+ {
+ OutSize = Size + NameLenByteCount;
+ return false;
+ }
+
+ const uint64_t NameLen = ReadVarUInt(View.GetData(), NameLenByteCount);
+ const uint64_t NameSize = NameLen + NameLenByteCount;
+
+ if (bDynamicSize && View.GetSize() < NameSize)
+ {
+ OutSize = Size + NameSize;
+ return false;
+ }
+
+ View.RightChopInline(NameSize);
+ Size += NameSize;
+ }
+
+ switch (CbFieldTypeOps::GetType(Type))
+ {
+ case CbFieldType::Object:
+ case CbFieldType::UniformObject:
+ case CbFieldType::Array:
+ case CbFieldType::UniformArray:
+ case CbFieldType::Binary:
+ case CbFieldType::String:
+ if (View.GetSize() == 0)
+ {
+ OutSize = Size + 1;
+ return false;
+ }
+ else
+ {
+ uint32_t PayloadSizeByteCount = MeasureVarUInt(View.GetData());
+ if (View.GetSize() < PayloadSizeByteCount)
+ {
+ OutSize = Size + PayloadSizeByteCount;
+ return false;
+ }
+ const uint64_t PayloadSize = ReadVarUInt(View.GetData(), PayloadSizeByteCount);
+ OutSize = Size + PayloadSize + PayloadSizeByteCount;
+ }
+ return true;
+
+ case CbFieldType::IntegerPositive:
+ case CbFieldType::IntegerNegative:
+ if (View.GetSize() == 0)
+ {
+ OutSize = Size + 1;
+ return false;
+ }
+ OutSize = Size + MeasureVarUInt(View.GetData());
+ return true;
+
+ default:
+ OutSize = Size + FixedSize;
+ return true;
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+CbField
+LoadCompactBinary(BinaryReader& Ar, BufferAllocator Allocator)
+{
+ std::vector<uint8_t> HeaderBytes;
+ CbFieldType FieldType;
+ uint64_t FieldSize = 1;
+
+ for (const int64_t StartPos = Ar.CurrentOffset(); FieldSize > 0;)
+ {
+ // Read in small increments until the total field size is known, to avoid reading too far.
+ const int32_t ReadSize = int32_t(FieldSize - HeaderBytes.size());
+ if (Ar.CurrentOffset() + ReadSize > Ar.GetSize())
+ {
+ break;
+ }
+
+ const size_t ReadOffset = HeaderBytes.size();
+ HeaderBytes.resize(ReadOffset + ReadSize);
+
+ Ar.Read(HeaderBytes.data() + ReadOffset, ReadSize);
+ if (TryMeasureCompactBinary(MakeMemoryView(HeaderBytes), FieldType, FieldSize))
+ {
+ if (FieldSize <= uint64_t(Ar.Size() - StartPos))
+ {
+ UniqueBuffer Buffer = Allocator(FieldSize);
+ ZEN_ASSERT(Buffer.GetSize() == FieldSize);
+ MutableMemoryView View = Buffer.GetMutableView();
+ memcpy(View.GetData(), HeaderBytes.data(), HeaderBytes.size());
+ View.RightChopInline(HeaderBytes.size());
+ if (!View.IsEmpty())
+ {
+ // Read the remainder of the field.
+ Ar.Read(View.GetData(), View.GetSize());
+ }
+ if (ValidateCompactBinary(Buffer, CbValidateMode::Default) == CbValidateError::None)
+ {
+ return CbField(SharedBuffer(std::move(Buffer)));
+ }
+ }
+ break;
+ }
+ }
+ return CbField();
+}
+
+CbObject
+LoadCompactBinaryObject(IoBuffer&& Payload)
+{
+ return CbObject{SharedBuffer(std::move(Payload))};
+}
+
+CbObject
+LoadCompactBinaryObject(const IoBuffer& Payload)
+{
+ return CbObject{SharedBuffer(Payload)};
+}
+
+CbObject
+LoadCompactBinaryObject(CompressedBuffer&& Payload)
+{
+ return CbObject{SharedBuffer(Payload.DecompressToComposite().Flatten())};
+}
+
+CbObject
+LoadCompactBinaryObject(const CompressedBuffer& Payload)
+{
+ return CbObject{SharedBuffer(Payload.DecompressToComposite().Flatten())};
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+void
+SaveCompactBinary(BinaryWriter& Ar, const CbFieldView& Field)
+{
+ Field.CopyTo(Ar);
+}
+
+void
+SaveCompactBinary(BinaryWriter& Ar, const CbArrayView& Array)
+{
+ Array.CopyTo(Ar);
+}
+
+void
+SaveCompactBinary(BinaryWriter& Ar, const CbObjectView& Object)
+{
+ Object.CopyTo(Ar);
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+class CbJsonWriter
+{
+public:
+ explicit CbJsonWriter(StringBuilderBase& InBuilder) : Builder(InBuilder) { NewLineAndIndent << LINE_TERMINATOR_ANSI; }
+
+ void WriteField(CbFieldView Field)
+ {
+ using namespace std::literals;
+
+ WriteOptionalComma();
+ WriteOptionalNewLine();
+
+ if (std::u8string_view Name = Field.GetU8Name(); !Name.empty())
+ {
+ AppendQuotedString(Name);
+ Builder << ": "sv;
+ }
+
+ switch (CbValue Accessor = Field.GetValue(); Accessor.GetType())
+ {
+ case CbFieldType::Null:
+ Builder << "null"sv;
+ break;
+ case CbFieldType::Object:
+ case CbFieldType::UniformObject:
+ {
+ Builder << '{';
+ NewLineAndIndent << '\t';
+ NeedsNewLine = true;
+ for (CbFieldView It : Field)
+ {
+ WriteField(It);
+ }
+ NewLineAndIndent.RemoveSuffix(1);
+ if (NeedsComma)
+ {
+ WriteOptionalNewLine();
+ }
+ Builder << '}';
+ }
+ break;
+ case CbFieldType::Array:
+ case CbFieldType::UniformArray:
+ {
+ Builder << '[';
+ NewLineAndIndent << '\t';
+ NeedsNewLine = true;
+ for (CbFieldView It : Field)
+ {
+ WriteField(It);
+ }
+ NewLineAndIndent.RemoveSuffix(1);
+ if (NeedsComma)
+ {
+ WriteOptionalNewLine();
+ }
+ Builder << ']';
+ }
+ break;
+ case CbFieldType::Binary:
+ AppendBase64String(Accessor.AsBinary());
+ break;
+ case CbFieldType::String:
+ AppendQuotedString(Accessor.AsU8String());
+ break;
+ case CbFieldType::IntegerPositive:
+ Builder << Accessor.AsIntegerPositive();
+ break;
+ case CbFieldType::IntegerNegative:
+ Builder << Accessor.AsIntegerNegative();
+ break;
+ case CbFieldType::Float32:
+ {
+ const float Value = Accessor.AsFloat32();
+ if (std::isfinite(Value))
+ {
+ Builder.Append(fmt::format("{:.9g}", Value));
+ }
+ else
+ {
+ Builder << "null"sv;
+ }
+ }
+ break;
+ case CbFieldType::Float64:
+ {
+ const double Value = Accessor.AsFloat64();
+ if (std::isfinite(Value))
+ {
+ Builder.Append(fmt::format("{:.17g}", Value));
+ }
+ else
+ {
+ Builder << "null"sv;
+ }
+ }
+ break;
+ case CbFieldType::BoolFalse:
+ Builder << "false"sv;
+ break;
+ case CbFieldType::BoolTrue:
+ Builder << "true"sv;
+ break;
+ case CbFieldType::ObjectAttachment:
+ case CbFieldType::BinaryAttachment:
+ {
+ Builder << '"';
+ Accessor.AsAttachment().ToHexString(Builder);
+ Builder << '"';
+ }
+ break;
+ case CbFieldType::Hash:
+ {
+ Builder << '"';
+ Accessor.AsHash().ToHexString(Builder);
+ Builder << '"';
+ }
+ break;
+ case CbFieldType::Uuid:
+ {
+ Builder << '"';
+ Accessor.AsUuid().ToString(Builder);
+ Builder << '"';
+ }
+ break;
+ case CbFieldType::DateTime:
+ Builder << '"' << DateTime(Accessor.AsDateTimeTicks()).ToIso8601() << '"';
+ break;
+ case CbFieldType::TimeSpan:
+ {
+ const TimeSpan Span(Accessor.AsTimeSpanTicks());
+ if (Span.GetDays() == 0)
+ {
+ Builder << '"' << Span.ToString("%h:%m:%s.%n") << '"';
+ }
+ else
+ {
+ Builder << '"' << Span.ToString("%d.%h:%m:%s.%n") << '"';
+ }
+ break;
+ }
+ case CbFieldType::ObjectId:
+ Builder << '"';
+ Accessor.AsObjectId().ToString(Builder);
+ Builder << '"';
+ break;
+ case CbFieldType::CustomById:
+ {
+ CbCustomById Custom = Accessor.AsCustomById();
+ Builder << "{ \"Id\": ";
+ Builder << Custom.Id;
+ Builder << ", \"Data\": ";
+ AppendBase64String(Custom.Data);
+ Builder << " }";
+ break;
+ }
+ case CbFieldType::CustomByName:
+ {
+ CbCustomByName Custom = Accessor.AsCustomByName();
+ Builder << "{ \"Name\": ";
+ AppendQuotedString(Custom.Name);
+ Builder << ", \"Data\": ";
+ AppendBase64String(Custom.Data);
+ Builder << " }";
+ break;
+ }
+ default:
+ ZEN_ASSERT(false);
+ break;
+ }
+
+ NeedsComma = true;
+ NeedsNewLine = true;
+ }
+
+private:
+ void WriteOptionalComma()
+ {
+ if (NeedsComma)
+ {
+ NeedsComma = false;
+ Builder << ',';
+ }
+ }
+
+ void WriteOptionalNewLine()
+ {
+ if (NeedsNewLine)
+ {
+ NeedsNewLine = false;
+ Builder << NewLineAndIndent;
+ }
+ }
+
+ void AppendQuotedString(std::u8string_view Value)
+ {
+ using namespace std::literals;
+
+ const AsciiSet EscapeSet(
+ "\\\"\b\f\n\r\t"
+ "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
+ "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f");
+
+ Builder << '\"';
+ while (!Value.empty())
+ {
+ std::u8string_view Verbatim = AsciiSet::FindPrefixWithout(Value, EscapeSet);
+ Builder << Verbatim;
+
+ Value = Value.substr(Verbatim.size());
+
+ std::u8string_view Escape = AsciiSet::FindPrefixWith(Value, EscapeSet);
+ for (char Char : Escape)
+ {
+ switch (Char)
+ {
+ case '\\':
+ Builder << "\\\\"sv;
+ break;
+ case '\"':
+ Builder << "\\\""sv;
+ break;
+ case '\b':
+ Builder << "\\b"sv;
+ break;
+ case '\f':
+ Builder << "\\f"sv;
+ break;
+ case '\n':
+ Builder << "\\n"sv;
+ break;
+ case '\r':
+ Builder << "\\r"sv;
+ break;
+ case '\t':
+ Builder << "\\t"sv;
+ break;
+ default:
+ Builder << Char;
+ break;
+ }
+ }
+ Value = Value.substr(Escape.size());
+ }
+ Builder << '\"';
+ }
+
+ void AppendBase64String(MemoryView Value)
+ {
+ Builder << '"';
+ ZEN_ASSERT(Value.GetSize() <= 512 * 1024 * 1024);
+ const uint32_t EncodedSize = Base64::GetEncodedDataSize(uint32_t(Value.GetSize()));
+ const size_t EncodedIndex = Builder.AddUninitialized(size_t(EncodedSize));
+ Base64::Encode(static_cast<const uint8_t*>(Value.GetData()), uint32_t(Value.GetSize()), Builder.Data() + EncodedIndex);
+ }
+
+private:
+ StringBuilderBase& Builder;
+ ExtendableStringBuilder<32> NewLineAndIndent;
+ bool NeedsComma{false};
+ bool NeedsNewLine{false};
+};
+
+void
+CompactBinaryToJson(const CbObjectView& Object, StringBuilderBase& Builder)
+{
+ CbJsonWriter Writer(Builder);
+ Writer.WriteField(Object.AsFieldView());
+}
+
+void
+CompactBinaryToJson(const CbArrayView& Array, StringBuilderBase& Builder)
+{
+ CbJsonWriter Writer(Builder);
+ Writer.WriteField(Array.AsFieldView());
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+class CbJsonReader
+{
+public:
+ static CbFieldIterator Read(std::string_view JsonText, std::string& Error)
+ {
+ using namespace json11;
+
+ const Json Json = Json::parse(std::string(JsonText), Error);
+
+ if (Error.empty())
+ {
+ CbWriter Writer;
+ if (ReadField(Writer, Json, std::string_view(), Error))
+ {
+ return Writer.Save();
+ }
+ }
+
+ return CbFieldIterator();
+ }
+
+private:
+ static bool ReadField(CbWriter& Writer, const json11::Json& Json, const std::string_view FieldName, std::string& Error)
+ {
+ using namespace json11;
+
+ switch (Json.type())
+ {
+ case Json::Type::OBJECT:
+ {
+ if (FieldName.empty())
+ {
+ Writer.BeginObject();
+ }
+ else
+ {
+ Writer.BeginObject(FieldName);
+ }
+
+ for (const auto& Kv : Json.object_items())
+ {
+ const std::string& Name = Kv.first;
+ const json11::Json& Item = Kv.second;
+
+ if (ReadField(Writer, Item, Name, Error) == false)
+ {
+ return false;
+ }
+ }
+
+ Writer.EndObject();
+ }
+ break;
+ case Json::Type::ARRAY:
+ {
+ if (FieldName.empty())
+ {
+ Writer.BeginArray();
+ }
+ else
+ {
+ Writer.BeginArray(FieldName);
+ }
+
+ for (const json11::Json& Item : Json.array_items())
+ {
+ if (ReadField(Writer, Item, std::string_view(), Error) == false)
+ {
+ return false;
+ }
+ }
+
+ Writer.EndArray();
+ }
+ break;
+ case Json::Type::NUL:
+ {
+ if (FieldName.empty())
+ {
+ Writer.AddNull();
+ }
+ else
+ {
+ Writer.AddNull(FieldName);
+ }
+ }
+ break;
+ case Json::Type::BOOL:
+ {
+ if (FieldName.empty())
+ {
+ Writer.AddBool(Json.bool_value());
+ }
+ else
+ {
+ Writer.AddBool(FieldName, Json.bool_value());
+ }
+ }
+ break;
+ case Json::Type::NUMBER:
+ {
+ if (FieldName.empty())
+ {
+ Writer.AddFloat(Json.number_value());
+ }
+ else
+ {
+ Writer.AddFloat(FieldName, Json.number_value());
+ }
+ }
+ break;
+ case Json::Type::STRING:
+ {
+ Oid Id;
+ if (TryParseObjectId(Json.string_value(), Id))
+ {
+ if (FieldName.empty())
+ {
+ Writer.AddObjectId(Id);
+ }
+ else
+ {
+ Writer.AddObjectId(FieldName, Id);
+ }
+
+ return true;
+ }
+
+ IoHash Hash;
+ if (TryParseIoHash(Json.string_value(), Hash))
+ {
+ if (FieldName.empty())
+ {
+ Writer.AddHash(Hash);
+ }
+ else
+ {
+ Writer.AddHash(FieldName, Hash);
+ }
+
+ return true;
+ }
+
+ if (FieldName.empty())
+ {
+ Writer.AddString(Json.string_value());
+ }
+ else
+ {
+ Writer.AddString(FieldName, Json.string_value());
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return true;
+ }
+
+ static constexpr AsciiSet HexCharSet = AsciiSet("0123456789abcdefABCDEF");
+
+ static bool TryParseObjectId(std::string_view Str, Oid& Id)
+ {
+ using namespace std::literals;
+
+ if (Str.size() == Oid::StringLength && AsciiSet::HasOnly(Str, HexCharSet))
+ {
+ Id = Oid::FromHexString(Str);
+ return true;
+ }
+
+ if (Str.starts_with("0x"sv))
+ {
+ return TryParseObjectId(Str.substr(2), Id);
+ }
+
+ return false;
+ }
+
+ static bool TryParseIoHash(std::string_view Str, IoHash& Hash)
+ {
+ using namespace std::literals;
+
+ if (Str.size() == IoHash::StringLength && AsciiSet::HasOnly(Str, HexCharSet))
+ {
+ Hash = IoHash::FromHexString(Str);
+ return true;
+ }
+
+ if (Str.starts_with("0x"sv))
+ {
+ return TryParseIoHash(Str.substr(2), Hash);
+ }
+
+ return false;
+ }
+};
+
+CbFieldIterator
+LoadCompactBinaryFromJson(std::string_view Json, std::string& Error)
+{
+ if (Json.empty() == false)
+ {
+ return CbJsonReader::Read(Json, Error);
+ }
+
+ return CbFieldIterator();
+}
+
+CbFieldIterator
+LoadCompactBinaryFromJson(std::string_view Json)
+{
+ std::string Error;
+ return LoadCompactBinaryFromJson(Json, Error);
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+#if ZEN_WITH_TESTS
+void
+uson_forcelink()
+{
+}
+
+TEST_CASE("uson")
+{
+ using namespace std::literals;
+
+ SUBCASE("CbField")
+ {
+ constexpr CbFieldView DefaultField;
+ static_assert(!DefaultField.HasName(), "Error in HasName()");
+ static_assert(!DefaultField.HasValue(), "Error in HasValue()");
+ static_assert(!DefaultField.HasError(), "Error in HasError()");
+ static_assert(DefaultField.GetError() == CbFieldError::None, "Error in GetError()");
+
+ CHECK(DefaultField.GetSize() == 1);
+ CHECK(DefaultField.GetName().size() == 0);
+ CHECK(DefaultField.HasName() == false);
+ CHECK(DefaultField.HasValue() == false);
+ CHECK(DefaultField.HasError() == false);
+ CHECK(DefaultField.GetError() == CbFieldError::None);
+
+ const uint8_t Type = (uint8_t)CbFieldType::None;
+ CHECK(DefaultField.GetHash() == IoHash::HashBuffer(&Type, sizeof Type));
+
+ CHECK(DefaultField.GetView() == MemoryView{});
+ MemoryView SerializedView;
+ CHECK(DefaultField.TryGetSerializedView(SerializedView) == false);
+ }
+
+ SUBCASE("CbField(None)")
+ {
+ CbFieldView NoneField(nullptr, CbFieldType::None);
+ CHECK(NoneField.GetSize() == 1);
+ CHECK(NoneField.GetName().size() == 0);
+ CHECK(NoneField.HasName() == false);
+ CHECK(NoneField.HasValue() == false);
+ CHECK(NoneField.HasError() == false);
+ CHECK(NoneField.GetError() == CbFieldError::None);
+ CHECK(NoneField.GetHash() == CbFieldView().GetHash());
+ CHECK(NoneField.GetView() == MemoryView());
+ MemoryView SerializedView;
+ CHECK(NoneField.TryGetSerializedView(SerializedView) == false);
+ }
+
+ SUBCASE("CbField(None|Type|Name)")
+ {
+ constexpr CbFieldType FieldType = CbFieldType::None | CbFieldType::HasFieldName;
+ const char NoneBytes[] = {char(FieldType), 4, 'N', 'a', 'm', 'e'};
+ CbFieldView NoneField(NoneBytes);
+
+ CHECK(NoneField.GetSize() == sizeof(NoneBytes));
+ CHECK(NoneField.GetName().compare("Name"sv) == 0);
+ CHECK(NoneField.HasName() == true);
+ CHECK(NoneField.HasValue() == false);
+ CHECK(NoneField.GetHash() == IoHash::HashBuffer(NoneBytes, sizeof NoneBytes));
+ CHECK(NoneField.GetView() == MemoryView(NoneBytes, sizeof NoneBytes));
+ MemoryView SerializedView;
+ CHECK(NoneField.TryGetSerializedView(SerializedView) == true);
+ CHECK(SerializedView == MemoryView(NoneBytes, sizeof NoneBytes));
+
+ uint8_t CopyBytes[sizeof(NoneBytes)];
+ NoneField.CopyTo(MutableMemoryView(CopyBytes, sizeof CopyBytes));
+ CHECK(MemoryView(NoneBytes, sizeof NoneBytes).EqualBytes(MemoryView(CopyBytes, sizeof CopyBytes)));
+ }
+
+ SUBCASE("CbField(None|Type)")
+ {
+ constexpr CbFieldType FieldType = CbFieldType::None;
+ const char NoneBytes[] = {char(FieldType)};
+ CbFieldView NoneField(NoneBytes);
+
+ CHECK(NoneField.GetSize() == sizeof NoneBytes);
+ CHECK(NoneField.GetName().size() == 0);
+ CHECK(NoneField.HasName() == false);
+ CHECK(NoneField.HasValue() == false);
+ CHECK(NoneField.GetHash() == CbFieldView().GetHash());
+ CHECK(NoneField.GetView() == MemoryView(NoneBytes, sizeof NoneBytes));
+ MemoryView SerializedView;
+ CHECK(NoneField.TryGetSerializedView(SerializedView) == true);
+ CHECK(SerializedView == MemoryView(NoneBytes, sizeof NoneBytes));
+ }
+
+ SUBCASE("CbField(None|Name)")
+ {
+ constexpr CbFieldType FieldType = CbFieldType::None | CbFieldType::HasFieldName;
+ const char NoneBytes[] = {char(FieldType), 4, 'N', 'a', 'm', 'e'};
+ CbFieldView NoneField(NoneBytes + 1, FieldType);
+ CHECK(NoneField.GetSize() == uint64_t(sizeof NoneBytes));
+ CHECK(NoneField.GetName().compare("Name") == 0);
+ CHECK(NoneField.HasName() == true);
+ CHECK(NoneField.HasValue() == false);
+ CHECK(NoneField.GetHash() == IoHash::HashBuffer(NoneBytes, sizeof NoneBytes));
+ CHECK(NoneField.GetView() == MemoryView(NoneBytes + 1, sizeof NoneBytes - 1));
+ MemoryView SerializedView;
+ CHECK(NoneField.TryGetSerializedView(SerializedView) == false);
+
+ uint8_t CopyBytes[sizeof(NoneBytes)];
+ NoneField.CopyTo(MutableMemoryView(CopyBytes, sizeof CopyBytes));
+ CHECK(MemoryView(NoneBytes, sizeof NoneBytes).EqualBytes(MemoryView(CopyBytes, sizeof CopyBytes)));
+ }
+
+ SUBCASE("CbField(None|EmptyName)")
+ {
+ constexpr CbFieldType FieldType = CbFieldType::None | CbFieldType::HasFieldName;
+ const uint8_t NoneBytes[] = {uint8_t(FieldType), 0};
+ CbFieldView NoneField(NoneBytes + 1, FieldType);
+ CHECK(NoneField.GetSize() == sizeof NoneBytes);
+ CHECK(NoneField.GetName().empty() == true);
+ CHECK(NoneField.HasName() == true);
+ CHECK(NoneField.HasValue() == false);
+ CHECK(NoneField.GetHash() == IoHash::HashBuffer(NoneBytes, sizeof NoneBytes));
+ CHECK(NoneField.GetView() == MemoryView(NoneBytes + 1, sizeof NoneBytes - 1));
+ MemoryView SerializedView;
+ CHECK(NoneField.TryGetSerializedView(SerializedView) == false);
+ }
+
+ static_assert(!std::is_constructible<CbFieldView, const CbObjectView&>::value, "Invalid constructor for CbField");
+ static_assert(!std::is_assignable<CbFieldView, const CbObjectView&>::value, "Invalid assignment for CbField");
+ static_assert(!std::is_convertible<CbFieldView, CbObjectView>::value, "Invalid conversion to CbObject");
+ static_assert(!std::is_assignable<CbObjectView, const CbFieldView&>::value, "Invalid assignment for CbObject");
+
+ static_assert(std::is_constructible<CbField>::value, "Missing constructor for CbField");
+ static_assert(std::is_constructible<CbField, const CbField&>::value, "Missing constructor for CbField");
+ static_assert(std::is_constructible<CbField, CbField&&>::value, "Missing constructor for CbField");
+}
+
+TEST_CASE("uson.null")
+{
+ using namespace std::literals;
+
+ SUBCASE("CbField(Null)")
+ {
+ CbFieldView NullField(nullptr, CbFieldType::Null);
+ CHECK(NullField.GetSize() == 1);
+ CHECK(NullField.IsNull() == true);
+ CHECK(NullField.HasValue() == true);
+ CHECK(NullField.HasError() == false);
+ CHECK(NullField.GetError() == CbFieldError::None);
+ const uint8_t Null[]{uint8_t(CbFieldType::Null)};
+ CHECK(NullField.GetHash() == IoHash::HashBuffer(Null, sizeof Null));
+ }
+
+ SUBCASE("CbField(None)")
+ {
+ CbFieldView Field;
+ CHECK(Field.IsNull() == false);
+ }
+}
+
+TEST_CASE("uson.json")
+{
+ SUBCASE("string")
+ {
+ CbObjectWriter Writer;
+ Writer << "KeyOne"
+ << "ValueOne";
+ Writer << "KeyTwo"
+ << "ValueTwo";
+ CbObject Obj = Writer.Save();
+
+ StringBuilder<128> Sb;
+ const char* JsonText = Obj.ToJson(Sb).Data();
+
+ std::string JsonError;
+ json11::Json Json = json11::Json::parse(JsonText, JsonError);
+
+ const std::string ValueOne = Json["KeyOne"].string_value();
+ const std::string ValueTwo = Json["KeyTwo"].string_value();
+
+ CHECK(JsonError.empty());
+ CHECK(ValueOne == "ValueOne");
+ CHECK(ValueTwo == "ValueTwo");
+ }
+
+ SUBCASE("number")
+ {
+ const float ExpectedFloatValue = 21.21f;
+ const double ExpectedDoubleValue = 42.42;
+
+ CbObjectWriter Writer;
+ Writer << "Float" << ExpectedFloatValue;
+ Writer << "Double" << ExpectedDoubleValue;
+
+ CbObject Obj = Writer.Save();
+
+ StringBuilder<128> Sb;
+ const char* JsonText = Obj.ToJson(Sb).Data();
+
+ std::string JsonError;
+ json11::Json Json = json11::Json::parse(JsonText, JsonError);
+
+ const float FloatValue = float(Json["Float"].number_value());
+ const double DoubleValue = Json["Double"].number_value();
+
+ CHECK(JsonError.empty());
+ CHECK(FloatValue == Approx(ExpectedFloatValue));
+ CHECK(DoubleValue == Approx(ExpectedDoubleValue));
+ }
+
+ SUBCASE("number.nan")
+ {
+ const float FloatNan = std::numeric_limits<float>::quiet_NaN();
+ const double DoubleNan = std::numeric_limits<double>::quiet_NaN();
+
+ CbObjectWriter Writer;
+ Writer << "FloatNan" << FloatNan;
+ Writer << "DoubleNan" << DoubleNan;
+
+ CbObject Obj = Writer.Save();
+
+ StringBuilder<128> Sb;
+ const char* JsonText = Obj.ToJson(Sb).Data();
+
+ std::string JsonError;
+ json11::Json Json = json11::Json::parse(JsonText, JsonError);
+
+ const double FloatValue = Json["FloatNan"].number_value();
+ const double DoubleValue = Json["DoubleNan"].number_value();
+
+ CHECK(JsonError.empty());
+ CHECK(FloatValue == 0);
+ CHECK(DoubleValue == 0);
+ }
+}
+
+TEST_CASE("uson.datetime")
+{
+ using namespace std::literals;
+
+ {
+ DateTime D1600(1601, 1, 1);
+ CHECK_EQ(D1600.GetYear(), 1601);
+ CHECK_EQ(D1600.GetMonth(), 1);
+ CHECK_EQ(D1600.GetDay(), 1);
+ CHECK_EQ(D1600.GetHour(), 0);
+ CHECK_EQ(D1600.GetMinute(), 0);
+ CHECK_EQ(D1600.GetSecond(), 0);
+
+ CHECK_EQ(D1600.ToIso8601(), "1601-01-01T00:00:00.000Z"sv);
+ }
+
+ {
+ DateTime D72(1972, 2, 23, 17, 30, 10);
+ CHECK_EQ(D72.GetYear(), 1972);
+ CHECK_EQ(D72.GetMonth(), 2);
+ CHECK_EQ(D72.GetDay(), 23);
+ CHECK_EQ(D72.GetHour(), 17);
+ CHECK_EQ(D72.GetMinute(), 30);
+ CHECK_EQ(D72.GetSecond(), 10);
+ }
+}
+
+TEST_CASE("json.uson")
+{
+ using namespace std::literals;
+ using namespace json11;
+
+ SUBCASE("empty")
+ {
+ CbFieldIterator It = LoadCompactBinaryFromJson(""sv);
+ CHECK(It.HasValue() == false);
+ }
+
+ SUBCASE("object")
+ {
+ const Json JsonObject = Json::object{{"Null", nullptr},
+ {"String", "Value1"},
+ {"Bool", true},
+ {"Number", 46.2},
+ {"Array", Json::array{1, 2, 3}},
+ {"Object",
+ Json::object{
+ {"String", "Value2"},
+ }}};
+
+ CbObject Cb = LoadCompactBinaryFromJson(JsonObject.dump()).AsObject();
+
+ CHECK(Cb["Null"].IsNull());
+ CHECK(Cb["String"].AsString() == "Value1"sv);
+ CHECK(Cb["Bool"].AsBool());
+ CHECK(Cb["Number"].AsDouble() == 46.2);
+ CHECK(Cb["Object"].IsObject());
+ CbObjectView Object = Cb["Object"].AsObjectView();
+ CHECK(Object["String"].AsString() == "Value2"sv);
+ }
+
+ SUBCASE("array")
+ {
+ const Json JsonArray = Json::array{42, 43, 44};
+ CbArray Cb = LoadCompactBinaryFromJson(JsonArray.dump()).AsArray();
+
+ auto It = Cb.CreateIterator();
+ CHECK((*It).AsDouble() == 42);
+ It++;
+ CHECK((*It).AsDouble() == 43);
+ It++;
+ CHECK((*It).AsDouble() == 44);
+ }
+
+ SUBCASE("objectid")
+ {
+ const Oid& Id = Oid::NewOid();
+
+ StringBuilder<64> Sb;
+ Id.ToString(Sb);
+
+ Json JsonObject = Json::object{{"value", Sb.ToString()}};
+ CbObject Cb = LoadCompactBinaryFromJson(JsonObject.dump()).AsObject();
+
+ CHECK(Cb["value"sv].IsObjectId());
+ CHECK(Cb["value"sv].AsObjectId() == Id);
+
+ Sb.Reset();
+ Sb << "0x";
+ Id.ToString(Sb);
+
+ JsonObject = Json::object{{"value", Sb.ToString()}};
+ Cb = LoadCompactBinaryFromJson(JsonObject.dump()).AsObject();
+
+ CHECK(Cb["value"sv].IsObjectId());
+ CHECK(Cb["value"sv].AsObjectId() == Id);
+ }
+
+ SUBCASE("iohash")
+ {
+ const uint8_t Data[] = {
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ };
+
+ const IoHash Hash = IoHash::HashBuffer(Data, sizeof(Data));
+
+ Json JsonObject = Json::object{{"value", Hash.ToHexString()}};
+ CbObject Cb = LoadCompactBinaryFromJson(JsonObject.dump()).AsObject();
+
+ CHECK(Cb["value"sv].IsHash());
+ CHECK(Cb["value"sv].AsHash() == Hash);
+
+ JsonObject = Json::object{{"value", "0x" + Hash.ToHexString()}};
+ Cb = LoadCompactBinaryFromJson(JsonObject.dump()).AsObject();
+
+ CHECK(Cb["value"sv].IsHash());
+ CHECK(Cb["value"sv].AsHash() == Hash);
+ }
+}
+
+#endif
+
+} // namespace zen