[search] Added a json-serializable struct for search data samples.

This commit is contained in:
Maxim Pimenov 2016-02-09 19:23:48 +03:00 committed by Sergey Yershov
parent 1c0c7b7a3a
commit 6a37fd2ee0
6 changed files with 414 additions and 5 deletions

View file

@ -6,10 +6,8 @@
#include <jansson.h>
namespace my
{
class Json
{
JsonHandle m_handle;
@ -27,5 +25,4 @@ public:
json_t * get() const { return m_handle.get(); }
};
}
} // namespace my

191
search/sample.cpp Normal file
View file

@ -0,0 +1,191 @@
#include "sample.hpp"
#include "base/logging.hpp"
#include "base/string_utils.hpp"
#include "std/sstream.hpp"
#include "std/string.hpp"
#include "3party/jansson/myjansson.hpp"
namespace
{
void ParsePosition(json_t * json, m2::PointD & pos)
{
if (!json_is_object(json))
MYTHROW(my::Json::Exception, ("Position must be a json object."));
json_t * posX = json_object_get(json, "x");
json_t * posY = json_object_get(json, "y");
if (!posX || !posY)
MYTHROW(my::Json::Exception, ("Position must have both x and y set."));
if (!json_is_number(posX) || !json_is_number(posY))
MYTHROW(my::Json::Exception, ("Position's x and y must be numbers."));
pos.x = json_number_value(posX);
pos.y = json_number_value(posY);
}
string ParseObligatoryString(json_t * root, string const & fieldName)
{
json_t * str = json_object_get(root, fieldName.c_str());
if (!str)
MYTHROW(my::Json::Exception, ("Obligatory field", fieldName, "is absent."));
if (!json_is_string(str))
MYTHROW(my::Json::Exception, ("The field", fieldName, "must contain a json string."));
return string(json_string_value(str));
}
void ParseResult(json_t * root, search::Sample::Result & result)
{
json_t * position = json_object_get(root, "position");
if (!position)
MYTHROW(my::Json::Exception, ("Obligatory field", "position", "is absent."));
ParsePosition(position, result.m_pos);
result.m_name = strings::MakeUniString(ParseObligatoryString(root, "name"));
result.m_houseNumber = ParseObligatoryString(root, "houseNumber");
json_t * types = json_object_get(root, "types");
if (!types)
MYTHROW(my::Json::Exception, ("Obligatory field", "types", "is absent."));
if (!json_is_array(types))
MYTHROW(my::Json::Exception, ("The field", "types", "must contain a json array."));
size_t const numTypes = json_array_size(types);
result.m_types.resize(numTypes);
for (size_t i = 0; i < numTypes; ++i)
{
json_t * type = json_array_get(types, i);
if (!json_is_string(type))
MYTHROW(my::Json::Exception, ("Result types must be strings."));
result.m_types[i] = json_string_value(type);
}
string relevance = ParseObligatoryString(root, "relevancy");
if (relevance == "vital")
result.m_relevance = search::Sample::Result::Relevance::RELEVANCE_VITAL;
else if (relevance == "relevant")
result.m_relevance = search::Sample::Result::Relevance::RELEVANCE_RELEVANT;
else
result.m_relevance = search::Sample::Result::Relevance::RELEVANCE_IRRELEVANT;
}
} // namespace
namespace search
{
bool Sample::DeserializeFromJSON(string const & jsonStr)
{
try
{
my::Json root(jsonStr.c_str());
DeserializeFromJSONImpl(root.get());
return true;
}
catch (my::Json::Exception const & e)
{
LOG(LDEBUG, ("Can't parse sample:", e.Msg(), jsonStr));
}
return false;
}
// static
bool Sample::DeserializeFromJSON(string const & jsonStr, vector<Sample> & samples)
{
try
{
my::Json root(jsonStr.c_str());
if (!json_is_array(root.get()))
MYTHROW(my::Json::Exception, ("The field", "samples", "must contain a json array."));
size_t numSamples = json_array_size(root.get());
samples.resize(numSamples);
for (size_t i = 0; i < numSamples; ++i)
samples[i].DeserializeFromJSONImpl(json_array_get(root.get(), i));
return true;
}
catch (my::Json::Exception const & e)
{
LOG(LERROR, ("Can't parse samples:", e.Msg(), jsonStr));
}
return false;
}
string Sample::ToStringDebug() const
{
ostringstream oss;
oss << "[";
oss << "query: " << DebugPrint(m_query) << " ";
oss << "locale: " << m_locale << " ";
oss << "pos: " << DebugPrint(m_pos) << " ";
oss << "viewport: " << DebugPrint(m_viewport) << " ";
oss << "results: [";
for (size_t i = 0; i < m_results.size(); ++i)
{
if (i > 0)
oss << " ";
oss << DebugPrint(m_results[i]);
}
oss << "]";
return oss.str();
}
void Sample::DeserializeFromJSONImpl(json_t * root)
{
m_query = strings::MakeUniString(ParseObligatoryString(root, "query"));
m_locale = ParseObligatoryString(root, "locale");
json_t * position = json_object_get(root, "position");
if (!position)
MYTHROW(my::Json::Exception, ("Obligatory field", "position", "is absent."));
ParsePosition(position, m_pos);
json_t * viewport = json_object_get(root, "viewport");
if (!viewport)
MYTHROW(my::Json::Exception, ("Obligatory field", "viewport", "is absent."));
m_viewport.setMinX(json_number_value(json_object_get(viewport, "minx")));
m_viewport.setMinY(json_number_value(json_object_get(viewport, "miny")));
m_viewport.setMaxX(json_number_value(json_object_get(viewport, "maxx")));
m_viewport.setMaxY(json_number_value(json_object_get(viewport, "maxy")));
json_t * results = json_object_get(root, "results");
if (!results)
MYTHROW(my::Json::Exception, ("Obligatory field", "results", "is absent."));
if (!json_is_array(results))
MYTHROW(my::Json::Exception, ("The field", "results", "must contain a json array."));
size_t numResults = json_array_size(results);
m_results.resize(numResults);
for (size_t i = 0; i < numResults; ++i)
ParseResult(json_array_get(results, i), m_results[i]);
}
string DebugPrint(Sample::Result::Relevance r)
{
switch (r)
{
case Sample::Result::RELEVANCE_IRRELEVANT: return "Irrelevant";
case Sample::Result::RELEVANCE_RELEVANT: return "Relevant";
case Sample::Result::RELEVANCE_VITAL: return "Vital";
}
return "Unknown";
}
string DebugPrint(Sample::Result const & r)
{
ostringstream oss;
oss << "relevance: " << DebugPrint(r.m_relevance) << " ";
oss << "name: " << DebugPrint(r.m_name) << " ";
oss << "house number: " << r.m_houseNumber << " ";
oss << "pos: " << r.m_pos << " ";
oss << "types: [";
for (size_t i = 0; i < r.m_types.size(); ++i)
{
if (i > 0)
oss << " ";
oss << r.m_types[i];
}
oss << "]";
return oss.str();
}
string DebugPrint(Sample const & s) { return s.ToStringDebug(); }
} // namespace search

