Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ sudo apt install pkg-config libssl-dev zlib1g-dev libgtest-dev libgmock-dev

Install [dconv](https://github.com/joinframework/dconv) from its release package:
```bash
wget https://github.com/joinframework/dconv/releases/download/v1.2.3/libdconv-dev_1.2.3_all.deb
sudo apt install ./libdconv-dev_1.2.3_all.deb
wget https://github.com/joinframework/dconv/releases/download/v2.0.0/libdconv-dev_2.0.0_all.deb
sudo apt install ./libdconv-dev_2.0.0_all.deb
```

> **Compilers:** GCC 11+ or Clang 16+ (C++20). Clang requires `libclang-rt-dev` for coverage instrumentation (`--coverage`).
Expand Down
2 changes: 1 addition & 1 deletion cmake/joinConfig.cmake.in
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ foreach(comp ${join_FIND_COMPONENTS})
endforeach()

if(@JOIN_ENABLE_DATA@ OR @JOIN_ENABLE_SERVICES@)
find_dependency(dconv 1.2.3)
find_dependency(dconv 2.0.0)
find_dependency(ZLIB)
endif()

Expand Down
2 changes: 1 addition & 1 deletion cmake/packaging.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ if(JOIN_ENABLE_CRYPTO)
string(APPEND JOIN_DEV_DEPENDS ", libssl-dev")
endif()
if(JOIN_ENABLE_DATA)
string(APPEND JOIN_DEV_DEPENDS ", libdconv-dev (>= 1.2.3), zlib1g-dev")
string(APPEND JOIN_DEV_DEPENDS ", libdconv-dev (>= 2.0.0), zlib1g-dev")
endif()
if(JOIN_ENABLE_IO_URING)
string(APPEND JOIN_DEV_DEPENDS ", liburing-dev")
Expand Down
2 changes: 1 addition & 1 deletion data/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.22.1)

find_package(dconv 1.2.3 REQUIRED)
find_package(dconv 2.0.0 REQUIRED)
find_package(ZLIB REQUIRED)

