Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/paimon/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ set(PAIMON_COMMON_SRCS
common/utils/range.cpp
common/utils/roaring_bitmap32.cpp
common/utils/roaring_bitmap64.cpp
common/utils/rapidjson_util.cpp
common/utils/status.cpp
common/utils/string_utils.cpp)

Expand Down
57 changes: 57 additions & 0 deletions src/paimon/common/utils/rapidjson_util.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2026-present Alibaba Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "paimon/common/utils/rapidjson_util.h"

namespace paimon {

std::string RapidJsonUtil::MapToJsonString(const std::map<std::string, std::string>& map) {
rapidjson::Document d;
d.SetObject();
rapidjson::Document::AllocatorType& allocator = d.GetAllocator();

for (const auto& kv : map) {
d.AddMember(rapidjson::Value(kv.first.c_str(), allocator),
rapidjson::Value(kv.second.c_str(), allocator), allocator);
}

rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
d.Accept(writer);

return buffer.GetString();
}

Result<std::map<std::string, std::string>> RapidJsonUtil::MapFromJsonString(
const std::string& json_str) {
rapidjson::Document doc;
doc.Parse(json_str.c_str());
if (doc.HasParseError() || !doc.IsObject()) {
return Status::Invalid("deserialize failed: parse error or not JSON object: ", json_str);
}

std::map<std::string, std::string> result;
for (auto it = doc.MemberBegin(); it != doc.MemberEnd(); ++it) {
if (!it->name.IsString() || !it->value.IsString()) {
return Status::Invalid("deserialize failed: non-string key or value in JSON object: ",
json_str);
}
result[it->name.GetString()] = it->value.GetString();
}
return result;
}

} // namespace paimon
40 changes: 25 additions & 15 deletions src/paimon/common/utils/rapidjson_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,12 @@
#include "rapidjson/writer.h"

