// // jsonizer_test.cxx // serializer-test // // Created by Sam Jaffe on 3/16/23. // #include "xcode_gtest_helper.h" #include #include #include #include #define CONCAT2(A, B) A##B #define CONCAT(A, B) CONCAT2(A, B) #define EXPECT_ROUNDTRIP(izer, value, as_json) \ Json::Value CONCAT(json_, __LINE__) = izer.to_json(value); \ EXPECT_THAT(CONCAT(json_, __LINE__), as_json); \ EXPECT_THAT(izer.from_json(CONCAT(json_, __LINE__)), value) using std::operator""s; using std::operator""sv; using namespace magic_enum::bitwise_operators; using testing::Field; using testing::IsNull; using testing::NotNull; using testing::Pointee; enum class Color { RED, BLUE, GREEN }; enum class Mask { LEFT = 1, RIGHT = 2 }; Json::Value operator""_json(char const * str, size_t len) { Json::Value rval; Json::Reader reader; if (!reader.parse(str, str + len, rval)) { throw std::invalid_argument(reader.getFormattedErrorMessages()); } return rval; } TEST(JsonizerTest, ParsesNumbers) { serializer::Jsonizer izer; EXPECT_ROUNDTRIP(izer, 1.5f, "1.5"_json); EXPECT_ROUNDTRIP(izer, 1.5, "1.5"_json); EXPECT_ROUNDTRIP(izer, 3, "3"_json); EXPECT_ROUNDTRIP(izer, true, "true"_json); } TEST(JsonizerTest, ParsesStrings) { serializer::Jsonizer izer; EXPECT_ROUNDTRIP(izer, "hello"s, "\"hello\""_json); EXPECT_THAT(izer.to_json("hello"), "\"hello\""_json); EXPECT_THAT(izer.to_json("hello"sv), "\"hello\""_json); } TEST(JsonizerTest, ParsesEnums) { serializer::Jsonizer izer; EXPECT_ROUNDTRIP(izer, Color::RED, "\"RED\""_json); EXPECT_TRUE(magic_enum::detail::is_flags_v); EXPECT_ROUNDTRIP(izer, Mask::LEFT | Mask::RIGHT, "\"LEFT|RIGHT\""_json); } TEST(JsonizerTest, ParsesTuples) { serializer::Jsonizer izer; EXPECT_ROUNDTRIP(izer, std::make_pair(1, true), "[1, true]"_json); EXPECT_ROUNDTRIP(izer, std::make_tuple(1, true, 1.5, "hi"s), "[1, true, 1.5, \"hi\"]"_json); } TEST(JsonizerTest, ParsesCollection) { serializer::Jsonizer izer; EXPECT_ROUNDTRIP(izer, std::vector({1, 2, 3, 1}), "[1, 2, 3, 1]"_json); EXPECT_ROUNDTRIP(izer, std::set({1, 2, 3, 1}), "[1, 2, 3]"_json); } TEST(JsonizerTest, ThrowsOnParsingCollectionFromNonArray) { serializer::Jsonizer izer; std::vector out; EXPECT_THROW(izer.from_json(out, "1"_json), std::invalid_argument); } TEST(JsonizerTest, ParsesAssociative) { serializer::Jsonizer izer; EXPECT_ROUNDTRIP(izer, (std::map{{1, 2}, {3, 1}}), "{\"1\":2, \"3\":1}"_json); } TEST(JsonizerTest, AssociativeCanParseFromPairListOrObject) { using Type = std::map; serializer::Jsonizer izer; EXPECT_THAT(izer.from_json("[[\"A\", 2], [\"B\", 1]]"_json), (Type{{"A", 2}, {"B", 1}})); EXPECT_THAT(izer.from_json("{ \"A\": 2, \"B\": 1 }"_json), (Type{{"A", 2}, {"B", 1}})); } TEST(JsonizerTest, AssociativeWithNonTrivialKeyIsPairList) { using Type = std::map, int>; serializer::Jsonizer izer; EXPECT_ROUNDTRIP(izer, (Type{{{1, 2}, 1}, {{2, 1}, 2}}), "[[[1, 2], 1], [[2, 1], 2]]"_json); } TEST(JsonizerTest, ThrowsOnParsingAssociativeFromNonCollection) { serializer::Jsonizer izer; std::map out; EXPECT_THROW(izer.from_json(out, "1"_json), std::invalid_argument); } struct Cacheable { std::string name; }; template <> void serializer::Jsonizer::from_json(Cacheable & value, Json::Value const & json) const { from_json(value.name, json["name"]); } TEST(JsonizerTest, CanAttemptToLoadFromCache) { serializer::Jsonizer izer; std::shared_ptr ptr; EXPECT_THROW(izer.from_json(ptr, "\"ONE\""_json), std::domain_error); EXPECT_THAT(ptr, IsNull()); EXPECT_NO_THROW(izer.from_json(ptr, "{\"name\":\"ONE\"}"_json)); EXPECT_THAT(ptr, NotNull()); EXPECT_THAT(ptr, Pointee(Field(&Cacheable::name, "ONE"))); } struct Serializable { using serial_type = std::map; Serializable(std::vector value = {}) : value(value) {} Serializable(serial_type vals) { for (auto [k, v] : vals) { while (v--) { value.push_back(k); } } } operator serial_type() const { serial_type rval; for (auto v : value) { ++rval[v]; } return rval; } std::vector value; }; bool operator==(Serializable const & lhs, Serializable const & rhs) { return lhs.value == rhs.value; } TEST(JsonizerTest, CanConstructFromAltType) { serializer::Jsonizer izer; EXPECT_ROUNDTRIP(izer, Serializable({1, 1, 1, 1, 2, 2, 2, 3, 3, 4}), "{\"1\":4, \"2\":3, \"3\":2, \"4\":1}"_json); }