54
search/sample.hpp Normal file
View file

@ -0,0 +1,54 @@
#pragma once
#include "geometry/point2d.hpp"
#include "geometry/rect2d.hpp"
#include "base/string_utils.hpp"
#include "std/string.hpp"
#include "3party/jansson/myjansson.hpp"
namespace search
{
class Sample
{
public:
struct Result
{
enum Relevance
{
RELEVANCE_IRRELEVANT,
RELEVANCE_RELEVANT,
RELEVANCE_VITAL
};
m2::PointD m_pos;
strings::UniString m_name;
string m_houseNumber;
vector<string> m_types; // MAPS.ME types, not OSM types.
Relevance m_relevance;
};
bool DeserializeFromJSON(string const &);
static bool DeserializeFromJSON(string const &, vector<Sample> &);
string ToStringDebug() const;
private:
void DeserializeFromJSONImpl(json_t *);
strings::UniString m_query;
string m_locale;
m2::PointD m_pos = m2::PointD(0, 0);
m2::RectD m_viewport = m2::RectD(0, 0, 0, 0);
vector<Result> m_results;
};
string DebugPrint(Sample::Result::Relevance);
string DebugPrint(Sample::Result const &);
string DebugPrint(Sample const &);
} // namespace search

View file

@ -8,6 +8,8 @@ ROOT_DIR = ..
include($$ROOT_DIR/common.pri)
INCLUDEPATH += $$ROOT_DIR/3party/jansson/src
HEADERS += \
algos.hpp \
approximate_string_match.hpp \
@ -32,6 +34,7 @@ HEADERS += \
result.hpp \
retrieval.hpp \
reverse_geocoder.hpp \
sample.hpp \
search_common.hpp \
search_engine.hpp \
search_index_values.hpp \
@ -78,6 +81,7 @@ SOURCES += \
result.cpp \
retrieval.cpp \
reverse_geocoder.cpp \
sample.cpp \
search_engine.cpp \
search_query.cpp \
search_query_params.cpp \