set(PUBLIC_HEADERS
Expand Down
85 changes: 31 additions & 54 deletions data/include/join/json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,9 @@ namespace join
*/
enum class JsonWriteMode
{
None, /**< no write mode set. */
Canonical, /**< canonical form. */
None, /**< no write mode set. */
ValidateEncoding, /**< validate UTF-8 encoding of strings. */
Canonical, /**< canonical form. */
};

/**
Expand Down Expand Up @@ -334,40 +335,16 @@ namespace join
*/
template <JsonWriteMode Mode = WriteMode>
int setDouble (double value)
requires (Mode == JsonWriteMode::None)
requires (Mode != JsonWriteMode::Canonical)
{
array ();
uint64_t bits;
memcpy (&bits, &value, sizeof (bits));
const bool neg = (bits >> 63) != 0;
const uint64_t exp = (bits >> 52) & 0x7FFULL;
const uint64_t frac = bits & 0x000FFFFFFFFFFFFFULL;
if (exp != 0x7FFULL)
if (JOIN_UNLIKELY (!std::isfinite (value)))
{
writeDouble (value);
}
else if (frac != 0)
{
if (neg)
{
append ("-NaN", 4);
}
else
{
append ("NaN", 3);
}
}
else
{
if (neg)
{
append ("-Inf", 4);
}
else
{
append ("Inf", 3);
}
join::lastError = make_error_code (SaxErrc::InvalidValue);
reset ();
return -1;
}
array ();
writeDouble (value);
endValue ();
return 0;
}
Expand All @@ -381,28 +358,27 @@ namespace join
int setDouble (double value)
requires (Mode == JsonWriteMode::Canonical)
{
if (JOIN_UNLIKELY (!std::isfinite (value)))
{
join::lastError = make_error_code (SaxErrc::InvalidValue);
reset ();
return -1;
}
array ();
if (std::isfinite (value))
if ((std::trunc (value) == value) && (value >= 0) &&
(value < static_cast<double> (std::numeric_limits<uint64_t>::max ())))
{
if ((std::trunc (value) == value) && (value >= 0) &&
(value < static_cast<double> (std::numeric_limits<uint64_t>::max ())))
{
writeUint64 (static_cast<uint64_t> (value));
}
else if ((std::trunc (value) == value) &&
(value >= static_cast<double> (std::numeric_limits<int64_t>::min ())) &&
(value < static_cast<double> (std::numeric_limits<int64_t>::max ())))
{
writeInt64 (static_cast<int64_t> (value));
}
else
{
writeDouble (value);
}
writeUint64 (static_cast<uint64_t> (value));
}
else if ((std::trunc (value) == value) &&
(value >= static_cast<double> (std::numeric_limits<int64_t>::min ())) &&
(value < static_cast<double> (std::numeric_limits<int64_t>::max ())))
{
writeInt64 (static_cast<int64_t> (value));
}
else
{
append ("null", 4);
writeDouble (value);
}
endValue ();
return 0;
Expand All @@ -415,7 +391,7 @@ namespace join
*/
int setString (std::string_view value)
{
if constexpr (WriteMode == JsonWriteMode::Canonical)
if constexpr (WriteMode != JsonWriteMode::None)
{
if (JOIN_UNLIKELY (utf8::validate (value.data (), value.data () + value.size ()) == -1))
{
Expand Down Expand Up @@ -516,7 +492,7 @@ namespace join
*/
template <typename Type, JsonWriteMode Mode = WriteMode>
const View<Type>& members (const View<Type>& object) const noexcept
requires (Mode == JsonWriteMode::None)
requires (Mode != JsonWriteMode::Canonical)
{
return object;
}
Expand Down Expand Up @@ -570,7 +546,7 @@ namespace join
reset ();
return -1;
}
if constexpr (WriteMode == JsonWriteMode::Canonical)
if constexpr (WriteMode != JsonWriteMode::None)
{
if (JOIN_UNLIKELY (utf8::validate (key.data (), key.data () + key.size ()) == -1))
{
Expand Down Expand Up @@ -1363,7 +1339,8 @@ namespace join
bool negative = false;
std::error_code ec;

if (JOIN_LIKELY (dconv::atod (document, value, significand, exponent, negative, ec) == 0))
if (JOIN_LIKELY (
dconv::atod<dconv::AtodMode::Json> (document, value, significand, exponent, negative, ec) == 0))
{
return handler.setDouble (value);
}
Expand Down
10 changes: 6 additions & 4 deletions data/tests/json_canonicalizer_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,9 @@ TEST (JsonCanonicalizer, nan)

std::stringstream out;
JsonCanonicalizer writer (out);
ASSERT_NE (value.serialize (writer), -1) << join::lastError.message ();
EXPECT_EQ (out.str (), "[null,null]");
ASSERT_EQ (value.serialize (writer), -1);
EXPECT_EQ (join::lastError, SaxErrc::InvalidValue);
EXPECT_TRUE (out.str ().empty ());
}

/**
Expand All @@ -125,8 +126,9 @@ TEST (JsonCanonicalizer, infinity)

std::stringstream out;
JsonCanonicalizer writer (out);
ASSERT_NE (value.serialize (writer), -1) << join::lastError.message ();
EXPECT_EQ (out.str (), "[null,null]");
ASSERT_EQ (value.serialize (writer), -1);
EXPECT_EQ (join::lastError, SaxErrc::InvalidValue);
EXPECT_TRUE (out.str ().empty ());
}

/**
Expand Down
57 changes: 27 additions & 30 deletions data/tests/json_reader_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -231,51 +231,48 @@ TEST (JsonReader, pass)

stream.clear ();
stream.str ("[NaN]");
ASSERT_EQ (JsonReader ().deserialize (stream, value), 0) << join::lastError.message ();
ASSERT_TRUE (value.isArray ());
ASSERT_FALSE (value.empty ());
ASSERT_TRUE (value[0].isDouble ());
ASSERT_TRUE (!std::signbit (value[0].getDouble ()) && std::isnan (value[0].getDouble ()));
ASSERT_EQ (JsonReader ().deserialize (stream, value), -1);
ASSERT_EQ (join::lastError, SaxErrc::InvalidValue);

stream.clear ();
stream.str ("[-NaN]");
ASSERT_EQ (JsonReader ().deserialize (stream, value), 0) << join::lastError.message ();
ASSERT_TRUE (value.isArray ());
ASSERT_FALSE (value.empty ());
ASSERT_TRUE (value[0].isDouble ());
ASSERT_TRUE (std::signbit (value[0].getDouble ()) && std::isnan (value[0].getDouble ()));
ASSERT_EQ (JsonReader ().deserialize (stream, value), -1);
ASSERT_EQ (join::lastError, SaxErrc::InvalidValue);

stream.clear ();
stream.str ("[Inf]");
ASSERT_EQ (JsonReader ().deserialize (stream, value), 0) << join::lastError.message ();
ASSERT_TRUE (value.isArray ());
ASSERT_FALSE (value.empty ());
ASSERT_TRUE (value[0].isDouble ());
ASSERT_TRUE (!std::signbit (value[0].getDouble ()) && std::isinf (value[0].getDouble ()));
ASSERT_EQ (JsonReader ().deserialize (stream, value), -1);
ASSERT_EQ (join::lastError, SaxErrc::InvalidValue);

stream.clear ();
stream.str ("[-Inf]");
ASSERT_EQ (JsonReader ().deserialize (stream, value), 0) << join::lastError.message ();
ASSERT_TRUE (value.isArray ());
ASSERT_FALSE (value.empty ());
ASSERT_TRUE (value[0].isDouble ());
ASSERT_TRUE (std::signbit (value[0].getDouble ()) && std::isinf (value[0].getDouble ()));
ASSERT_EQ (JsonReader ().deserialize (stream, value), -1);
ASSERT_EQ (join::lastError, SaxErrc::InvalidValue);

stream.clear ();
stream.str ("[Infinity]");
ASSERT_EQ (JsonReader ().deserialize (stream, value), 0) << join::lastError.message ();
ASSERT_TRUE (value.isArray ());
ASSERT_FALSE (value.empty ());
ASSERT_TRUE (value[0].isDouble ());
ASSERT_TRUE (!std::signbit (value[0].getDouble ()) && std::isinf (value[0].getDouble ()));
ASSERT_EQ (JsonReader ().deserialize (stream, value), -1);
ASSERT_EQ (join::lastError, SaxErrc::InvalidValue);

stream.clear ();
stream.str ("[-Infinity]");
ASSERT_EQ (JsonReader ().deserialize (stream, value), 0) << join::lastError.message ();
ASSERT_TRUE (value.isArray ());
ASSERT_FALSE (value.empty ());
ASSERT_TRUE (value[0].isDouble ());
ASSERT_TRUE (std::signbit (value[0].getDouble ()) && std::isinf (value[0].getDouble ()));
ASSERT_EQ (JsonReader ().deserialize (stream, value), -1);
ASSERT_EQ (join::lastError, SaxErrc::InvalidValue);

stream.clear ();
stream.str ("[01]");
ASSERT_EQ (JsonReader ().deserialize (stream, value), -1);
ASSERT_EQ (join::lastError, SaxErrc::InvalidValue);

stream.clear ();
stream.str ("[1.]");
ASSERT_EQ (JsonReader ().deserialize (stream, value), -1);
ASSERT_EQ (join::lastError, SaxErrc::InvalidValue);

stream.clear ();
stream.str ("[.5]");
ASSERT_EQ (JsonReader ().deserialize (stream, value), -1);
ASSERT_EQ (join::lastError, SaxErrc::InvalidValue);

stream.clear ();
stream.str ("[true]");
Expand Down
28 changes: 8 additions & 20 deletions data/tests/json_roundtrip_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -267,35 +267,23 @@ TEST (Json, roundtrip)

stream.clear ();
stream.str ("[Inf]");
ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message ();
stream.str ("");
stream.clear ();
ASSERT_NE (value.serialize (writer), -1) << join::lastError.message ();
EXPECT_EQ (stream.str (), "[Inf]");
ASSERT_EQ (reader.deserialize (stream, value), -1);
EXPECT_EQ (join::lastError, SaxErrc::InvalidValue);

stream.clear ();
stream.str ("[-Inf]");
ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message ();
stream.str ("");
stream.clear ();
ASSERT_NE (value.serialize (writer), -1) << join::lastError.message ();
EXPECT_EQ (stream.str (), "[-Inf]");
ASSERT_EQ (reader.deserialize (stream, value), -1);
EXPECT_EQ (join::lastError, SaxErrc::InvalidValue);

stream.clear ();
stream.str ("[NaN]");
ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message ();
stream.str ("");
stream.clear ();
ASSERT_NE (value.serialize (writer), -1) << join::lastError.message ();
EXPECT_EQ (stream.str (), "[NaN]");
ASSERT_EQ (reader.deserialize (stream, value), -1);
EXPECT_EQ (join::lastError, SaxErrc::InvalidValue);

stream.clear ();
stream.str ("[-NaN]");
ASSERT_NE (reader.deserialize (stream, value), -1) << join::lastError.message ();
stream.str ("");
stream.clear ();
ASSERT_NE (value.serialize (writer), -1) << join::lastError.message ();
EXPECT_EQ (stream.str (), "[-NaN]");
ASSERT_EQ (reader.deserialize (stream, value), -1);
EXPECT_EQ (join::lastError, SaxErrc::InvalidValue);
}

/**
Expand Down
47 changes: 39 additions & 8 deletions data/tests/json_writer_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -190,20 +190,29 @@ TEST (JsonWriter, setDouble)
EXPECT_EQ (stream.str (), "1.23456789e-13");

stream.str ("");
EXPECT_EQ (jsonWriter.setDouble (-std::numeric_limits<double>::infinity ()), 0);
EXPECT_EQ (stream.str (), "-Inf");
EXPECT_EQ (jsonWriter.setDouble (-std::numeric_limits<double>::infinity ()), -1);
EXPECT_EQ (join::lastError, SaxErrc::InvalidValue);
EXPECT_TRUE (stream.str ().empty ());

stream.str ("");
EXPECT_EQ (jsonWriter.setDouble (std::numeric_limits<double>::infinity ()), -1);
EXPECT_EQ (join::lastError, SaxErrc::InvalidValue);
EXPECT_TRUE (stream.str ().empty ());

stream.str ("");
EXPECT_EQ (jsonWriter.setDouble (std::numeric_limits<double>::infinity ()), 0);
EXPECT_EQ (stream.str (), "Inf");
EXPECT_EQ (jsonWriter.setDouble (-std::numeric_limits<double>::quiet_NaN ()), -1);
EXPECT_EQ (join::lastError, SaxErrc::InvalidValue);
EXPECT_TRUE (stream.str ().empty ());

stream.str ("");
EXPECT_EQ (jsonWriter.setDouble (-std::numeric_limits<double>::quiet_NaN ()), 0);
EXPECT_EQ (stream.str (), "-NaN");
EXPECT_EQ (jsonWriter.setDouble (std::numeric_limits<double>::quiet_NaN ()), -1);
EXPECT_EQ (join::lastError, SaxErrc::InvalidValue);
EXPECT_TRUE (stream.str ().empty ());

join::BasicJsonWriter<join::JsonWriteMode::ValidateEncoding> validator (stream);
stream.str ("");
EXPECT_EQ (jsonWriter.setDouble (std::numeric_limits<double>::quiet_NaN ()), 0);
EXPECT_EQ (stream.str (), "NaN");
EXPECT_EQ (validator.setDouble (1.0), 0);
EXPECT_EQ (stream.str (), "1.0");

stream.str ("");
std::string expected ("[98.6");
Expand Down Expand Up @@ -233,6 +242,21 @@ TEST (JsonWriter, setString)
stream.str ("");
EXPECT_EQ (jsonWriter.setString ("\"\\\b\f\n\r\t\x19"), 0);
EXPECT_EQ (stream.str (), "\"\\\"\\\\\\b\\f\\n\\r\\t\\u0019\"");

stream.str ("");
EXPECT_EQ (jsonWriter.setString ("\xff"), 0);
EXPECT_EQ (stream.str (), "\"\xff\"");

join::BasicJsonWriter<join::JsonWriteMode::ValidateEncoding> validator (stream);

stream.str ("");
EXPECT_EQ (validator.setString ("caf\xc3\xa9"), 0);
EXPECT_EQ (stream.str (), "\"caf\xc3\xa9\"");

stream.str ("");
EXPECT_EQ (validator.setString ("\xff"), -1);
EXPECT_EQ (join::lastError, join::JsonErrc::InvalidEncoding);
EXPECT_TRUE (stream.str ().empty ());
}

/**
Expand Down Expand Up @@ -400,6 +424,13 @@ TEST (JsonWriter, setKey)
EXPECT_EQ (jsonWriter.setKey ("\"\\\b\f\n\r\t\x19"), 0);
jsonWriter.flush ();
EXPECT_EQ (stream.str (), "{\"\\\"\\\\\\b\\f\\n\\r\\t\\u0019\":");

join::BasicJsonWriter<join::JsonWriteMode::ValidateEncoding> validator (stream);
stream.str ("");
EXPECT_EQ (validator.startObject (), 0);
EXPECT_EQ (validator.setKey ("\xff"), -1);
EXPECT_EQ (join::lastError, join::JsonErrc::InvalidEncoding);
EXPECT_TRUE (stream.str ().empty ());
}

/**
Expand Down
Loading