Skip to content

Commit

Permalink
Fix json to string for custom objects when used in vectors
Browse files Browse the repository at this point in the history
  • Loading branch information
sjanel committed Dec 17, 2024
1 parent bb0459c commit 27be8a2
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 33 deletions.
2 changes: 1 addition & 1 deletion src/api/common/src/commonapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ CurrencyCodeVector CommonAPI::FiatsFunc::retrieveFiatsSource1() {

auto nbCurrencies = currencies.AlphabeticCode.size();
for (decltype(nbCurrencies) currencyPos = 0; currencyPos < nbCurrencies; ++currencyPos) {
if (currencies.WithdrawalDate[currencyPos].empty()) {
if (currencies.WithdrawalDate[currencyPos].empty() && !currencies.AlphabeticCode[currencyPos].empty()) {
fiatsVec.emplace_back(currencies.AlphabeticCode[currencyPos]);
log::debug("Stored {} fiat", fiatsVec.back());
}
Expand Down
2 changes: 1 addition & 1 deletion src/basic-objects/include/currencycode.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ template <>
struct to<JSON, ::cct::CurrencyCode> {
template <auto Opts, is_context Ctx, class B, class IX>
static void op(auto &&value, Ctx &&, B &&b, IX &&ix) {
::cct::details::ToJson<Opts>(value, b, ix);
::cct::details::ToStrLikeJson<Opts>(value, b, ix);
}
};
} // namespace glz::detail
24 changes: 15 additions & 9 deletions src/basic-objects/include/generic-object-json.hpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
#pragma once

#include <algorithm>
#include <cstdint>
#include <iterator>

#include "cct_cctype.hpp"
#include "cct_json-serialization.hpp"
Expand All @@ -11,19 +9,27 @@ namespace cct::details {

template <auto Opts, class B, class IX>
constexpr bool JsonWithQuotes(B &&b, IX &&ix) {
if (ix == 0) {
return false;
}

// This is a hack waiting for resolution of this issue: https://github.com/stephenberry/glaze/issues/1477
const char *pFirstChar = b.data();
const char *pChar = pFirstChar + ix - 1;

if constexpr (Opts.prettify) {
auto begIt = std::reverse_iterator(b.data() + ix);
auto endIt = std::reverse_iterator(b.data());
auto foundIfNotIt = std::find_if_not(begIt, endIt, [](char ch) { return isspace(ch); });
while (isspace(*pChar) && --pChar != pFirstChar);
}

return foundIfNotIt != endIt && *foundIfNotIt == ':';
} else {
return ix != 0 && b[ix - 1] == ':';
if (*pChar == ':') {
return true;
}

return *pChar != '"';
}

template <auto Opts, class B, class IX>
constexpr void ToJson(auto &&value, B &&b, IX &&ix) {
constexpr void ToStrLikeJson(auto &&value, B &&b, IX &&ix) {
auto valueLen = value.strLen();
bool withQuotes = JsonWithQuotes<Opts>(b, ix);

Expand Down
4 changes: 3 additions & 1 deletion src/basic-objects/include/market.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ class Market {
public:
enum class Type : int8_t { kRegularExchangeMarket, kFiatConversionMarket };

static constexpr auto kMaxLen = CurrencyCode::kMaxLen * 2 + 2U; // 1 for sep, 1 for '*' if fiat conversion

constexpr Market() noexcept(std::is_nothrow_default_constructible_v<CurrencyCode>) = default;

constexpr Market(CurrencyCode first, CurrencyCode second, Type type = Type::kRegularExchangeMarket)
Expand Down Expand Up @@ -172,7 +174,7 @@ template <>
struct to<JSON, ::cct::Market> {
template <auto Opts, is_context Ctx, class B, class IX>
static void op(auto &&value, Ctx &&, B &&b, IX &&ix) {
::cct::details::ToJson<Opts>(value, b, ix);
::cct::details::ToStrLikeJson<Opts>(value, b, ix);
}
};
} // namespace glz::detail
36 changes: 32 additions & 4 deletions src/basic-objects/include/monetaryamount.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <charconv>
#include <concepts>
#include <cstdint>
#include <cstring>
#include <iterator>
#include <limits>
#include <optional>
Expand Down Expand Up @@ -49,6 +50,7 @@ class MonetaryAmount {
enum class RoundType : int8_t { kDown, kUp, kNearest };

static constexpr std::size_t kMaxNbCharsAmount = std::numeric_limits<AmountType>::digits10 + 3;
static constexpr auto kMaxLen = kMaxNbCharsAmount + 1U + CurrencyCode::kMaxLen; // 1 for space

/// Constructs a MonetaryAmount with a value of 0 of neutral currency.
constexpr MonetaryAmount() noexcept : _amount(0) {}
Expand Down Expand Up @@ -306,10 +308,36 @@ class MonetaryAmount {
return std::copy_n(std::begin(amountBuf) + amountCharPos, nbDigits - amountCharPos, it);
}

// optimization for char buffers
char *appendAmount(char *it) const {
if (_amount < 0) {
*it++ = '-';
}

const auto nbDigits = ndigits(_amount);
auto nbDecs = nbDecimals();
int remNbZerosToPrint = std::max(0, nbDecs + 1 - nbDigits);

if (remNbZerosToPrint > 0) {
std::memcpy(it, "0.", 2);
it += 2;
if (--remNbZerosToPrint > 0) {
std::memset(it, '0', remNbZerosToPrint);
it += remNbZerosToPrint;
}
nbDecs = 0;
}
auto [ptr, ec] = std::to_chars(it, it + std::numeric_limits<AmountType>::digits10 + 1, std::abs(_amount));
if (nbDecs > 0) {
std::memmove(ptr - nbDecs + 1, ptr - nbDecs, nbDecs);
*(ptr - nbDecs) = '.';
++ptr;
}
return ptr;
}

/// @brief Appends a string representation of the amount plus its currency to given output iterator
/// @param it output iterator should have at least a capacity of
/// kMaxNbCharsAmount for the amount (explanation above)
/// + CurrencyCodeBase::kMaxLen + 1 for the currency and the space separator
/// @param it output iterator should have at least a capacity of MonetaryAmount::kMaxLen
template <class OutputIt>
OutputIt appendTo(OutputIt it) const {
it = appendAmount(it);
Expand Down Expand Up @@ -454,7 +482,7 @@ template <>
struct to<JSON, ::cct::MonetaryAmount> {
template <auto Opts, is_context Ctx, class B, class IX>
static void op(auto &&value, Ctx &&, B &&b, IX &&ix) {
::cct::details::ToJson<Opts>(value, b, ix);
::cct::details::ToStrLikeJson<Opts>(value, b, ix);
}
};
} // namespace glz::detail
3 changes: 2 additions & 1 deletion src/basic-objects/src/monetaryamount.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,9 @@ MonetaryAmount::MonetaryAmount(double amount, CurrencyCode currencyCode, RoundTy
const auto nbDigitsAmount = ndigits(_amount);
const auto szCurrencyCode = _curWithDecimals.size();
const auto nbDec = nbDecimals();

return static_cast<uint32_t>(static_cast<uint32_t>(_amount < 0) + nbDigitsAmount + static_cast<uint32_t>(nbDec != 0) +
static_cast<uint32_t>(nbDec >= nbDigitsAmount) +
(nbDec >= nbDigitsAmount ? (nbDec - nbDigitsAmount + 1) : 0) +
static_cast<uint32_t>(withSpace == WithSpace::kYes && szCurrencyCode != 0) +
szCurrencyCode);
}
Expand Down
35 changes: 30 additions & 5 deletions src/basic-objects/test/currencycode_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "cct_invalid_argument_exception.hpp"
#include "cct_json-serialization.hpp"
#include "cct_string.hpp"
#include "cct_vector.hpp"

namespace cct {

Expand Down Expand Up @@ -242,20 +243,33 @@ TEST(CurrencyCodeTest, JsonSerializationValue) {

EXPECT_FALSE(res);

EXPECT_EQ(buffer, R"({"currencyCode":"DOGE"})");
EXPECT_EQ(std::string_view(buffer), R"({"currencyCode":"DOGE"})");
}

using CurrencyCodeMap = std::map<CurrencyCode, bool>;
struct Bar {
vector<CurrencyCode> currencyCodes{"EUR", "DOGE"};
};

TEST(CurrencyCodeTest, JsonSerializationVector) {
Bar bar;

string buffer;
auto res = json::write<json::opts{.raw_string = true}>(bar, buffer); // NOLINT(readability-implicit-bool-conversion)

EXPECT_FALSE(res);

EXPECT_EQ(std::string_view(buffer), R"({"currencyCodes":["EUR","DOGE"]})");
}

TEST(CurrencyCodeTest, JsonSerializationKey) {
CurrencyCodeMap map{{"DOGE", true}, {"BTC", false}};
std::map<CurrencyCode, bool> map{{"DOGE", true}, {"BTC", false}};

string buffer;
auto res = json::write<json::opts{.raw_string = true}>(map, buffer); // NOLINT(readability-implicit-bool-conversion)
auto res = json::write<json::opts{.raw_string = true}>(map, buffer);

EXPECT_FALSE(res);

EXPECT_EQ(buffer, R"({"BTC":false,"DOGE":true})");
EXPECT_EQ(std::string_view(buffer), R"({"BTC":false,"DOGE":true})");
}

TEST(CurrencyCodeTest, JsonDeserialization) {
Expand All @@ -269,4 +283,15 @@ TEST(CurrencyCodeTest, JsonDeserialization) {
EXPECT_EQ(foo, Foo{"DOGE"});
}

TEST(CurrencyCodeTest, JsonDeserializationVector) {
vector<CurrencyCode> data;

// NOLINTNEXTLINE(readability-implicit-bool-conversion)
auto ec = json::read<json::opts{.raw_string = true}>(data, R"(["EUR","DOGE"])");

ASSERT_FALSE(ec);

EXPECT_EQ(data, vector<CurrencyCode>({"EUR", "DOGE"}));
}

} // namespace cct
20 changes: 17 additions & 3 deletions src/basic-objects/test/market_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "cct_exception.hpp"
#include "cct_json-serialization.hpp"
#include "cct_string.hpp"
#include "cct_vector.hpp"
#include "currencycode.hpp"

namespace cct {
Expand Down Expand Up @@ -87,10 +88,8 @@ TEST(MarketTest, JsonSerializationValue) {
EXPECT_EQ(buffer, R"({"market":"DOGE-BTC"})");
}

using MarketMap = std::map<Market, bool>;

TEST(MarketTest, JsonSerializationKey) {
MarketMap map{{Market{"DOGE", "BTC"}, true}, {Market{"BTC", "ETH"}, false}};
std::map<Market, bool> map{{Market{"DOGE", "BTC"}, true}, {Market{"BTC", "ETH"}, false}};

string buffer;
auto res = json::write<json::opts{.raw_string = true}>(map, buffer); // NOLINT(readability-implicit-bool-conversion)
Expand All @@ -100,6 +99,21 @@ TEST(MarketTest, JsonSerializationKey) {
EXPECT_EQ(buffer, R"({"BTC-ETH":false,"DOGE-BTC":true})");
}

struct Bar {
vector<Market> markets{Market{"DOGE", "BTC"}, Market{"ETH", "KRW"}};
};

TEST(MarketTest, JsonSerializationVector) {
Bar bar;

string buffer;
auto res = json::write<json::opts{.raw_string = true}>(bar, buffer); // NOLINT(readability-implicit-bool-conversion)

EXPECT_FALSE(res);

EXPECT_EQ(buffer, R"({"markets":["DOGE-BTC","ETH-KRW"]})");
}

TEST(MarketTest, JsonDeserialization) {
Foo foo;

Expand Down
Loading

0 comments on commit 27be8a2

Please sign in to comment.