diff options
author | Sergey Yershov <syershov@maps.me> | 2017-06-23 22:32:15 +0300 |
---|---|---|
committer | Yuri Gorshenin <mipt.vi002@gmail.com> | 2017-07-05 16:41:38 +0300 |
commit | 8a5fcdf2cf8eec8812f731a70997e59be009f776 (patch) | |
tree | 5db010e08dccf7ad4769d4f3df81534098e9f643 /ugc | |
parent | 4f0146194b6e4246ab8976a8545a419c587ba6df (diff) |
Serialization and deserealization UGC structures to/from JSON
Diffstat (limited to 'ugc')
-rw-r--r-- | ugc/serdes_json.hpp | 226 | ||||
-rw-r--r-- | ugc/ugc_tests/CMakeLists.txt | 3 | ||||
-rw-r--r-- | ugc/ugc_tests/serdes_json_test.cpp | 91 | ||||
-rw-r--r-- | ugc/ugc_tests/ugc_tests.pro | 3 |
4 files changed, 321 insertions, 2 deletions
diff --git a/ugc/serdes_json.hpp b/ugc/serdes_json.hpp new file mode 100644 index 0000000000..43151efd14 --- /dev/null +++ b/ugc/serdes_json.hpp @@ -0,0 +1,226 @@ +#pragma once + +#include "ugc/serdes.hpp" +#include "ugc/types.hpp" + +#include "coding/multilang_utf8_string.hpp" +#include "coding/point_to_integer.hpp" +#include "coding/reader.hpp" +#include "coding/varint.hpp" +#include "coding/write_to_sink.hpp" + +#include "base/exception.hpp" + +#include "3party/jansson/myjansson.hpp" + +#include <cmath> +#include <cstdint> +#include <iostream> + +namespace ugc +{ + template <typename Sink> + class SerializerJson + { + public: + SerializerJson(Sink & sink, HeaderV0 const & header) : m_sink(sink), m_header(header) {} + + void operator()(bool const d, char const * name = nullptr) + { + ToJSONObject(*m_json, name, d); + } + + void operator()(uint8_t const d, char const * name = nullptr) + { + ToJSONObject(*m_json, name, d); + } + + void operator()(uint32_t const d, char const * name = nullptr) + { + ToJSONObject(*m_json, name, d); + } + + void operator()(uint64_t const d, char const * name = nullptr) + { + ToJSONObject(*m_json, name, d); + } + + void operator()(std::string const & s, char const * name = nullptr) + { + ToJSONObject(*m_json, name, s); + } + + void operator()(Time const & t, char const * name = nullptr) + { + (*this)(ToDaysSinceEpoch(t), name); + } + + void operator()(Sentiment sentiment, char const * name = nullptr) + { + switch (sentiment) + { + case Sentiment::Negative: return (*this)(false, name); + case Sentiment::Positive: return (*this)(true, name); + } + } + + template <typename T> + void operator()(vector<T> const & vs, char const * name = nullptr) + { + my::JSONPtr safe_json = std::move(m_json); + m_json = my::NewJSONArray(); + for (auto const & v : vs) + (*this)(v); + Update(std::move(safe_json), name); + } + + template <typename R> + void operator()(R const & r, char const * name = nullptr) + { + my::JSONPtr safe_json = std::move(m_json); + m_json = my::NewJSONObject(); + r.Visit(*this); + Update(std::move(safe_json), name); + } + + void VisitRating(float const f, char const * name = nullptr) + { + CHECK_GREATER_OR_EQUAL(f, 0.0, ()); + auto const d = static_cast<double>(f); + ToJSONObject(*m_json, name, d); + } + + void Flush() + { + std::string json(json_dumps(m_json.get(), 0)); + m_sink.Write(json.data(), json.size()); + } + + private: + + void Update(my::JSONPtr safe_json, char const * name = nullptr) + { + if (safe_json && json_is_array(safe_json)) + json_array_append_new(safe_json.get(), m_json.release()); + else if (safe_json && json_is_object(safe_json)) + json_object_set_new(safe_json.get(), name, m_json.release()); + + if (safe_json) + m_json = std::move(safe_json); + } + + my::JSONPtr m_json = nullptr; + Sink & m_sink; + HeaderV0 const m_header; + }; + + template <typename Source> + class DeserializerJsonV0 + { + public: + DECLARE_EXCEPTION(Exception, RootException); + + DeserializerJsonV0(Source & source, HeaderV0 const & header) : m_source(source), m_header(header) + { + std::string src; + src.resize(source.Size()); + source.Read(static_cast<void *>(&src[0]), source.Size()); + m_jsonObject.Attach(src.c_str()); + m_json = m_jsonObject.get(); + } + + void operator()(bool & d, char const * name = nullptr) + { + FromJSONObject(m_json, name, d); + } + + void operator()(uint8_t & d, char const * name = nullptr) + { + FromJSONObject(m_json, name, d); + } + + void operator()(uint32_t & d, char const * name = nullptr) + { + FromJSONObject(m_json, name, d); + } + + void operator()(uint64_t & d, char const * name = nullptr) + { + FromJSONObject(m_json, name, d); + } + + void operator()(std::string & s, char const * name = nullptr) + { + FromJSONObject(m_json, name, s); + } + + void operator()(Time & t, char const * name = nullptr) + { + uint32_t d = 0; + FromJSONObject(m_json, name, d); + t = FromDaysSinceEpoch(d); + } + + void operator()(Sentiment & sentiment, char const * name = nullptr) + { + bool s = false; + FromJSONObject(m_json, name, s); + sentiment = s ? Sentiment::Positive : Sentiment::Negative; + } + + template <typename T> + void operator()(vector<T> & vs, char const * name = nullptr) + { + json_t * context = SaveContext(name); + + if (!json_is_array(m_json)) + MYTHROW(my::Json::Exception, ("The field", name, "must contain a json array.")); + + vs.resize(json_array_size(m_json)); + for (size_t index = 0; index < vs.size(); ++index) + { + json_t * context = SaveContext(); + m_json = json_array_get(context, index); + (*this)(vs[index]); + RestoreContext(context); + } + + RestoreContext(context); + } + + template <typename R> + void operator()(R & r, char const * name = nullptr) + { + json_t * context = SaveContext(name); + r.Visit(*this); + RestoreContext(context); + } + + void VisitRating(float & f, char const * name = nullptr) + { + double d = 0.0; + FromJSONObject(m_json, name, d); + f = static_cast<float>(d); + } + + private: + json_t * SaveContext(char const * name = nullptr) + { + json_t * context = m_json; + if (name) + m_json = my::GetJSONObligatoryField(context, name); + return context; + } + + void RestoreContext(json_t * context) + { + if (context) + m_json = context; + } + + my::Json m_jsonObject; + json_t * m_json = nullptr; + Source & m_source; + HeaderV0 const m_header; + }; +} // namespace ugc diff --git a/ugc/ugc_tests/CMakeLists.txt b/ugc/ugc_tests/CMakeLists.txt index b8501676c0..63638a70a8 100644 --- a/ugc/ugc_tests/CMakeLists.txt +++ b/ugc/ugc_tests/CMakeLists.txt @@ -3,12 +3,14 @@ project(ugc_tests) set( SRC serdes_tests.cpp + serdes_json_tests.cpp ) omim_add_test(${PROJECT_NAME} ${SRC}) omim_link_libraries( ${PROJECT_NAME} ugc + jansson indexer platform coding @@ -17,5 +19,4 @@ omim_link_libraries( stats_client ${LIBZ} ) - link_qt5_core(${PROJECT_NAME}) diff --git a/ugc/ugc_tests/serdes_json_test.cpp b/ugc/ugc_tests/serdes_json_test.cpp new file mode 100644 index 0000000000..6f7179da8c --- /dev/null +++ b/ugc/ugc_tests/serdes_json_test.cpp @@ -0,0 +1,91 @@ +#include "testing/testing.hpp" + +#include "ugc/api.hpp" +#include "ugc/serdes_json.hpp" +#include "ugc/types.hpp" + +#include "coding/reader.hpp" +#include "coding/writer.hpp" + +#include <cstdint> +#include <vector> + +using namespace std; +using namespace ugc; + +namespace +{ + using Buffer = vector<uint8_t>; + using Ser = SerializerJson<MemWriter<Buffer>>; + using Des = DeserializerJsonV0<ReaderSource<MemReader>>; + + Rating GetTestRating() + { + vector<RatingRecord> records; + records.emplace_back("music" /* key */, 5.0 /* value */); + records.emplace_back("service" /* key */, 4.0 /* value */); + + return Rating(records, 4.5 /* aggValue */); + } + + MemWriter<Buffer> MakeSink(Buffer & buffer) { return MemWriter<Buffer>(buffer); } + + ReaderSource<MemReader> MakeSource(Buffer const & buffer) + { + MemReader reader(buffer.data(), buffer.size()); + return ReaderSource<MemReader>(reader); + } + + UNIT_TEST(SerDes_Json_Rating) + { + auto expectedRating = GetTestRating(); + TEST_EQUAL(expectedRating, expectedRating, ()); + + HeaderV0 header; + + Buffer buffer; + + { + auto sink = MakeSink(buffer); + Ser ser(sink, header); + ser(expectedRating); + ser.Flush(); + } + + Rating actualRating({} /* ratings */, {} /* aggValue */); + + { + auto source = MakeSource(buffer); + Des des(source, header); + des(actualRating); + } + + TEST_EQUAL(expectedRating, actualRating, ()); + } + + UNIT_TEST(SerDes_Json_UGC) + { + auto expectedUGC = Api::MakeTestUGC1(); + TEST_EQUAL(expectedUGC, expectedUGC, ()); + + HeaderV0 header; + + Buffer buffer; + + { + auto sink = MakeSink(buffer); + Ser ser(sink, header); + ser(expectedUGC); + ser.Flush(); + } + + UGC actualUGC({} /* rating */, {} /* reviews */, {} /* attributes */); + { + auto source = MakeSource(buffer); + Des des(source, header); + des(actualUGC); + } + + TEST_EQUAL(expectedUGC, actualUGC, ()); + } +} // namespace diff --git a/ugc/ugc_tests/ugc_tests.pro b/ugc/ugc_tests/ugc_tests.pro index bc20f3e7a3..2537430a24 100644 --- a/ugc/ugc_tests/ugc_tests.pro +++ b/ugc/ugc_tests/ugc_tests.pro @@ -6,7 +6,7 @@ CONFIG -= app_bundle TEMPLATE = app ROOT_DIR = ../.. -DEPENDENCIES = ugc indexer platform coding geometry base stats_client +DEPENDENCIES = ugc jansson indexer platform coding geometry base stats_client macx-* { LIBS *= "-framework IOKit" "-framework SystemConfiguration" @@ -19,3 +19,4 @@ QT *= core SOURCES += \ ../../testing/testingmain.cpp \ serdes_tests.cpp \ + serdes_json_tests.cpp \ |