namespace paimon {

class RapidJsonUtil {
public:
RapidJsonUtil() = delete;
~RapidJsonUtil() = delete;

// supports vector and map and optional, if T is custom type, T must have ToJson()
// noted that rapidjson does not support map with non string key (will
// trigger assert in rapidjson: Assertion `name.IsString()' failed)
// therefore, RapidJsonUtil convert key to string in serialize and convert string to key type in
// deserialize
template <typename T>
static inline Status ToJsonString(const T& obj, std::string* json_str) {
rapidjson::Document doc;
Expand All @@ -53,6 +49,9 @@ class RapidJsonUtil {
try {
if constexpr (is_pointer<T>::value) {
value = obj->ToJson(&allocator);
} else if constexpr (std::is_same_v<T, std::map<std::string, std::string>>) {
*json_str = MapToJsonString(obj);
return Status::OK();
} else {
value = obj.ToJson(&allocator);
}
Expand All @@ -67,19 +66,26 @@ class RapidJsonUtil {
return Status::OK();
}

// supports vector and map, if T is custom type, T must have FromJson()
template <typename T>
static inline Status FromJsonString(const std::string& json_str, T* obj) {
rapidjson::Document doc;
if (!obj || !FromJson(json_str, &doc)) {
return Status::Invalid("deserialize failed: ", json_str);
if (!obj) {
return Status::Invalid("deserialize failed: obj is nullptr");
}
try {
obj->FromJson(doc);
} catch (const std::invalid_argument& e) {
return Status::Invalid("deserialize failed, possibly type incompatible: ", e.what());
} catch (...) {
return Status::Invalid("deserialize failed, reason unknown: ", json_str);
if constexpr (std::is_same_v<T, std::map<std::string, std::string>>) {
PAIMON_ASSIGN_OR_RAISE(*obj, MapFromJsonString(json_str));
} else {
rapidjson::Document doc;
if (!FromJson(json_str, &doc)) {
return Status::Invalid("deserialize failed: ", json_str);
}
try {
obj->FromJson(doc);
} catch (const std::invalid_argument& e) {
return Status::Invalid("deserialize failed, possibly type incompatible: ",
e.what());
} catch (...) {
return Status::Invalid("deserialize failed, reason unknown: ", json_str);
}
}
return Status::OK();
}
Expand Down Expand Up @@ -140,6 +146,10 @@ class RapidJsonUtil {

template <typename T>
static T GetValue(const rapidjson::Value& value);

static std::string MapToJsonString(const std::map<std::string, std::string>& map);
static Result<std::map<std::string, std::string>> MapFromJsonString(
const std::string& json_str);
};

template <typename T>
Expand Down
12 changes: 12 additions & 0 deletions src/paimon/common/utils/rapidjson_util_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <vector>

#include "gtest/gtest.h"
#include "paimon/testing/utils/testharness.h"
#include "rapidjson/allocators.h"
#include "rapidjson/document.h"
#include "rapidjson/rapidjson.h"
Expand Down Expand Up @@ -129,4 +130,15 @@ TEST(RapidJsonUtilTest, TestSerializeAndDeserialize) {
ASSERT_EQ(2.333, non_exist_value);
}

TEST(RapidJsonUtilTest, TestMapJsonString) {
std::map<std::string, std::string> m1 = {{"key1", "value1"}, {"key2", "value2"}};
std::string result;
ASSERT_OK(RapidJsonUtil::ToJsonString(m1, &result));
ASSERT_EQ(result, "{\"key1\":\"value1\",\"key2\":\"value2\"}");

std::map<std::string, std::string> m2;
ASSERT_OK(RapidJsonUtil::FromJsonString(result, &m2));
ASSERT_EQ(m1, m2);
}

} // namespace paimon::test
10 changes: 10 additions & 0 deletions src/paimon/common/utils/string_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ std::string StringUtils::Replace(const std::string& text, const std::string& sea
return str;
}

std::string StringUtils::ReplaceLast(const std::string& text, const std::string& old_str,
const std::string& new_str) {
std::string str = text;
size_t pos = str.rfind(old_str);
if (pos != std::string::npos) {
str.replace(pos, old_str.size(), new_str);
}
return str;
}

bool StringUtils::StartsWith(const std::string& str, const std::string& prefix, size_t start_pos) {
return (str.size() >= prefix.size()) && (str.compare(start_pos, prefix.size(), prefix) == 0);
}
Expand Down
3 changes: 3 additions & 0 deletions src/paimon/common/utils/string_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ class PAIMON_EXPORT StringUtils {
static std::string Replace(const std::string& text, const std::string& search_string,
const std::string& replacement, int32_t max);

static std::string ReplaceLast(const std::string& text, const std::string& old_str,
const std::string& new_str);

static bool StartsWith(const std::string& str, const std::string& prefix, size_t start_pos = 0);

static bool EndsWith(const std::string& str, const std::string& suffix);
Expand Down
22 changes: 22 additions & 0 deletions src/paimon/common/utils/string_utils_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,28 @@ TEST_F(StringUtilsTest, TestReplaceAll) {
}
}

TEST_F(StringUtilsTest, TestReplaceLast) {
{
std::string origin = "a/b/c//";
std::string expect = "a/b/c/_";
std::string actual = StringUtils::ReplaceLast(origin, "/", "_");
ASSERT_EQ(expect, actual);
}
{
std::string origin = "a/b/c//";
std::string expect = "a/b/c//";
std::string actual = StringUtils::ReplaceLast(origin, "_", "/");
ASSERT_EQ(expect, actual);
}

{
std::string origin = "how is is you";
std::string expect = "how is are you";
std::string actual = StringUtils::ReplaceLast(origin, "is", "are");
ASSERT_EQ(expect, actual);
}
}

TEST_F(StringUtilsTest, TestReplaceWithMaxCount) {
{
std::string origin = "how is is you";
Expand Down
Loading