leaves12138 commented on code in PR #23:
URL: https://github.com/apache/paimon-cpp/pull/23#discussion_r3310583595


##########
src/paimon/common/utils/rapidjson_util.h:
##########
@@ -0,0 +1,441 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+#pragma once
+
+#include <cassert>
+#include <cstdint>
+#include <map>
+#include <optional>
+#include <stdexcept>
+#include <string>
+
+#include "paimon/common/utils/string_utils.h"
+#include "paimon/status.h"
+#include "paimon/traits.h"
+#include "rapidjson/allocators.h"
+#include "rapidjson/document.h"
+#include "rapidjson/encodings.h"
+#include "rapidjson/prettywriter.h"
+#include "rapidjson/rapidjson.h"
+#include "rapidjson/stringbuffer.h"
+#include "rapidjson/writer.h"
+
+namespace paimon {
+
+class RapidJsonUtil {
+ public:
+    RapidJsonUtil() = delete;
+    ~RapidJsonUtil() = delete;
+
+    // if T is custom type, T must have ToJson()
+    template <typename T>
+    static inline Status ToJsonString(const T& obj, std::string* json_str) {
+        rapidjson::Document doc;
+        rapidjson::Document::AllocatorType& allocator = doc.GetAllocator();
+        rapidjson::Value value;
+        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);
+            }
+        } catch (const std::invalid_argument& e) {
+            return Status::Invalid("json serialize failed:", e.what());
+        } catch (...) {
+            return Status::Invalid("json serialize failed, unknown error");
+        }
+        if (!ToJson(value, json_str)) {
+            return Status::Invalid("serialize failed");
+        }
+        return Status::OK();
+    }
+
+    // if T is custom type, T must have FromJson()
+    template <typename T>
+    static inline Status FromJsonString(const std::string& json_str, T* obj) {
+        if (!obj) {
+            return Status::Invalid("deserialize failed: obj is nullptr");
+        }
+        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();
+    }
+
+    // if T is std::nullopt, will use rapid_json null_value
+    template <typename T>
+    static rapidjson::Value SerializeValue(const T& obj,
+                                           rapidjson::Document::AllocatorType* 
allocator);
+
+    // condition1 : has key & value -> return value
+    // condition2 : no key or null value -> return default value
+    template <typename T>
+    static T DeserializeKeyValue(const rapidjson::Value& value, const 
std::string& key,
+                                 const T& default_value);
+
+    // condition1 : has key & value -> return value
+    // condition2 : no key or null value, T is optional -> return std::nullopt
+    // condition3 : no key or null value, T is not optional -> throw exception
+    template <typename T>
+    static T DeserializeKeyValue(const rapidjson::Value& value, const 
std::string& key);
+
+    template <typename T>
+    static T DeserializeValue(const rapidjson::Value& value);
+
+ private:
+    static inline bool ToJson(const rapidjson::Value& value, std::string* 
json_str) {
+        assert(json_str);
+        rapidjson::StringBuffer buffer;
+        rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buffer);
+        if (!value.Accept(writer)) {
+            return false;
+        }
+        *json_str = buffer.GetString();
+        return true;
+    }
+
+    static inline bool FromJson(const std::string& json_str, 
rapidjson::Document* doc) {
+        doc->Parse(json_str.c_str());
+        if (doc->HasParseError()) {
+            return false;
+        }
+        return true;
+    }
+
+    template <typename T>
+    static rapidjson::Value SerializeMap(const T& map,
+                                         rapidjson::Document::AllocatorType* 
allocator);
+
+    template <typename T>
+    static rapidjson::Value SerializeVector(const T& vec,
+                                            
rapidjson::Document::AllocatorType* allocator);
+
+    template <typename T>
+    static T DeserializeVector(const rapidjson::Value& value);
+
+    template <typename T>
+    static T DeserializeMap(const rapidjson::Value& value);
+
+    template <typename T>
+    static T GetValue(const rapidjson::Value& value);
+
+    static std::string 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();
+    }
+    static Result<std::map<std::string, std::string>> 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;
+    }
+};
+
+template <typename T>
+inline rapidjson::Value RapidJsonUtil::SerializeValue(
+    const T& obj, rapidjson::Document::AllocatorType* allocator) {
+    if constexpr (is_optional<T>::value) {
+        if (obj == std::nullopt) {
+            rapidjson::Value null_value;
+            null_value.SetNull();
+            return null_value;
+        } else {
+            return SerializeValue(obj.value(), allocator);
+        }
+    } else {
+        if constexpr (std::is_same_v<T, std::string>) {
+            return rapidjson::Value(obj.c_str(), *allocator);
+        } else if constexpr (std::is_arithmetic_v<T>) {
+            return rapidjson::Value(obj);
+        } else if constexpr (is_map<T>::value) {
+            return SerializeMap(obj, allocator);
+        } else if constexpr (is_vector<T>::value) {
+            return SerializeVector(obj, allocator);
+        } else {
+            // custom type
+            return obj.ToJson(allocator);
+        }
+    }
+}
+
+template <typename T>
+inline rapidjson::Value RapidJsonUtil::SerializeMap(const T& map,
+                                                    
rapidjson::Document::AllocatorType* allocator) {
+    rapidjson::Value value(rapidjson::kObjectType);
+    using K = typename T::key_type;
+    for (const auto& kv : map) {
+        rapidjson::Value key;
+        if constexpr (std::is_same_v<K, std::string>) {
+            key = SerializeValue(kv.first, allocator);
+        } else if constexpr (std::is_same_v<T, bool>) {
+            throw std::invalid_argument("map key cannot be bool");
+        } else {
+            std::string key_str = std::to_string(kv.first);
+            key = SerializeValue(key_str, allocator);
+        }
+        rapidjson::Value val = SerializeValue(kv.second, allocator);
+        value.AddMember(key, val, *allocator);
+    }
+    return value;
+}
+
+template <typename T>
+inline rapidjson::Value RapidJsonUtil::SerializeVector(
+    const T& vec, rapidjson::Document::AllocatorType* allocator) {
+    rapidjson::Value value(rapidjson::kArrayType);
+    using V = typename T::value_type;
+    for (const V& item : vec) {
+        value.PushBack(SerializeValue(item, allocator), *allocator);
+    }
+    return value;
+}
+
+template <typename T>
+inline T RapidJsonUtil::DeserializeKeyValue(const rapidjson::Value& value, 
const std::string& key) {
+    if (!value.IsObject()) {
+        throw std::invalid_argument("value must be an object");
+    }
+    if constexpr (is_optional<T>::value) {
+        if (!value.HasMember(key.c_str()) || value[key].IsNull()) {
+            return std::nullopt;
+        } else {
+            return DeserializeValue<typename T::value_type>(value[key]);
+        }
+    } else {
+        if (!value.HasMember(key.c_str()) || value[key].IsNull()) {
+            throw std::invalid_argument("key must exist");
+        }
+        return DeserializeValue<T>(value[key]);
+    }
+}
+
+template <typename T>
+inline T RapidJsonUtil::DeserializeKeyValue(const rapidjson::Value& value, 
const std::string& key,
+                                            const T& default_value) {
+    if (!value.IsObject()) {
+        throw std::invalid_argument("value must be an object");
+    }
+    if (!value.HasMember(key.c_str()) || value[key].IsNull()) {
+        return default_value;
+    }
+    if constexpr (is_optional<T>::value) {
+        return DeserializeValue<typename T::value_type>(value[key]);
+    } else {
+        return DeserializeValue<T>(value[key]);
+    }
+}
+
+template <typename T>
+inline T RapidJsonUtil::DeserializeValue(const rapidjson::Value& value) {
+    if constexpr (is_vector<T>::value) {
+        return DeserializeVector<T>(value);
+    } else if constexpr (is_map<T>::value) {
+        return DeserializeMap<T>(value);
+    } else {
+        // arithmetic or string or custom type
+        return GetValue<T>(value);
+    }
+}
+
+template <typename T>
+inline T RapidJsonUtil::DeserializeVector(const rapidjson::Value& value) {
+    if (!value.IsArray()) {
+        throw std::invalid_argument("value must be an array");
+    }
+    T obj;
+    obj.reserve(value.Size());
+    using V = typename T::value_type;
+    for (const auto& item : value.GetArray()) {
+        obj.push_back(DeserializeValue<V>(item));
+    }
+    return obj;
+}
+
+template <typename T>
+inline T RapidJsonUtil::DeserializeMap(const rapidjson::Value& value) {
+    if (!value.IsObject()) {
+        throw std::invalid_argument("value must be an object");
+    }
+    using K = typename T::key_type;
+    using V = typename T::mapped_type;
+    T obj;
+    for (auto it = value.MemberBegin(); it != value.MemberEnd(); ++it) {
+        K key;
+        if constexpr (std::is_same_v<K, std::string>) {
+            key = DeserializeValue<K>(it->name);
+        } else {
+            auto key_str = DeserializeValue<std::string>(it->name);
+            auto optional_key = StringUtils::StringToValue<K>(key_str);
+            if (!optional_key) {
+                throw std::invalid_argument("key cannot be parse from string");
+            }
+            key = optional_key.value();
+        }
+        obj.emplace(key, DeserializeValue<V>(it->value));
+    }
+    return obj;
+}
+
+template <>
+inline bool RapidJsonUtil::GetValue<bool>(const rapidjson::Value& value) {
+    if (!value.IsBool()) {
+        throw std::invalid_argument("value must be bool");
+    }
+    return value.GetBool();
+}
+
+template <>
+inline int8_t RapidJsonUtil::GetValue<int8_t>(const rapidjson::Value& value) {
+    if (!value.IsInt()) {
+        throw std::invalid_argument("value must be int");
+    }
+    return static_cast<int8_t>(value.GetInt());

Review Comment:
   The small-integer JSON readers only check RapidJSON's broad `IsInt()` / 
`IsUint()` predicates, then cast to `int8_t` / `uint8_t` / `int16_t` / 
`uint16_t`. This silently wraps out-of-range JSON values, e.g. deserializing 
`500` as `int8_t` yields a truncated value instead of failing. Since 
`StringUtils::StringToValue` already rejects the same overflow cases, JSON 
deserialization should be consistent: check `value.GetInt()` / `GetUint()` 
against `std::numeric_limits<T>::min()/max()` before casting, and add overflow 
tests for the narrow integer types.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to