diff --git a/src/iceberg/json_internal.cc b/src/iceberg/json_internal.cc index ad470a22..2be95c1e 100644 --- a/src/iceberg/json_internal.cc +++ b/src/iceberg/json_internal.cc @@ -42,6 +42,7 @@ #include "iceberg/transform.h" #include "iceberg/type.h" #include "iceberg/util/formatter.h" // IWYU pragma: keep +#include "iceberg/util/json_util_internal.h" #include "iceberg/util/macros.h" #include "iceberg/util/timepoint.h" @@ -166,185 +167,6 @@ constexpr std::string_view kFileSizeInBytes = "file-size-in-bytes"; constexpr std::string_view kFileFooterSizeInBytes = "file-footer-size-in-bytes"; constexpr std::string_view kBlobMetadata = "blob-metadata"; -template -void SetOptionalField(nlohmann::json& json, std::string_view key, - const std::optional& value) { - if (value.has_value()) { - json[key] = *value; - } -} - -std::string SafeDumpJson(const nlohmann::json& json) { - return json.dump(/*indent=*/-1, /*indent_char=*/' ', /*ensure_ascii=*/false, - nlohmann::detail::error_handler_t::ignore); -} - -template -Result GetJsonValueImpl(const nlohmann::json& json, std::string_view key) { - try { - return json.at(key).get(); - } catch (const std::exception& ex) { - return JsonParseError("Failed to parse '{}' from {}: {}", key, SafeDumpJson(json), - ex.what()); - } -} - -template -Result> GetJsonValueOptional(const nlohmann::json& json, - std::string_view key) { - if (!json.contains(key)) { - return std::nullopt; - } - return GetJsonValueImpl(json, key); -} - -template -Result GetJsonValue(const nlohmann::json& json, std::string_view key) { - if (!json.contains(key)) { - return JsonParseError("Missing '{}' in {}", key, SafeDumpJson(json)); - } - return GetJsonValueImpl(json, key); -} - -template -Result GetJsonValueOrDefault(const nlohmann::json& json, std::string_view key, - T default_value = T{}) { - if (!json.contains(key)) { - return default_value; - } - return GetJsonValueImpl(json, key); -} - -/// \brief Convert a list of items to a json array. -/// -/// Note that ToJson(const T&) is required for this function to work. -template -nlohmann::json::array_t ToJsonList(const std::vector& list) { - return std::accumulate(list.cbegin(), list.cend(), nlohmann::json::array(), - [](nlohmann::json::array_t arr, const T& item) { - arr.push_back(ToJson(item)); - return arr; - }); -} - -/// \brief Overload of the above function for a list of shared pointers. -template -nlohmann::json::array_t ToJsonList(const std::vector>& list) { - return std::accumulate(list.cbegin(), list.cend(), nlohmann::json::array(), - [](nlohmann::json::array_t arr, const std::shared_ptr& item) { - arr.push_back(ToJson(*item)); - return arr; - }); -} - -/// \brief Parse a list of items from a JSON object. -/// -/// \param[in] json The JSON object to parse. -/// \param[in] key The key to parse. -/// \param[in] from_json The function to parse an item from a JSON object. -/// \return The list of items. -template -Result> FromJsonList( - const nlohmann::json& json, std::string_view key, - const std::function(const nlohmann::json&)>& from_json) { - std::vector list{}; - if (json.contains(key)) { - ICEBERG_ASSIGN_OR_RAISE(auto list_json, GetJsonValue(json, key)); - if (!list_json.is_array()) { - return JsonParseError("Cannot parse '{}' from non-array: {}", key, - SafeDumpJson(list_json)); - } - for (const auto& entry_json : list_json) { - ICEBERG_ASSIGN_OR_RAISE(auto entry, from_json(entry_json)); - list.emplace_back(std::move(entry)); - } - } - return list; -} - -/// \brief Parse a list of items from a JSON object. -/// -/// \param[in] json The JSON object to parse. -/// \param[in] key The key to parse. -/// \param[in] from_json The function to parse an item from a JSON object. -/// \return The list of items. -template -Result>> FromJsonList( - const nlohmann::json& json, std::string_view key, - const std::function>(const nlohmann::json&)>& from_json) { - std::vector> list{}; - if (json.contains(key)) { - ICEBERG_ASSIGN_OR_RAISE(auto list_json, GetJsonValue(json, key)); - if (!list_json.is_array()) { - return JsonParseError("Cannot parse '{}' from non-array: {}", key, - SafeDumpJson(list_json)); - } - for (const auto& entry_json : list_json) { - ICEBERG_ASSIGN_OR_RAISE(auto entry, from_json(entry_json)); - list.emplace_back(std::move(entry)); - } - } - return list; -} - -/// \brief Convert a map of type to a json object. -/// -/// Note that ToJson(const T&) is required for this function to work. -template -nlohmann::json::object_t ToJsonMap(const std::unordered_map& map) { - return std::accumulate(map.cbegin(), map.cend(), nlohmann::json::object(), - [](nlohmann::json::object_t obj, const auto& item) { - obj[item.first] = ToJson(item.second); - return obj; - }); -} - -/// \brief Overload of the above function for a map of type >. -template -nlohmann::json::object_t ToJsonMap( - const std::unordered_map>& map) { - return std::accumulate(map.cbegin(), map.cend(), nlohmann::json::object(), - [](nlohmann::json::object_t obj, const auto& item) { - obj[item.first] = ToJson(*item.second); - return obj; - }); -} - -/// \brief Parse a map of type from a JSON object. -/// -/// \param[in] json The JSON object to parse. -/// \param[in] key The key to parse. -/// \param[in] from_json The function to parse an item from a JSON object. -/// \return The map of items. -template -Result> FromJsonMap( - const nlohmann::json& json, std::string_view key, - const std::function(const nlohmann::json&)>& from_json = - [](const nlohmann::json& json) -> Result { - static_assert(std::is_same_v, "T must be std::string"); - try { - return json.get(); - } catch (const std::exception& ex) { - return JsonParseError("Cannot parse {} to a string value: {}", SafeDumpJson(json), - ex.what()); - } - }) { - std::unordered_map map{}; - if (json.contains(key)) { - ICEBERG_ASSIGN_OR_RAISE(auto map_json, GetJsonValue(json, key)); - if (!map_json.is_object()) { - return JsonParseError("Cannot parse '{}' from non-object: {}", key, - SafeDumpJson(map_json)); - } - for (const auto& [key, value] : map_json.items()) { - ICEBERG_ASSIGN_OR_RAISE(auto entry, from_json(value)); - map[key] = std::move(entry); - } - } - return map; -} - } // namespace nlohmann::json ToJson(const SortField& sort_field) { diff --git a/src/iceberg/util/json_util_internal.h b/src/iceberg/util/json_util_internal.h new file mode 100644 index 00000000..c525ac90 --- /dev/null +++ b/src/iceberg/util/json_util_internal.h @@ -0,0 +1,214 @@ +/* + * 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 + +#include + +#include "iceberg/result.h" +#include "iceberg/util/macros.h" + +/// \file iceberg/util/json_util_internal.h +/// \brief Internal utilities for JSON serialization and deserialization. + +namespace iceberg { + +template +void SetOptionalField(nlohmann::json& json, std::string_view key, + const std::optional& value) { + if (value.has_value()) { + json[key] = *value; + } +} + +inline std::string SafeDumpJson(const nlohmann::json& json) { + return json.dump(/*indent=*/-1, /*indent_char=*/' ', /*ensure_ascii=*/false, + nlohmann::detail::error_handler_t::ignore); +} + +template +Result GetJsonValueImpl(const nlohmann::json& json, std::string_view key) { + try { + return json.at(key).get(); + } catch (const std::exception& ex) { + return JsonParseError("Failed to parse '{}' from {}: {}", key, SafeDumpJson(json), + ex.what()); + } +} + +template +Result> GetJsonValueOptional(const nlohmann::json& json, + std::string_view key) { + if (!json.contains(key)) { + return std::nullopt; + } + ICEBERG_ASSIGN_OR_RAISE(auto value, GetJsonValueImpl(json, key)); + return std::optional(std::move(value)); +} + +template +Result GetJsonValue(const nlohmann::json& json, std::string_view key) { + if (!json.contains(key)) { + return JsonParseError("Missing '{}' in {}", key, SafeDumpJson(json)); + } + return GetJsonValueImpl(json, key); +} + +template +Result GetJsonValueOrDefault(const nlohmann::json& json, std::string_view key, + T default_value = T{}) { + if (!json.contains(key)) { + return default_value; + } + return GetJsonValueImpl(json, key); +} + +/// \brief Convert a list of items to a json array. +/// +/// Note that ToJson(const T&) is required for this function to work. +template +nlohmann::json::array_t ToJsonList(const std::vector& list) { + return std::accumulate(list.cbegin(), list.cend(), nlohmann::json::array(), + [](nlohmann::json::array_t arr, const T& item) { + arr.push_back(ToJson(item)); + return arr; + }); +} + +/// \brief Overload of the above function for a list of shared pointers. +template +nlohmann::json::array_t ToJsonList(const std::vector>& list) { + return std::accumulate(list.cbegin(), list.cend(), nlohmann::json::array(), + [](nlohmann::json::array_t arr, const std::shared_ptr& item) { + arr.push_back(ToJson(*item)); + return arr; + }); +} + +/// \brief Parse a list of items from a JSON object. +/// +/// \param[in] json The JSON object to parse. +/// \param[in] key The key to parse. +/// \param[in] from_json The function to parse an item from a JSON object. +/// \return The list of items. +template +Result> FromJsonList( + const nlohmann::json& json, std::string_view key, + const std::function(const nlohmann::json&)>& from_json) { + std::vector list{}; + if (json.contains(key)) { + ICEBERG_ASSIGN_OR_RAISE(auto list_json, GetJsonValue(json, key)); + if (!list_json.is_array()) { + return JsonParseError("Cannot parse '{}' from non-array: {}", key, + SafeDumpJson(list_json)); + } + for (const auto& entry_json : list_json) { + ICEBERG_ASSIGN_OR_RAISE(auto entry, from_json(entry_json)); + list.emplace_back(std::move(entry)); + } + } + return list; +} + +/// \brief Parse a list of items from a JSON object. +/// +/// \param[in] json The JSON object to parse. +/// \param[in] key The key to parse. +/// \param[in] from_json The function to parse an item from a JSON object. +/// \return The list of items. +template +Result>> FromJsonList( + const nlohmann::json& json, std::string_view key, + const std::function>(const nlohmann::json&)>& from_json) { + std::vector> list{}; + if (json.contains(key)) { + ICEBERG_ASSIGN_OR_RAISE(auto list_json, GetJsonValue(json, key)); + if (!list_json.is_array()) { + return JsonParseError("Cannot parse '{}' from non-array: {}", key, + SafeDumpJson(list_json)); + } + for (const auto& entry_json : list_json) { + ICEBERG_ASSIGN_OR_RAISE(auto entry, from_json(entry_json)); + list.emplace_back(std::move(entry)); + } + } + return list; +} + +/// \brief Convert a map of type to a json object. +/// +/// Note that ToJson(const T&) is required for this function to work. +template +nlohmann::json::object_t ToJsonMap(const std::unordered_map& map) { + return std::accumulate(map.cbegin(), map.cend(), nlohmann::json::object(), + [](nlohmann::json::object_t obj, const auto& item) { + obj[item.first] = ToJson(item.second); + return obj; + }); +} + +/// \brief Overload of the above function for a map of type >. +template +nlohmann::json::object_t ToJsonMap( + const std::unordered_map>& map) { + return std::accumulate(map.cbegin(), map.cend(), nlohmann::json::object(), + [](nlohmann::json::object_t obj, const auto& item) { + obj[item.first] = ToJson(*item.second); + return obj; + }); +} + +/// \brief Parse a map of type from a JSON object. +/// +/// \param[in] json The JSON object to parse. +/// \param[in] key The key to parse. +/// \param[in] from_json The function to parse an item from a JSON object. +/// \return The map of items. +template +Result> FromJsonMap( + const nlohmann::json& json, std::string_view key, + const std::function(const nlohmann::json&)>& from_json = + [](const nlohmann::json& json) -> Result { + static_assert(std::is_same_v, "T must be std::string"); + try { + return json.get(); + } catch (const std::exception& ex) { + return JsonParseError("Cannot parse {} to a string value: {}", SafeDumpJson(json), + ex.what()); + } + }) { + std::unordered_map map{}; + if (json.contains(key)) { + ICEBERG_ASSIGN_OR_RAISE(auto map_json, GetJsonValue(json, key)); + if (!map_json.is_object()) { + return JsonParseError("Cannot parse '{}' from non-object: {}", key, + SafeDumpJson(map_json)); + } + for (const auto& [key, value] : map_json.items()) { + ICEBERG_ASSIGN_OR_RAISE(auto entry, from_json(value)); + map[key] = std::move(entry); + } + } + return map; +} + +} // namespace iceberg