diff --git a/cmake/download_test_data.cmake b/cmake/download_test_data.cmake index 53aef17fe..fe95235f8 100644 --- a/cmake/download_test_data.cmake +++ b/cmake/download_test_data.cmake @@ -1,7 +1,7 @@ find_package(Git) set(JSON_TEST_DATA_URL https://github.com/nlohmann/json_test_data) -set(JSON_TEST_DATA_VERSION 1.0.0) +set(JSON_TEST_DATA_VERSION 2.0.0) # target to download test data add_custom_target(download_test_data diff --git a/include/nlohmann/detail/output/binary_writer.hpp b/include/nlohmann/detail/output/binary_writer.hpp index 7d4a40314..9c6e615b5 100644 --- a/include/nlohmann/detail/output/binary_writer.hpp +++ b/include/nlohmann/detail/output/binary_writer.hpp @@ -6,6 +6,7 @@ #include // memcpy #include // numeric_limits #include // string +#include // isnan, isinf #include #include @@ -177,8 +178,35 @@ class binary_writer case value_t::number_float: { - oa->write_character(get_cbor_float_prefix(j.m_value.number_float)); - write_number(j.m_value.number_float); + if (std::isnan(j.m_value.number_float)) + { + // NaN is 0xf97e00 in CBOR + oa->write_character(to_char_type(0xF9)); + oa->write_character(to_char_type(0x7E)); + oa->write_character(to_char_type(0x00)); + } + else if (std::isinf(j.m_value.number_float)) + { + // Infinity is 0xf97c00, -Infinity is 0xf9fc00 + oa->write_character(to_char_type(0xf9)); + oa->write_character(j.m_value.number_float > 0 ? to_char_type(0x7C) : to_char_type(0xFC)); + oa->write_character(to_char_type(0x00)); + } + else + { + if (j.m_value.number_float >= std::numeric_limits::lowest() and + j.m_value.number_float <= std::numeric_limits::max() and + static_cast(static_cast(j.m_value.number_float)) == j.m_value.number_float) + { + oa->write_character(get_cbor_float_prefix(static_cast(j.m_value.number_float))); + write_number(static_cast(j.m_value.number_float)); + } + else + { + oa->write_character(get_cbor_float_prefix(j.m_value.number_float)); + write_number(j.m_value.number_float); + } + } break; } diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 6801326af..ff5accfd1 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -4946,6 +4946,11 @@ class basic_json element as string (see example). For primitive types (e.g., numbers), `key()` returns an empty string. + @warning Using `items()` on temporary objects is dangerous. Make sure the + object's lifetime exeeds the iteration. See + for more + information. + @return iteration proxy object wrapping @a ref with an interface to use in range-based for loops @@ -7106,7 +7111,7 @@ class basic_json - break (0xFF) @param[in] j JSON value to serialize - @return MessagePack serialization as byte vector + @return CBOR serialization as byte vector @complexity Linear in the size of the JSON value @a j. diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 12945016a..c5ef3f7f0 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -11873,6 +11873,7 @@ class json_ref #include // memcpy #include // numeric_limits #include // string +#include // isnan, isinf // #include @@ -12171,8 +12172,35 @@ class binary_writer case value_t::number_float: { - oa->write_character(get_cbor_float_prefix(j.m_value.number_float)); - write_number(j.m_value.number_float); + if (std::isnan(j.m_value.number_float)) + { + // NaN is 0xf97e00 in CBOR + oa->write_character(to_char_type(0xF9)); + oa->write_character(to_char_type(0x7E)); + oa->write_character(to_char_type(0x00)); + } + else if (std::isinf(j.m_value.number_float)) + { + // Infinity is 0xf97c00, -Infinity is 0xf9fc00 + oa->write_character(to_char_type(0xf9)); + oa->write_character(j.m_value.number_float > 0 ? to_char_type(0x7C) : to_char_type(0xFC)); + oa->write_character(to_char_type(0x00)); + } + else + { + if (j.m_value.number_float >= std::numeric_limits::lowest() and + j.m_value.number_float <= std::numeric_limits::max() and + static_cast(static_cast(j.m_value.number_float)) == j.m_value.number_float) + { + oa->write_character(get_cbor_float_prefix(static_cast(j.m_value.number_float))); + write_number(static_cast(j.m_value.number_float)); + } + else + { + oa->write_character(get_cbor_float_prefix(j.m_value.number_float)); + write_number(j.m_value.number_float); + } + } break; } @@ -20437,6 +20465,11 @@ class basic_json element as string (see example). For primitive types (e.g., numbers), `key()` returns an empty string. + @warning Using `items()` on temporary objects is dangerous. Make sure the + object's lifetime exeeds the iteration. See + for more + information. + @return iteration proxy object wrapping @a ref with an interface to use in range-based for loops @@ -22597,7 +22630,7 @@ class basic_json - break (0xFF) @param[in] j JSON value to serialize - @return MessagePack serialization as byte vector + @return CBOR serialization as byte vector @complexity Linear in the size of the JSON value @a j. diff --git a/test/src/unit-cbor.cpp b/test/src/unit-cbor.cpp index 710e7af81..280032587 100644 --- a/test/src/unit-cbor.cpp +++ b/test/src/unit-cbor.cpp @@ -130,6 +130,24 @@ TEST_CASE("CBOR") CHECK(result.empty()); } + SECTION("NaN") + { + // NaN value + json j = std::numeric_limits::quiet_NaN(); + std::vector expected = {0xf9, 0x7e, 0x00}; + const auto result = json::to_cbor(j); + CHECK(result == expected); + } + + SECTION("Infinity") + { + // Infinity value + json j = std::numeric_limits::infinity(); + std::vector expected = {0xf9, 0x7c, 0x00}; + const auto result = json::to_cbor(j); + CHECK(result == expected); + } + SECTION("null") { json j = nullptr; @@ -816,7 +834,7 @@ TEST_CASE("CBOR") } } - SECTION("float") + SECTION("double-precision float") { SECTION("3.1415925") { @@ -837,6 +855,135 @@ TEST_CASE("CBOR") } } + SECTION("single-precision float") + { + SECTION("0.5") + { + double v = 0.5; + json j = v; + // its double-precision float binary value is + // {0xfb, 0x3f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + // but to save memory, we can store it as single-precision float. + std::vector expected = {0xfa, 0x3f, 0x00, 0x00, 0x00}; + const auto result = json::to_cbor(j); + CHECK(result == expected); + // roundtrip + CHECK(json::from_cbor(result) == j); + CHECK(json::from_cbor(result) == v); + } + SECTION("0.0") + { + double v = 0.0; + json j = v; + // its double-precision binary value is: + // {0xfb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + std::vector expected = {0xfa, 0x00, 0x00, 0x00, 0x00}; + const auto result = json::to_cbor(j); + CHECK(result == expected); + // roundtrip + CHECK(json::from_cbor(result) == j); + CHECK(json::from_cbor(result) == v); + } + SECTION("-0.0") + { + double v = -0.0; + json j = v; + // its double-precision binary value is: + // {0xfb, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + std::vector expected = {0xfa, 0x80, 0x00, 0x00, 0x00}; + const auto result = json::to_cbor(j); + CHECK(result == expected); + // roundtrip + CHECK(json::from_cbor(result) == j); + CHECK(json::from_cbor(result) == v); + } + SECTION("100.0") + { + double v = 100.0; + json j = v; + // its double-precision binary value is: + // {0xfb, 0x40, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + std::vector expected = {0xfa, 0x42, 0xc8, 0x00, 0x00}; + const auto result = json::to_cbor(j); + CHECK(result == expected); + // roundtrip + CHECK(json::from_cbor(result) == j); + CHECK(json::from_cbor(result) == v); + } + SECTION("200.0") + { + double v = 200.0; + json j = v; + // its double-precision binary value is: + // {0xfb, 0x40, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + std::vector expected = {0xfa, 0x43, 0x48, 0x00, 0x00}; + const auto result = json::to_cbor(j); + CHECK(result == expected); + // roundtrip + CHECK(json::from_cbor(result) == j); + CHECK(json::from_cbor(result) == v); + } + SECTION("3.40282e+38(max float)") + { + float v = std::numeric_limits::max(); + json j = v; + std::vector expected = + { + 0xfa, 0x7f, 0x7f, 0xff, 0xff + }; + const auto result = json::to_cbor(j); + CHECK(result == expected); + // roundtrip + CHECK(json::from_cbor(result) == j); + CHECK(json::from_cbor(result) == v); + } + SECTION("-3.40282e+38(lowest float)") + { + double v = std::numeric_limits::lowest(); + json j = v; + std::vector expected = + { + 0xfa, 0xff, 0x7f, 0xff, 0xff + }; + const auto result = json::to_cbor(j); + CHECK(result == expected); + // roundtrip + CHECK(json::from_cbor(result) == j); + CHECK(json::from_cbor(result) == v); + } + SECTION("1 + 3.40282e+38(more than max float)") + { + double v = std::numeric_limits::max() + 0.1e+34; + json j = v; + std::vector expected = + { + 0xfb, 0x47, 0xf0, 0x00, 0x03, 0x04, 0xdc, 0x64, 0x49 + }; + // double + const auto result = json::to_cbor(j); + CHECK(result == expected); + // roundtrip + CHECK(json::from_cbor(result) == j); + CHECK(json::from_cbor(result) == v); + } + SECTION("-1 - 3.40282e+38(less than lowest float)") + { + double v = std::numeric_limits::lowest() - 1; + json j = v; + std::vector expected = + { + 0xfa, 0xff, 0x7f, 0xff, 0xff + }; + // the same with lowest float + const auto result = json::to_cbor(j); + CHECK(result == expected); + // roundtrip + CHECK(json::from_cbor(result) == j); + CHECK(json::from_cbor(result) == v); + } + + } + SECTION("half-precision float (edge cases)") { SECTION("errors") @@ -936,7 +1083,7 @@ TEST_CASE("CBOR") SECTION("NaN") { - json j = json::from_cbor(std::vector({0xf9, 0x7c, 0x01})); + json j = json::from_cbor(std::vector({0xf9, 0x7e, 0x00})); json::number_float_t d = j; CHECK(std::isnan(d)); CHECK(j.dump() == "null");