diff options
Diffstat (limited to 'src/zencore')
| -rw-r--r-- | src/zencore/compactbinary.cpp | 781 | ||||
| -rw-r--r-- | src/zencore/compactbinaryjson.cpp | 805 | ||||
| -rw-r--r-- | src/zencore/compactbinaryyaml.cpp | 352 | ||||
| -rw-r--r-- | src/zencore/include/zencore/compactbinary.h | 32 | ||||
| -rw-r--r-- | src/zencore/xmake.lua | 2 | ||||
| -rw-r--r-- | src/zencore/zencore.cpp | 2 |
6 files changed, 1191 insertions, 783 deletions
diff --git a/src/zencore/compactbinary.cpp b/src/zencore/compactbinary.cpp index 6677b5a61..0907f8a2e 100644 --- a/src/zencore/compactbinary.cpp +++ b/src/zencore/compactbinary.cpp @@ -24,10 +24,6 @@ # 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}; @@ -1468,339 +1464,6 @@ SaveCompactBinary(BinaryWriter& Ar, const CbObjectView& Object) ////////////////////////////////////////////////////////////////////////// -class CbJsonWriter -{ -public: - explicit CbJsonWriter(StringBuilderBase& InBuilder) : Builder(InBuilder) { NewLineAndIndent << LINE_TERMINATOR_ANSI; } - - void BeginObject() - { - Builder << '{'; - NewLineAndIndent << '\t'; - NeedsNewLine = true; - } - - void EndObject() - { - NewLineAndIndent.RemoveSuffix(1); - if (NeedsComma) - { - WriteOptionalNewLine(); - } - Builder << '}'; - } - - void BeginArray() - { - Builder << '['; - NewLineAndIndent << '\t'; - NeedsNewLine = true; - } - - void EndArray() - { - NewLineAndIndent.RemoveSuffix(1); - if (NeedsComma) - { - WriteOptionalNewLine(); - } - Builder << ']'; - } - - 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: - { - BeginObject(); - for (CbFieldView It : Field) - { - WriteField(It); - } - EndObject(); - } - break; - case CbFieldType::Array: - case CbFieldType::UniformArray: - { - BeginArray(); - for (CbFieldView It : Field) - { - WriteField(It); - } - EndArray(); - } - 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_FORMAT(false, "invalid field type: {}", uint8_t(Accessor.GetType())); - 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()); -} - -void -CompactBinaryToJson(MemoryView Data, StringBuilderBase& InBuilder) -{ - std::vector<CbFieldView> Fields = ReadCompactBinaryStream(Data); - CbJsonWriter Writer(InBuilder); - if (!Fields.empty()) - { - if (Fields.size() == 1) - { - Writer.WriteField(Fields[0]); - return; - } - bool UseTopLevelObject = Fields[0].HasName(); - if (UseTopLevelObject) - { - Writer.BeginObject(); - } - else - { - Writer.BeginArray(); - } - for (const CbFieldView& Field : Fields) - { - Writer.WriteField(Field); - } - if (UseTopLevelObject) - { - Writer.EndObject(); - } - else - { - Writer.EndArray(); - } - } -} - std::vector<CbFieldView> ReadCompactBinaryStream(MemoryView Data) { @@ -1823,225 +1486,6 @@ ReadCompactBinaryStream(MemoryView Data) ////////////////////////////////////////////////////////////////////////// -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() @@ -2211,130 +1655,6 @@ TEST_CASE("uson.null") } } -TEST_CASE("uson.json") -{ - using namespace std::literals; - - 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") - { - constexpr float FloatNan = std::numeric_limits<float>::quiet_NaN(); - constexpr 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); - } - - SUBCASE("stream") - { - const auto MakeObject = [&](std::string_view Name, const std::vector<int>& Fields) -> CbObject { - CbWriter Writer; - Writer.SetName(Name); - Writer.BeginObject(); - for (const auto& Field : Fields) - { - Writer.AddInteger(fmt::format("{}", Field), Field); - } - Writer.EndObject(); - return Writer.Save().AsObject(); - }; - - std::vector<uint8_t> Buffer; - - auto AppendToBuffer = [&](const void* Data, size_t Count) { - const uint8_t* AppendBytes = reinterpret_cast<const uint8_t*>(Data); - Buffer.insert(Buffer.end(), AppendBytes, AppendBytes + Count); - }; - - auto Append = [&](const CbFieldView& Field) { - Field.WriteToStream([&](const void* Data, size_t Count) { - const uint8_t* AppendBytes = reinterpret_cast<const uint8_t*>(Data); - Buffer.insert(Buffer.end(), AppendBytes, AppendBytes + Count); - }); - }; - - CbObject DataObjects[] = {MakeObject("Empty object"sv, {}), - MakeObject("OneField object"sv, {5}), - MakeObject("TwoField object"sv, {-5, 999}), - MakeObject("ThreeField object"sv, {1, 2, -129})}; - for (const CbObject& Object : DataObjects) - { - Object.AsField().WriteToStream(AppendToBuffer); - } - - ExtendableStringBuilder<128> Sb; - CompactBinaryToJson(MemoryView(Buffer.data(), Buffer.size()), Sb); - std::string JsonText = Sb.ToString().c_str(); - std::string JsonError; - json11::Json Json = json11::Json::parse(JsonText, JsonError); - std::string ParsedJsonString = Json.dump(); - CHECK(!ParsedJsonString.empty()); - } -} - TEST_CASE("uson.datetime") { using namespace std::literals; @@ -2362,107 +1682,6 @@ TEST_CASE("uson.datetime") } } -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); - } -} - ////////////////////////////////////////////////////////////////////////// TEST_SUITE_BEGIN("core.datetime"); diff --git a/src/zencore/compactbinaryjson.cpp b/src/zencore/compactbinaryjson.cpp new file mode 100644 index 000000000..58a91c731 --- /dev/null +++ b/src/zencore/compactbinaryjson.cpp @@ -0,0 +1,805 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "zencore/compactbinary.h" +#include "zencore/compactbinarybuilder.h" +#include "zencore/compactbinaryvalue.h" + +#include <zencore/assertfmt.h> +#include <zencore/base64.h> +#include <zencore/fmtutils.h> +#include <zencore/string.h> +#include <zencore/testing.h> + +#include <fmt/format.h> +#include <vector> + +ZEN_THIRD_PARTY_INCLUDES_START +#include <json11.hpp> +ZEN_THIRD_PARTY_INCLUDES_END + +namespace zen { + +class CbJsonWriter +{ +public: + explicit CbJsonWriter(StringBuilderBase& InBuilder) : Builder(InBuilder) { NewLineAndIndent << LINE_TERMINATOR_ANSI; } + + void BeginObject() + { + Builder << '{'; + NewLineAndIndent << '\t'; + NeedsNewLine = true; + } + + void EndObject() + { + NewLineAndIndent.RemoveSuffix(1); + if (NeedsComma) + { + WriteOptionalNewLine(); + } + Builder << '}'; + } + + void BeginArray() + { + Builder << '['; + NewLineAndIndent << '\t'; + NeedsNewLine = true; + } + + void EndArray() + { + NewLineAndIndent.RemoveSuffix(1); + if (NeedsComma) + { + WriteOptionalNewLine(); + } + Builder << ']'; + } + + 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: + { + BeginObject(); + for (CbFieldView It : Field) + { + WriteField(It); + } + EndObject(); + } + break; + case CbFieldType::Array: + case CbFieldType::UniformArray: + { + BeginArray(); + for (CbFieldView It : Field) + { + WriteField(It); + } + EndArray(); + } + 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_FORMAT(false, "invalid field type: {}", uint8_t(Accessor.GetType())); + 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()); +} + +void +CompactBinaryToJson(MemoryView Data, StringBuilderBase& InBuilder) +{ + std::vector<CbFieldView> Fields = ReadCompactBinaryStream(Data); + CbJsonWriter Writer(InBuilder); + if (!Fields.empty()) + { + if (Fields.size() == 1) + { + Writer.WriteField(Fields[0]); + return; + } + bool UseTopLevelObject = Fields[0].HasName(); + if (UseTopLevelObject) + { + Writer.BeginObject(); + } + else + { + Writer.BeginArray(); + } + for (const CbFieldView& Field : Fields) + { + Writer.WriteField(Field); + } + if (UseTopLevelObject) + { + Writer.EndObject(); + } + else + { + Writer.EndArray(); + } + } +} + +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 +cbjson_forcelink() +{ +} + +TEST_CASE("uson.json") +{ + using namespace std::literals; + + 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") + { + constexpr float FloatNan = std::numeric_limits<float>::quiet_NaN(); + constexpr 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); + } + + SUBCASE("stream") + { + const auto MakeObject = [&](std::string_view Name, const std::vector<int>& Fields) -> CbObject { + CbWriter Writer; + Writer.SetName(Name); + Writer.BeginObject(); + for (const auto& Field : Fields) + { + Writer.AddInteger(fmt::format("{}", Field), Field); + } + Writer.EndObject(); + return Writer.Save().AsObject(); + }; + + std::vector<uint8_t> Buffer; + + auto AppendToBuffer = [&](const void* Data, size_t Count) { + const uint8_t* AppendBytes = reinterpret_cast<const uint8_t*>(Data); + Buffer.insert(Buffer.end(), AppendBytes, AppendBytes + Count); + }; + + auto Append = [&](const CbFieldView& Field) { + Field.WriteToStream([&](const void* Data, size_t Count) { + const uint8_t* AppendBytes = reinterpret_cast<const uint8_t*>(Data); + Buffer.insert(Buffer.end(), AppendBytes, AppendBytes + Count); + }); + }; + + CbObject DataObjects[] = {MakeObject("Empty object"sv, {}), + MakeObject("OneField object"sv, {5}), + MakeObject("TwoField object"sv, {-5, 999}), + MakeObject("ThreeField object"sv, {1, 2, -129})}; + for (const CbObject& Object : DataObjects) + { + Object.AsField().WriteToStream(AppendToBuffer); + } + + ExtendableStringBuilder<128> Sb; + CompactBinaryToJson(MemoryView(Buffer.data(), Buffer.size()), Sb); + std::string JsonText = Sb.ToString().c_str(); + std::string JsonError; + json11::Json Json = json11::Json::parse(JsonText, JsonError); + std::string ParsedJsonString = Json.dump(); + CHECK(!ParsedJsonString.empty()); + } +} + +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 // ZEN_WITH_TESTS + +} // namespace zen diff --git a/src/zencore/compactbinaryyaml.cpp b/src/zencore/compactbinaryyaml.cpp new file mode 100644 index 000000000..3a9705684 --- /dev/null +++ b/src/zencore/compactbinaryyaml.cpp @@ -0,0 +1,352 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "zencore/compactbinary.h" +#include "zencore/compactbinarybuilder.h" +#include "zencore/compactbinaryvalue.h" + +#include <zencore/assertfmt.h> +#include <zencore/base64.h> +#include <zencore/fmtutils.h> +#include <zencore/string.h> +#include <zencore/testing.h> + +#include <fmt/format.h> +#include <string_view> +#include <vector> + +ZEN_THIRD_PARTY_INCLUDES_START +#include <ryml/ryml.hpp> +#include <ryml/ryml_std.hpp> +ZEN_THIRD_PARTY_INCLUDES_END + +namespace zen { + +////////////////////////////////////////////////////////////////////////// + +class CbYamlWriter +{ +public: + explicit CbYamlWriter(StringBuilderBase& InBuilder) : m_StrBuilder(InBuilder) { m_NodeStack.push_back(m_Tree.rootref()); } + + void WriteField(CbFieldView Field) + { + ryml::NodeRef Node; + + if (m_IsFirst) + { + Node = Top(); + + m_IsFirst = false; + } + else + { + Node = Top().append_child(); + } + + if (std::u8string_view Name = Field.GetU8Name(); !Name.empty()) + { + Node.set_key_serialized(ryml::csubstr((const char*)Name.data(), Name.size())); + } + + switch (CbValue Accessor = Field.GetValue(); Accessor.GetType()) + { + case CbFieldType::Null: + Node.set_val("null"); + break; + case CbFieldType::Object: + case CbFieldType::UniformObject: + Node |= ryml::MAP; + m_NodeStack.push_back(Node); + for (CbFieldView It : Field) + { + WriteField(It); + } + m_NodeStack.pop_back(); + break; + case CbFieldType::Array: + case CbFieldType::UniformArray: + Node |= ryml::SEQ; + m_NodeStack.push_back(Node); + for (CbFieldView It : Field) + { + WriteField(It); + } + m_NodeStack.pop_back(); + break; + case CbFieldType::Binary: + { + ExtendableStringBuilder<256> Builder; + const MemoryView Value = Accessor.AsBinary(); + 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); + + Node.set_key_serialized(Builder.c_str()); + } + break; + case CbFieldType::String: + { + const std::u8string_view U8String = Accessor.AsU8String(); + Node.set_val(ryml::csubstr((const char*)U8String.data(), U8String.size())); + } + break; + case CbFieldType::IntegerPositive: + Node << Accessor.AsIntegerPositive(); + break; + case CbFieldType::IntegerNegative: + Node << Accessor.AsIntegerNegative(); + break; + case CbFieldType::Float32: + if (const float Value = Accessor.AsFloat32(); std::isfinite(Value)) + { + Node << Value; + } + else + { + Node << "null"; + } + break; + case CbFieldType::Float64: + if (const double Value = Accessor.AsFloat64(); std::isfinite(Value)) + { + Node << Value; + } + else + { + Node << "null"; + } + break; + case CbFieldType::BoolFalse: + Node << "false"; + break; + case CbFieldType::BoolTrue: + Node << "true"; + break; + case CbFieldType::ObjectAttachment: + case CbFieldType::BinaryAttachment: + Node << Accessor.AsAttachment().ToHexString(); + break; + case CbFieldType::Hash: + Node << Accessor.AsHash().ToHexString(); + break; + case CbFieldType::Uuid: + Node << fmt::format("{}", Accessor.AsUuid()); + break; + case CbFieldType::DateTime: + Node << DateTime(Accessor.AsDateTimeTicks()).ToIso8601(); + break; + case CbFieldType::TimeSpan: + if (const TimeSpan Span(Accessor.AsTimeSpanTicks()); Span.GetDays() == 0) + { + Node << Span.ToString("%h:%m:%s.%n"); + } + else + { + Node << Span.ToString("%d.%h:%m:%s.%n"); + } + break; + case CbFieldType::ObjectId: + Node << fmt::format("{}", Accessor.AsObjectId()); + break; + case CbFieldType::CustomById: + { + CbCustomById Custom = Accessor.AsCustomById(); + + Node |= ryml::MAP; + + ryml::NodeRef IdNode = Node.append_child(); + IdNode.set_key("Id"); + IdNode.set_val_serialized(fmt::format("{}", Custom.Id)); + + ryml::NodeRef DataNode = Node.append_child(); + DataNode.set_key("Data"); + + ExtendableStringBuilder<256> Builder; + const MemoryView& Value = Custom.Data; + 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); + + DataNode.set_val_serialized(Builder.c_str()); + } + break; + case CbFieldType::CustomByName: + { + CbCustomByName Custom = Accessor.AsCustomByName(); + + Node |= ryml::MAP; + + ryml::NodeRef NameNode = Node.append_child(); + NameNode.set_key("Name"); + std::string_view Name = std::string_view((const char*)Custom.Name.data(), Custom.Name.size()); + NameNode.set_val_serialized(std::string(Name)); + + ryml::NodeRef DataNode = Node.append_child(); + DataNode.set_key("Data"); + + ExtendableStringBuilder<256> Builder; + const MemoryView& Value = Custom.Data; + 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); + + DataNode.set_val_serialized(Builder.c_str()); + } + break; + default: + ZEN_ASSERT_FORMAT(false, "invalid field type: {}", uint8_t(Accessor.GetType())); + break; + } + + if (m_NodeStack.size() == 1) + { + std::string Yaml = ryml::emitrs_yaml<std::string>(m_Tree); + m_StrBuilder << Yaml; + } + } + +private: + StringBuilderBase& m_StrBuilder; + bool m_IsFirst = true; + + ryml::Tree m_Tree; + std::vector<ryml::NodeRef> m_NodeStack; + ryml::NodeRef& Top() { return m_NodeStack.back(); } +}; + +void +CompactBinaryToYaml(const CbObjectView& Object, StringBuilderBase& Builder) +{ + CbYamlWriter Writer(Builder); + Writer.WriteField(Object.AsFieldView()); +} + +void +CompactBinaryToYaml(const CbArrayView& Array, StringBuilderBase& Builder) +{ + CbYamlWriter Writer(Builder); + Writer.WriteField(Array.AsFieldView()); +} + +#if ZEN_WITH_TESTS +void +cbyaml_forcelink() +{ +} + +TEST_CASE("uson.yaml") +{ + using namespace std::literals; + + SUBCASE("simple") + { + CbObjectWriter Writer; + Writer << "KeyOne" + << "ValueOne"; + Writer << "KeyTwo" + << "ValueTwo"; + CbObject Obj = Writer.Save(); + + StringBuilder<128> Sb; + CbYamlWriter YamlWriter(Sb); + YamlWriter.WriteField(Obj.AsFieldView()); + + CHECK_EQ(Sb.ToView(), "KeyOne: ValueOne\nKeyTwo: ValueTwo\n"sv); + } + + SUBCASE("scalar_fields") + { + CbObjectWriter Writer; + Writer << "small_int"sv << 10; + Writer << "neg_small_int"sv << -10; + Writer << "small_real"sv << 10.5f; + Writer << "neg_small_real"sv << -10.5f; + Writer.AddNull("null_val"sv); + Writer.AddDateTimeTicks("date"sv, 622'033'000'000'000'000ull); + Writer.AddHash("hash"sv, IoHash::FromHexString("0011223344556677889900112233445566778899"sv)); + Writer.AddObjectId("oid"sv, Oid::FromHexString("112233445566778899001122"sv)); + Writer.AddTimeSpanTicks("dt"sv, 3'000'000'000'000ull); + Writer.AddUuid("guid"sv, Guid::FromString("E0596ADC-996A-4BA4-ACA3-A2A378AB2796")); + Writer.AddBool("yes"sv, true); + Writer.AddBool("no"sv, false); + CbObject Obj = Writer.Save(); + + ExtendableStringBuilder<128> Sb; + CbYamlWriter YamlWriter(Sb); + YamlWriter.WriteField(Obj.AsFieldView()); + + CHECK_EQ(Sb.ToView(), + "small_int: 10\n" + "neg_small_int: -10\n" + "small_real: 10.5\n" + "neg_small_real: -10.5\n" + "null_val: null\n" + "date: '1972-02-23T14:26:40.000Z'\n" + "hash: 0011223344556677889900112233445566778899\n" + "oid: 112233445566778899001122\n" + "dt: '+3.11:20:00.000000000'\n" + "guid: 'e0596adc-996a-4ba4-aca3-a2a378ab2796'\n" + "yes: true\n" + "no: false\n"sv); + } + + SUBCASE("complex_fields") + { + CbObjectWriter Writer; + Writer.BeginObject("sub"); + Writer.AddBool("no"sv, false); + Writer.BeginObject("sub"); + Writer.AddBool("yes"sv, true); + Writer.BeginArray("seq"); + Writer.AddInteger(1); + Writer.AddInteger(2); + Writer.AddInteger(3); + Writer.EndArray(); + Writer.EndObject(); + Writer.EndObject(); + Writer.BeginArray("seq"); + Writer.AddInteger(1); + Writer.AddInteger(2); + Writer.AddInteger(3); + Writer.EndArray(); + Writer.BeginArray("mixed_seq"); + Writer.AddInteger(1); + Writer.AddString("hello"sv); + Writer.AddFloat(44.4f); + Writer.BeginObject(); + Writer.AddBool("yes"sv, true); + Writer.AddBool("no"sv, false); + Writer.EndObject(); + Writer.EndArray(); + CbObject Obj = Writer.Save(); + + ExtendableStringBuilder<128> Sb; + CbYamlWriter YamlWriter(Sb); + YamlWriter.WriteField(Obj.AsFieldView()); + + CHECK_EQ(Sb.ToView(), + R"(sub: + no: false + sub: + yes: true + seq: + - 1 + - 2 + - 3 +seq: + - 1 + - 2 + - 3 +mixed_seq: + - 1 + - hello + - 44.4 + - yes: true + no: false +)"sv); + } +} +#endif + +} // namespace zen diff --git a/src/zencore/include/zencore/compactbinary.h b/src/zencore/include/zencore/compactbinary.h index 675e2a8d4..a8707ba2b 100644 --- a/src/zencore/include/zencore/compactbinary.h +++ b/src/zencore/include/zencore/compactbinary.h @@ -895,6 +895,11 @@ private: ZENCORE_API void CompactBinaryToJson(const CbArrayView& Object, StringBuilderBase& Builder); /** + * Serialize a compact binary array to YAML. + */ +ZENCORE_API void CompactBinaryToYaml(const CbArrayView& Object, StringBuilderBase& Builder); + +/** * Array of CbField that have no names. * * Accessing a field of the array requires iteration. Access by index is not provided because the @@ -974,6 +979,12 @@ public: return Builder; } + StringBuilderBase& ToYaml(StringBuilderBase& Builder) const + { + CompactBinaryToYaml(*this, Builder); + return Builder; + } + private: friend inline CbFieldViewIterator begin(const CbArrayView& Array) { return Array.CreateViewIterator(); } friend inline CbFieldViewIterator end(const CbArrayView&) { return CbFieldViewIterator(); } @@ -986,6 +997,10 @@ private: * Serialize a compact binary object to JSON. */ ZENCORE_API void CompactBinaryToJson(const CbObjectView& Object, StringBuilderBase& Builder); +/** + * Serialize a compact binary object to YAML. + */ +ZENCORE_API void CompactBinaryToYaml(const CbObjectView& Object, StringBuilderBase& Builder); class CbObjectView : protected CbFieldView { @@ -1075,6 +1090,12 @@ public: return Builder; } + StringBuilderBase& ToYaml(StringBuilderBase& Builder) const + { + CompactBinaryToYaml(*this, Builder); + return Builder; + } + private: friend inline CbFieldViewIterator begin(const CbObjectView& Object) { return Object.CreateViewIterator(); } friend inline CbFieldViewIterator end(const CbObjectView&) { return CbFieldViewIterator(); } @@ -1497,12 +1518,19 @@ end(CbFieldView&) } /** - * Serialize serialized compact binary blob to jaons. It must be 0 to n fields with including type for each field + * Serialize serialized compact binary blob to JSON. It must be 0 to n fields with including type for each field */ ZENCORE_API void CompactBinaryToJson(MemoryView Data, StringBuilderBase& InBuilder); +/** + * Serialize serialized compact binary blob to YAML. It must be 0 to n fields with including type for each field + */ +ZENCORE_API void CompactBinaryToYaml(MemoryView Data, StringBuilderBase& InBuilder); + ZENCORE_API std::vector<CbFieldView> ReadCompactBinaryStream(MemoryView Data); -void uson_forcelink(); // internal +void uson_forcelink(); // internal +void cbjson_forcelink(); // internal +void cbyaml_forcelink(); // internal } // namespace zen diff --git a/src/zencore/xmake.lua b/src/zencore/xmake.lua index 5420464fa..e6102679d 100644 --- a/src/zencore/xmake.lua +++ b/src/zencore/xmake.lua @@ -32,6 +32,8 @@ target('zencore') add_packages( "vcpkg::blake3", "vcpkg::json11", + "vcpkg::ryml", + "vcpkg::c4core", "vcpkg::mimalloc", "vcpkg::openssl", -- required for crypto "vcpkg::spdlog") diff --git a/src/zencore/zencore.cpp b/src/zencore/zencore.cpp index b80b1d280..c97f5e5ca 100644 --- a/src/zencore/zencore.cpp +++ b/src/zencore/zencore.cpp @@ -160,6 +160,8 @@ zencore_forcelinktests() zen::uson_forcelink(); zen::usonbuilder_forcelink(); zen::usonpackage_forcelink(); + zen::cbjson_forcelink(); + zen::cbyaml_forcelink(); zen::workthreadpool_forcelink(); } } // namespace zen |