Skip to content

Commit 39fea58

Browse files
authored
feat: support map<string, string> to/from json string and string util (#45)
1 parent a2a6ec0 commit 39fea58

5 files changed

Lines changed: 106 additions & 15 deletions

File tree

src/paimon/common/utils/rapidjson_util.h

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,13 @@
3535
#include "rapidjson/writer.h"
3636

3737
namespace paimon {
38+
3839
class RapidJsonUtil {
3940
public:
4041
RapidJsonUtil() = delete;
4142
~RapidJsonUtil() = delete;
4243

43-
// supports vector and map and optional, if T is custom type, T must have ToJson()
44-
// noted that rapidjson does not support map with non string key (will
45-
// trigger assert in rapidjson: Assertion `name.IsString()' failed)
46-
// therefore, RapidJsonUtil convert key to string in serialize and convert string to key type in
47-
// deserialize
44+
// if T is custom type, T must have ToJson()
4845
template <typename T>
4946
static inline Status ToJsonString(const T& obj, std::string* json_str) {
5047
rapidjson::Document doc;
@@ -53,6 +50,9 @@ class RapidJsonUtil {
5350
try {
5451
if constexpr (is_pointer<T>::value) {
5552
value = obj->ToJson(&allocator);
53+
} else if constexpr (std::is_same_v<T, std::map<std::string, std::string>>) {
54+
*json_str = MapToJsonString(obj);
55+
return Status::OK();
5656
} else {
5757
value = obj.ToJson(&allocator);
5858
}
@@ -67,19 +67,27 @@ class RapidJsonUtil {
6767
return Status::OK();
6868
}
6969

70-
// supports vector and map, if T is custom type, T must have FromJson()
70+
// if T is custom type, T must have FromJson()
7171
template <typename T>
7272
static inline Status FromJsonString(const std::string& json_str, T* obj) {
73-
rapidjson::Document doc;
74-
if (!obj || !FromJson(json_str, &doc)) {
75-
return Status::Invalid("deserialize failed: ", json_str);
73+
if (!obj) {
74+
return Status::Invalid("deserialize failed: obj is nullptr");
7675
}
77-
try {
78-
obj->FromJson(doc);
79-
} catch (const std::invalid_argument& e) {
80-
return Status::Invalid("deserialize failed, possibly type incompatible: ", e.what());
81-
} catch (...) {
82-
return Status::Invalid("deserialize failed, reason unknown: ", json_str);
76+
if constexpr (std::is_same_v<T, std::map<std::string, std::string>>) {
77+
PAIMON_ASSIGN_OR_RAISE(*obj, MapFromJsonString(json_str));
78+
} else {
79+
rapidjson::Document doc;
80+
if (!FromJson(json_str, &doc)) {
81+
return Status::Invalid("deserialize failed: ", json_str);
82+
}
83+
try {
84+
obj->FromJson(doc);
85+
} catch (const std::invalid_argument& e) {
86+
return Status::Invalid("deserialize failed, possibly type incompatible: ",
87+
e.what());
88+
} catch (...) {
89+
return Status::Invalid("deserialize failed, reason unknown: ", json_str);
90+
}
8391
}
8492
return Status::OK();
8593
}
@@ -140,6 +148,42 @@ class RapidJsonUtil {
140148

141149
template <typename T>
142150
static T GetValue(const rapidjson::Value& value);
151+
152+
static std::string MapToJsonString(const std::map<std::string, std::string>& map) {
153+
rapidjson::Document d;
154+
d.SetObject();
155+
rapidjson::Document::AllocatorType& allocator = d.GetAllocator();
156+
157+
for (const auto& kv : map) {
158+
d.AddMember(rapidjson::Value(kv.first.c_str(), allocator),
159+
rapidjson::Value(kv.second.c_str(), allocator), allocator);
160+
}
161+
162+
rapidjson::StringBuffer buffer;
163+
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
164+
d.Accept(writer);
165+
166+
return buffer.GetString();
167+
}
168+
static Result<std::map<std::string, std::string>> MapFromJsonString(
169+
const std::string& json_str) {
170+
rapidjson::Document doc;
171+
doc.Parse(json_str.c_str());
172+
if (doc.HasParseError() || !doc.IsObject()) {
173+
return Status::Invalid("deserialize failed: parse error or not JSON object: ",
174+
json_str);
175+
}
176+
177+
std::map<std::string, std::string> result;
178+
for (auto it = doc.MemberBegin(); it != doc.MemberEnd(); ++it) {
179+
if (!it->name.IsString() || !it->value.IsString()) {
180+
return Status::Invalid(
181+
"deserialize failed: non-string key or value in JSON object: ", json_str);
182+
}
183+
result[it->name.GetString()] = it->value.GetString();
184+
}
185+
return result;
186+
}
143187
};
144188

145189
template <typename T>

src/paimon/common/utils/rapidjson_util_test.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <vector>
2323

2424
#include "gtest/gtest.h"
25+
#include "paimon/testing/utils/testharness.h"
2526
#include "rapidjson/allocators.h"
2627
#include "rapidjson/document.h"
2728
#include "rapidjson/rapidjson.h"
@@ -129,4 +130,15 @@ TEST(RapidJsonUtilTest, TestSerializeAndDeserialize) {
129130
ASSERT_EQ(2.333, non_exist_value);
130131
}
131132

133+
TEST(RapidJsonUtilTest, TestMapJsonString) {
134+
std::map<std::string, std::string> m1 = {{"key1", "value1"}, {"key2", "value2"}};
135+
std::string result;
136+
ASSERT_OK(RapidJsonUtil::ToJsonString(m1, &result));
137+
ASSERT_EQ(result, "{\"key1\":\"value1\",\"key2\":\"value2\"}");
138+
139+
std::map<std::string, std::string> m2;
140+
ASSERT_OK(RapidJsonUtil::FromJsonString(result, &m2));
141+
ASSERT_EQ(m1, m2);
142+
}
143+
132144
} // namespace paimon::test

src/paimon/common/utils/string_utils.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,16 @@ std::string StringUtils::Replace(const std::string& text, const std::string& sea
4040
return str;
4141
}
4242

43+
std::string StringUtils::ReplaceLast(const std::string& text, const std::string& old_str,
44+
const std::string& new_str) {
45+
std::string str = text;
46+
size_t pos = str.rfind(old_str);
47+
if (pos != std::string::npos) {
48+
str.replace(pos, old_str.size(), new_str);
49+
}
50+
return str;
51+
}
52+
4353
bool StringUtils::StartsWith(const std::string& str, const std::string& prefix, size_t start_pos) {
4454
return (str.size() >= prefix.size()) && (str.compare(start_pos, prefix.size(), prefix) == 0);
4555
}

src/paimon/common/utils/string_utils.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ class PAIMON_EXPORT StringUtils {
9898
static std::string Replace(const std::string& text, const std::string& search_string,
9999
const std::string& replacement, int32_t max);
100100

101+
static std::string ReplaceLast(const std::string& text, const std::string& old_str,
102+
const std::string& new_str);
103+
101104
static bool StartsWith(const std::string& str, const std::string& prefix, size_t start_pos = 0);
102105

103106
static bool EndsWith(const std::string& str, const std::string& suffix);

src/paimon/common/utils/string_utils_test.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,28 @@ TEST_F(StringUtilsTest, TestReplaceAll) {
113113
}
114114
}
115115

116+
TEST_F(StringUtilsTest, TestReplaceLast) {
117+
{
118+
std::string origin = "a/b/c//";
119+
std::string expect = "a/b/c/_";
120+
std::string actual = StringUtils::ReplaceLast(origin, "/", "_");
121+
ASSERT_EQ(expect, actual);
122+
}
123+
{
124+
std::string origin = "a/b/c//";
125+
std::string expect = "a/b/c//";
126+
std::string actual = StringUtils::ReplaceLast(origin, "_", "/");
127+
ASSERT_EQ(expect, actual);
128+
}
129+
130+
{
131+
std::string origin = "how is is you";
132+
std::string expect = "how is are you";
133+
std::string actual = StringUtils::ReplaceLast(origin, "is", "are");
134+
ASSERT_EQ(expect, actual);
135+
}
136+
}
137+
116138
TEST_F(StringUtilsTest, TestReplaceWithMaxCount) {
117139
{
118140
std::string origin = "how is is you";

0 commit comments

Comments
 (0)