View file

@ -0,0 +1,160 @@
#include "testing/testing.hpp"
#include "search/sample.hpp"
#include "base/logging.hpp"
using namespace search;
UNIT_TEST(Sample_Smoke)
{
auto const jsonStr = R"EOF(
{
"query": "cuba",
"locale": "en",
"position": {
"x": 37.618706,
"y": 99.53730574302003
},
"viewport": {
"minx": 37.1336,
"miny": 67.1349,
"maxx": 38.0314,
"maxy": 67.7348
},
"results": [
{
"name": "Cuba",
"relevancy": "relevant",
"types": [
"place-country"
],
"position": {
"x": -80.832886,
"y": 15.521132748163712
},
"houseNumber": ""
}
]
}
)EOF";
Sample s;
TEST(s.DeserializeFromJSON(jsonStr), ());
LOG(LINFO, (DebugPrint(s)));
}
UNIT_TEST(Sample_BadViewport)
{
auto const jsonStr = R"EOF(
{
"results": [
{
"houseNumber": "",
"position": {
"y": 15.521132748163712,
"x": -80.832886
},
"types": [
"place-country"
],
"relevancy": "relevant",
"name": "Cuba"
}
],
"viewport": {
"maxy": 67.7348,
"maxx": 38.0314,
},
"position": {
"y": 99.53730574302003,
"x": 37.618706
},
"locale": "en",
"query": "cuba"
}
)EOF";
Sample s;
TEST(!s.DeserializeFromJSON(jsonStr), ());
}
UNIT_TEST(Sample_Arrays)
{
auto const jsonStr = R"EOF(
[
{
"query": "cuba",
"locale": "en",
"position": {
"x": 37.618706,
"y": 99.53730574302003
},
"viewport": {
"minx": 37.1336,
"miny": 67.1349,
"maxx": 38.0314,
"maxy": 67.7348
},
"results": [
{
"name": "Cuba",
"relevancy": "relevant",
"types": [
"place-country"
],
"position": {
"x": -80.832886,
"y": 15.521132748163712
},
"houseNumber": ""
}
]
},
{
"query": "riga",
"locale": "en",
"position": {
"x": 37.65376,
"y": 98.51110651930014
},
"viewport": {
"minx": 37.5064,
"miny": 67.0476,
"maxx": 37.7799,
"maxy": 67.304
},
"results": [
{
"name": "R\u012bga",
"relevancy": "vital",
"types": [
"place-city-capital-2"
],
"position": {
"x": 24.105186,
"y": 107.7819569220319
},
"houseNumber": ""
},
{
"name": "R\u012bga",
"relevancy": "vital",
"types": [
"place-city-capital-2"
],
"position": {
"x": 24.105186,
"y": 107.7819569220319
},
"houseNumber": ""
}
]
}
]
)EOF";
vector<Sample> samples;
TEST(Sample::DeserializeFromJSON(jsonStr, samples), ());
}

View file

@ -6,10 +6,12 @@ CONFIG -= app_bundle
TEMPLATE = app
ROOT_DIR = ../..
DEPENDENCIES = search indexer platform editor geometry coding base protobuf tomcrypt succinct pugixml
DEPENDENCIES = search indexer platform editor geometry coding base protobuf jansson tomcrypt succinct pugixml
include($$ROOT_DIR/common.pri)
INCLUDEPATH += $$ROOT_DIR/3party/jansson/src
QT *= core
macx-*: LIBS *= "-framework IOKit"
@ -27,6 +29,7 @@ SOURCES += \
locality_scorer_test.cpp \
query_saver_tests.cpp \
ranking_tests.cpp \
sample_test.cpp \
string_intersection_test.cpp \
string_match_test.cpp \