From 13aee51011ef9452e5c2a006996f06fe4c8568b2 Mon Sep 17 00:00:00 2001 From: John Keiser Date: Thu, 2 Apr 2020 12:14:29 -0700 Subject: [PATCH] Add element.type() for type switching --- doc/basics.md | 67 +++- include/simdjson/document.h | 123 +++++--- include/simdjson/inline/document.h | 70 +++-- tests/basictests.cpp | 488 +++++++++++++++++++++++++++++ tests/readme_examples.cpp | 46 +++ 5 files changed, 732 insertions(+), 62 deletions(-) diff --git a/doc/basics.md b/doc/basics.md index de27ce1b..1d56ca52 100644 --- a/doc/basics.md +++ b/doc/basics.md @@ -8,8 +8,9 @@ An overview of what you need to know to use simdjson, with examples. * [Using the Parsed JSON](#using-the-parsed-json) * [JSON Pointer](#json-pointer) * [Error Handling](#error-handling) - * [Error Handling Example](#error-handling-example) - * [Exceptions](#exceptions) + * [Error Handling Example](#error-handling-example) + * [Exceptions](#exceptions) +* [Tree Walking and JSON Element Types](#tree-walking-and-json-element-types) * [Newline-Delimited JSON (ndjson) and JSON lines](#newline-delimited-json-ndjson-and-json-lines) * [Thread Safety](#thread-safety) @@ -67,6 +68,8 @@ Once you have an element, you can navigate it with idiomatic C++ iterators, oper first element. > Note that array[0] does not compile, because implementing [] gives the impression indexing is a > O(1) operation, which it is not presently in simdjson. +* **Checking an Element Type:** You can check an element's type with `element.type()`. It + returns an `element_type`. Here are some examples of all of the above: @@ -152,9 +155,9 @@ This is how the example in "Using the Parsed JSON" could be written using only e ```c++ auto cars_json = R"( [ -{ "make": "Toyota", "model": "Camry", "year": 2018, "tire_pressure": [ 40.1, 39.9, 37.7, 40.4 ] }, -{ "make": "Kia", "model": "Soul", "year": 2012, "tire_pressure": [ 30.1, 31.0, 28.6, 28.7 ] }, -{ "make": "Toyota", "model": "Tercel", "year": 1999, "tire_pressure": [ 29.8, 30.0, 30.2, 30.5 ] } + { "make": "Toyota", "model": "Camry", "year": 2018, "tire_pressure": [ 40.1, 39.9, 37.7, 40.4 ] }, + { "make": "Kia", "model": "Soul", "year": 2012, "tire_pressure": [ 30.1, 31.0, 28.6, 28.7 ] }, + { "make": "Toyota", "model": "Tercel", "year": 1999, "tire_pressure": [ 29.8, 30.0, 30.2, 30.5 ] } ] )"_padded; dom::parser parser; auto [cars, error] = parser.parse(cars_json).get(); @@ -210,6 +213,60 @@ dom::element doc = parser.parse(json); // Throws an exception if there was an er When used this way, a `simdjson_error` exception will be thrown if an error occurs, preventing the program from continuing if there was an error. +Tree Walking and JSON Element Types +----------------------------------- + +Sometimes you don't necessarily have a document with a known type, and are trying to generically +inspect or walk over JSON elements. To do that, you can use iterators and the type() method. For +example, here's a quick and dirty recursive function that verbosely prints the JSON document as JSON +(* ignoring nuances like trailing commas and escaping strings, for brevity's sake): + +```c++ +void print_json(dom::element element) { + switch (element.type()) { + case dom::element_type::ARRAY: + cout << "["; + for (dom::element child : dom::array(element)) { + print_json(child); + cout << ","; + } + cout << "]"; + break; + case dom::element_type::OBJECT: + cout << "{"; + for (dom::key_value_pair field : dom::object(element)) { + cout << "\"" << field.key << "\": "; + print_json(field.value); + } + cout << "}"; + break; + case dom::element_type::INT64: + cout << int64_t(element) << endl; + break; + case dom::element_type::UINT64: + cout << uint64_t(element) << endl; + break; + case dom::element_type::DOUBLE: + cout << double(element) << endl; + break; + case dom::element_type::STRING: + cout << std::string_view(element) << endl; + break; + case dom::element_type::BOOL: + cout << bool(element) << endl; + break; + case dom::element_type::NULL_VALUE: + cout << "null" << endl; + break; + } +} + +void basics_treewalk_1() { + dom::parser parser; + print_json(parser.load("twitter.json")); +} +``` + Newline-Delimited JSON (ndjson) and JSON lines ---------------------------------------------- diff --git a/include/simdjson/document.h b/include/simdjson/document.h index 5f0340af..dc70ac1e 100644 --- a/include/simdjson/document.h +++ b/include/simdjson/document.h @@ -41,50 +41,65 @@ namespace simdjson::internal { using namespace simdjson::dom; constexpr const uint64_t JSON_VALUE_MASK = 0x00FFFFFFFFFFFFFF; -enum class tape_type; -class tape_ref; - /** - * The possible types in the tape. Internal only. - */ - enum class tape_type { - ROOT = 'r', - START_ARRAY = '[', - START_OBJECT = '{', - END_ARRAY = ']', - END_OBJECT = '}', - STRING = '"', - INT64 = 'l', - UINT64 = 'u', - DOUBLE = 'd', - TRUE_VALUE = 't', - FALSE_VALUE = 'f', - NULL_VALUE = 'n' - }; - /** - * A reference to an element on the tape. Internal only. - */ - class tape_ref { - public: - really_inline tape_ref() noexcept; - really_inline tape_ref(const document *doc, size_t json_index) noexcept; - inline size_t after_element() const noexcept; - really_inline tape_type type() const noexcept; - really_inline uint64_t tape_value() const noexcept; - template - really_inline T next_tape_value() const noexcept; - inline std::string_view get_string_view() const noexcept; +/** + * The possible types in the tape. Internal only. + */ +enum class tape_type { + ROOT = 'r', + START_ARRAY = '[', + START_OBJECT = '{', + END_ARRAY = ']', + END_OBJECT = '}', + STRING = '"', + INT64 = 'l', + UINT64 = 'u', + DOUBLE = 'd', + TRUE_VALUE = 't', + FALSE_VALUE = 'f', + NULL_VALUE = 'n' +}; - /** The document this element references. */ - const document *doc; +/** + * A reference to an element on the tape. Internal only. + */ +class tape_ref { +public: + really_inline tape_ref() noexcept; + really_inline tape_ref(const document *doc, size_t json_index) noexcept; + inline size_t after_element() const noexcept; + really_inline tape_type tape_ref_type() const noexcept; + really_inline uint64_t tape_value() const noexcept; + template + really_inline T next_tape_value() const noexcept; + inline std::string_view get_string_view() const noexcept; + + /** The document this element references. */ + const document *doc; + + /** The index of this element on `doc.tape[]` */ + size_t json_index; +}; - /** The index of this element on `doc.tape[]` */ - size_t json_index; - }; } // namespace simdjson::internal namespace simdjson::dom { +/** + * The actual concrete type of a JSON element + * This is the type it is most easily cast to with get<>. + */ +enum class element_type { + ARRAY, ///< dom::array + OBJECT, ///< dom::object + INT64, ///< int64_t + UINT64, ///< uint64_t: any integer that fits in uint64_t but *not* int64_t + DOUBLE, ///< double: Any number with a "." or "e" that fits in double. + STRING, ///< std::string_view + BOOL, ///< bool + NULL_VALUE ///< null +}; + /** * JSON array. */ @@ -367,6 +382,9 @@ public: /** Create a new, invalid element. */ really_inline element() noexcept; + /** The type of this element. */ + really_inline element_type type() const noexcept; + /** Whether this element is a json `null`. */ really_inline bool is_null() const noexcept; @@ -1121,6 +1139,36 @@ inline std::ostream& operator<<(std::ostream& out, const object &value) { return */ inline std::ostream& operator<<(std::ostream& out, const key_value_pair &value) { return out << minify(value); } +/** + * Print element type to an output stream. + * + * @param out The output stream. + * @param value The value to print. + * @throw if there is an error with the underlying output stream. simdjson itself will not throw. + */ +inline std::ostream& operator<<(std::ostream& out, element_type type) { + switch (type) { + case element_type::ARRAY: + return out << "array"; + case element_type::OBJECT: + return out << "object"; + case element_type::INT64: + return out << "int64_t"; + case element_type::UINT64: + return out << "uint64_t"; + case element_type::DOUBLE: + return out << "double"; + case element_type::STRING: + return out << "string"; + case element_type::BOOL: + return out << "bool"; + case element_type::NULL_VALUE: + return out << "null"; + default: + abort(); + } +} + } // namespace dom #if SIMDJSON_EXCEPTIONS @@ -1172,6 +1220,7 @@ public: really_inline simdjson_result(dom::element &&value) noexcept; ///< @private really_inline simdjson_result(error_code error) noexcept; ///< @private + inline simdjson_result type() const noexcept; inline simdjson_result is_null() const noexcept; template inline simdjson_result is() const noexcept; diff --git a/include/simdjson/inline/document.h b/include/simdjson/inline/document.h index dba261a0..8279925d 100644 --- a/include/simdjson/inline/document.h +++ b/include/simdjson/inline/document.h @@ -23,6 +23,10 @@ really_inline simdjson_result::simdjson_result(dom::element &&valu : internal::simdjson_result_base(std::forward(value)) {} really_inline simdjson_result::simdjson_result(error_code error) noexcept : internal::simdjson_result_base(error) {} +inline simdjson_result simdjson_result::type() const noexcept { + if (error()) { return error(); } + return first.type(); +} inline simdjson_result simdjson_result::is_null() const noexcept { if (error()) { return error(); } return first.is_null(); @@ -715,13 +719,39 @@ inline key_value_pair::key_value_pair(const std::string_view &_key, element _val really_inline element::element() noexcept : internal::tape_ref() {} really_inline element::element(const document *_doc, size_t _json_index) noexcept : internal::tape_ref(_doc, _json_index) { } +inline element_type element::type() const noexcept { + switch (tape_ref_type()) { + case internal::tape_type::START_ARRAY: + return element_type::ARRAY; + case internal::tape_type::START_OBJECT: + return element_type::OBJECT; + case internal::tape_type::INT64: + return element_type::INT64; + case internal::tape_type::UINT64: + return element_type::UINT64; + case internal::tape_type::DOUBLE: + return element_type::DOUBLE; + case internal::tape_type::STRING: + return element_type::STRING; + case internal::tape_type::TRUE_VALUE: + case internal::tape_type::FALSE_VALUE: + return element_type::BOOL; + case internal::tape_type::NULL_VALUE: + return element_type::NULL_VALUE; + case internal::tape_type::ROOT: + case internal::tape_type::END_ARRAY: + case internal::tape_type::END_OBJECT: + default: + abort(); + } +} really_inline bool element::is_null() const noexcept { - return type() == internal::tape_type::NULL_VALUE; + return tape_ref_type() == internal::tape_type::NULL_VALUE; } template<> inline simdjson_result element::get() const noexcept { - switch (type()) { + switch (tape_ref_type()) { case internal::tape_type::TRUE_VALUE: return true; case internal::tape_type::FALSE_VALUE: @@ -732,7 +762,7 @@ inline simdjson_result element::get() const noexcept { } template<> inline simdjson_result element::get() const noexcept { - switch (type()) { + switch (tape_ref_type()) { case internal::tape_type::STRING: { size_t string_buf_index = tape_value(); return reinterpret_cast(&doc->string_buf[string_buf_index + sizeof(uint32_t)]); @@ -743,7 +773,7 @@ inline simdjson_result element::get() const noexcept } template<> inline simdjson_result element::get() const noexcept { - switch (type()) { + switch (tape_ref_type()) { case internal::tape_type::STRING: return get_string_view(); default: @@ -752,7 +782,7 @@ inline simdjson_result element::get() const } template<> inline simdjson_result element::get() const noexcept { - switch (type()) { + switch (tape_ref_type()) { case internal::tape_type::UINT64: return next_tape_value(); case internal::tape_type::INT64: { @@ -768,11 +798,11 @@ inline simdjson_result element::get() const noexcept { } template<> inline simdjson_result element::get() const noexcept { - switch (type()) { + switch (tape_ref_type()) { case internal::tape_type::UINT64: { uint64_t result = next_tape_value(); // Wrapping max in parens to handle Windows issue: https://stackoverflow.com/questions/11544073/how-do-i-deal-with-the-max-macro-in-windows-h-colliding-with-max-in-std - if (result > (std::numeric_limits::max)()) { + if (result > (std::numeric_limits::max)()) { return NUMBER_OUT_OF_RANGE; } return static_cast(result); @@ -785,7 +815,7 @@ inline simdjson_result element::get() const noexcept { } template<> inline simdjson_result element::get() const noexcept { - switch (type()) { + switch (tape_ref_type()) { case internal::tape_type::UINT64: return next_tape_value(); case internal::tape_type::INT64: { @@ -804,7 +834,7 @@ inline simdjson_result element::get() const noexcept { } template<> inline simdjson_result element::get() const noexcept { - switch (type()) { + switch (tape_ref_type()) { case internal::tape_type::START_ARRAY: return array(doc, json_index); default: @@ -813,7 +843,7 @@ inline simdjson_result element::get() const noexcept { } template<> inline simdjson_result element::get() const noexcept { - switch (type()) { + switch (tape_ref_type()) { case internal::tape_type::START_OBJECT: return object(doc, json_index); default: @@ -838,10 +868,10 @@ inline element::operator double() const noexcept(false) { return get(); inline element::operator array() const noexcept(false) { return get(); } inline element::operator object() const noexcept(false) { return get(); } -inline dom::array::iterator dom::element::begin() const noexcept(false) { +inline array::iterator element::begin() const noexcept(false) { return get().begin(); } -inline dom::array::iterator dom::element::end() const noexcept(false) { +inline array::iterator element::end() const noexcept(false) { return get().end(); } @@ -854,7 +884,7 @@ inline simdjson_result element::operator[](const char *key) const noexc return at_key(key); } inline simdjson_result element::at(const std::string_view &json_pointer) const noexcept { - switch (type()) { + switch (tape_ref_type()) { case internal::tape_type::START_OBJECT: return object(doc, json_index).at(json_pointer); case internal::tape_type::START_ARRAY: @@ -905,7 +935,7 @@ inline std::ostream& minify::print(std::ostream& out) { out << '"' << internal::escape_json_string(iter.get_string_view()) << "\":"; iter.json_index++; } - switch (iter.type()) { + switch (iter.tape_ref_type()) { // Arrays case tape_type::START_ARRAY: { @@ -923,7 +953,7 @@ inline std::ostream& minify::print(std::ostream& out) { iter.json_index++; // Handle empty [] (we don't want to come back around and print commas) - if (iter.type() == tape_type::END_ARRAY) { + if (iter.tape_ref_type() == tape_type::END_ARRAY) { out << ']'; depth--; break; @@ -950,7 +980,7 @@ inline std::ostream& minify::print(std::ostream& out) { iter.json_index++; // Handle empty {} (we don't want to come back around and print commas) - if (iter.type() == tape_type::END_OBJECT) { + if (iter.tape_ref_type() == tape_type::END_OBJECT) { out << '}'; depth--; break; @@ -997,8 +1027,8 @@ inline std::ostream& minify::print(std::ostream& out) { after_value = true; // Handle multiple ends in a row - while (depth != 0 && (iter.type() == tape_type::END_ARRAY || iter.type() == tape_type::END_OBJECT)) { - out << char(iter.type()); + while (depth != 0 && (iter.tape_ref_type() == tape_type::END_ARRAY || iter.tape_ref_type() == tape_type::END_OBJECT)) { + out << char(iter.tape_ref_type()); depth--; iter.json_index++; } @@ -1070,7 +1100,7 @@ really_inline tape_ref::tape_ref() noexcept : doc{nullptr}, json_index{0} {} really_inline tape_ref::tape_ref(const document *_doc, size_t _json_index) noexcept : doc{_doc}, json_index{_json_index} {} inline size_t tape_ref::after_element() const noexcept { - switch (type()) { + switch (tape_ref_type()) { case tape_type::START_ARRAY: case tape_type::START_OBJECT: return tape_value(); @@ -1082,7 +1112,7 @@ inline size_t tape_ref::after_element() const noexcept { return json_index + 1; } } -really_inline tape_type tape_ref::type() const noexcept { +really_inline tape_type tape_ref::tape_ref_type() const noexcept { return static_cast(doc->tape[json_index] >> 56); } really_inline uint64_t internal::tape_ref::tape_value() const noexcept { diff --git a/tests/basictests.cpp b/tests/basictests.cpp index 61332954..07002404 100644 --- a/tests/basictests.cpp +++ b/tests/basictests.cpp @@ -23,6 +23,11 @@ #define NDJSON_TEST_PATH "jsonexamples/amazon_cellphones.ndjson" #endif +#define ASSERT_EQUAL(ACTUAL, EXPECTED) if ((ACTUAL) != (EXPECTED)) { std::cerr << "Expected " << #ACTUAL << " to be " << (EXPECTED) << ", got " << (ACTUAL) << " instead!" << std::endl; return false; } +#define ASSERT_TRUE(ACTUAL) ASSERT_EQUAL(ACTUAL, true) +#define ASSERT_FALSE(ACTUAL) ASSERT_EQUAL(ACTUAL, false) +#define ASSERT_SUCCESS(ERROR) if (ERROR) { std::cerr << (ERROR) << std::endl; return false; } + namespace number_tests { // ulp distance @@ -1104,6 +1109,488 @@ namespace dom_api_tests { } } +namespace type_tests { + using namespace simdjson; + using namespace std; + + const padded_string ALL_TYPES_JSON = R"( + { + "array": [], + + "object": {}, + + "string": "foo", + + "0": 0, + "1": 1, + "-1": -1, + "9223372036854775807": 9223372036854775807, + "-9223372036854775808": -9223372036854775808, + + "9223372036854775808": 9223372036854775808, + "18446744073709551615": 18446744073709551615, + + "0.0": 0.0, + "0.1": 0.1, + "1e0": 1e0, + "1e100": 1e100, + + "true": true, + "false": false, + + "null": null + } + )"_padded; + + bool test_array() { + std::cout << "Running " << __func__ << std::endl; + + const auto key = "array"; + const auto expected_type = dom::element_type::ARRAY; + + dom::parser parser; + simdjson_result result = parser.parse(ALL_TYPES_JSON)[key]; + ASSERT_SUCCESS(result.error()); + + // Test simdjson_result.is() (error chain) + ASSERT_TRUE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is_null()); + + // Test simdjson_result.type() (error chain) + simdjson::error_code error; + dom::element_type type; + result.type().tie(type, error); + ASSERT_SUCCESS(error); + ASSERT_EQUAL(type, expected_type); + + // Test element.is() + dom::element element = result.first; + ASSERT_TRUE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is_null()); + + // Test element.type() + ASSERT_EQUAL(element.type(), expected_type); + + // Test element.get() + dom::array value; + result.get().tie(value, error); + ASSERT_SUCCESS(error); + + return true; + } + + bool test_object() { + std::cout << "Running " << __func__ << std::endl; + + const auto key = "object"; + const auto expected_type = dom::element_type::OBJECT; + + dom::parser parser; + simdjson_result result = parser.parse(ALL_TYPES_JSON)[key]; + ASSERT_SUCCESS(result.error()); + + // Test simdjson_result.is() (error chain) + ASSERT_FALSE(result.is()); + ASSERT_TRUE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is_null()); + + // Test simdjson_result.type() (error chain) + simdjson::error_code error; + dom::element_type type; + result.type().tie(type, error); + ASSERT_SUCCESS(error); + ASSERT_EQUAL(type, expected_type); + + // Test element.is() + dom::element element = result.first; + ASSERT_FALSE(element.is()); + ASSERT_TRUE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is_null()); + + // Test element.type() + ASSERT_EQUAL(element.type(), expected_type); + + // Test element.get() + dom::object value; + result.get().tie(value, error); + ASSERT_SUCCESS(error); + + return true; + } + + bool test_string() { + std::cout << "Running " << __func__ << std::endl; + + const auto key = "string"; + const auto expected_type = dom::element_type::STRING; + + dom::parser parser; + simdjson_result result = parser.parse(ALL_TYPES_JSON)[key]; + ASSERT_SUCCESS(result.error()); + + // Test simdjson_result.is() (error chain) + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_TRUE(result.is()); + ASSERT_TRUE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is_null()); + + // Test simdjson_result.type() (error chain) + simdjson::error_code error; + dom::element_type type; + result.type().tie(type, error); + ASSERT_SUCCESS(error); + ASSERT_EQUAL(type, expected_type); + + // Test element.is() + dom::element element = result.first; + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_TRUE(element.is()); + ASSERT_TRUE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is_null()); + + // Test element.type() + ASSERT_EQUAL(element.type(), expected_type); + + // Test element.get() + std::string_view value; + result.get().tie(value, error); + ASSERT_SUCCESS(error); + ASSERT_EQUAL(value, string_view("foo")); + + const char *value2; + result.get().tie(value2, error); + ASSERT_SUCCESS(error); + ASSERT_EQUAL(string_view(value2), string_view("foo")); + + return true; + } + + bool test_int64(const char *key, int64_t expected_value) { + std::cout << "Running " << __func__ << "(" << key << ")" << std::endl; + + const auto expected_type = dom::element_type::INT64; + + dom::parser parser; + simdjson_result result = parser.parse(ALL_TYPES_JSON)[key]; + ASSERT_SUCCESS(result.error()); + + // Test simdjson_result.is() (error chain) + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_TRUE(result.is()); + if (expected_value < 0) { + ASSERT_FALSE(result.is()); + } else { + ASSERT_TRUE(result.is()); + } + ASSERT_TRUE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is_null()); + + // Test simdjson_result.type() (error chain) + simdjson::error_code error; + dom::element_type type; + result.type().tie(type, error); + ASSERT_SUCCESS(error); + ASSERT_EQUAL(type, expected_type); + + // Test element.is() + dom::element element = result.first; + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_TRUE(element.is()); + if (expected_value < 0) { + ASSERT_FALSE(element.is()); + } else { + ASSERT_TRUE(element.is()); + } + ASSERT_TRUE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is_null()); + + // Test element.type() + ASSERT_EQUAL(element.type(), expected_type); + + // Test element.get() + int64_t value; + result.get().tie(value, error); + ASSERT_SUCCESS(error); + ASSERT_EQUAL(value, expected_value); + + return true; + } + + bool test_uint64(const char *key, uint64_t expected_value) { + std::cout << "Running " << __func__ << "(" << key << ")" << std::endl; + + const auto expected_type = dom::element_type::UINT64; + + dom::parser parser; + simdjson_result result = parser.parse(ALL_TYPES_JSON)[key]; + ASSERT_SUCCESS(result.error()); + + // Test simdjson_result.is() (error chain) + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_TRUE(result.is()); + ASSERT_TRUE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is_null()); + + // Test simdjson_result.type() (error chain) + simdjson::error_code error; + dom::element_type type; + result.type().tie(type, error); + ASSERT_SUCCESS(error); + ASSERT_EQUAL(type, expected_type); + + // Test element.is() + dom::element element = result.first; + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_TRUE(element.is()); + ASSERT_TRUE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is_null()); + + // Test element.type() + ASSERT_EQUAL(element.type(), expected_type); + + // Test element.get() + uint64_t value; + result.get().tie(value, error); + ASSERT_SUCCESS(error); + ASSERT_EQUAL(value, expected_value); + + return true; + } + + bool test_double(const char *key, double expected_value) { + std::cout << "Running " << __func__ << "(" << key << ")" << std::endl; + + const auto expected_type = dom::element_type::DOUBLE; + + dom::parser parser; + simdjson_result result = parser.parse(ALL_TYPES_JSON)[key]; + ASSERT_SUCCESS(result.error()); + + // Test simdjson_result.is() (error chain) + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_TRUE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is_null()); + + // Test simdjson_result.type() (error chain) + simdjson::error_code error; + dom::element_type type; + result.type().tie(type, error); + ASSERT_SUCCESS(error); + ASSERT_EQUAL(type, expected_type); + + // Test element.is() + dom::element element = result.first; + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_TRUE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is_null()); + + // Test element.type() + ASSERT_EQUAL(element.type(), expected_type); + + // Test element.get() + double value; + result.get().tie(value, error); + ASSERT_SUCCESS(error); + ASSERT_EQUAL(value, expected_value); + + return true; + } + + bool test_bool(const char *key, bool expected_value) { + std::cout << "Running " << __func__ << "(" << key << ")" << std::endl; + + const auto expected_type = dom::element_type::BOOL; + + dom::parser parser; + simdjson_result result = parser.parse(ALL_TYPES_JSON)[key]; + ASSERT_SUCCESS(result.error()); + + // Test simdjson_result.is() (error chain) + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_TRUE(result.is()); + ASSERT_FALSE(result.is_null()); + + // Test simdjson_result.type() (error chain) + simdjson::error_code error; + dom::element_type type; + result.type().tie(type, error); + ASSERT_SUCCESS(error); + ASSERT_EQUAL(type, expected_type); + + // Test element.is() + dom::element element = result.first; + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_TRUE(element.is()); + ASSERT_FALSE(element.is_null()); + + // Test element.type() + ASSERT_EQUAL(element.type(), expected_type); + + // Test element.get() + bool value; + result.get().tie(value, error); + ASSERT_SUCCESS(error); + ASSERT_EQUAL(value, expected_value); + + return true; + } + + bool test_null() { + std::cout << "Running " << __func__ << std::endl; + + const auto expected_type = dom::element_type::NULL_VALUE; + + dom::parser parser; + simdjson_result result = parser.parse(ALL_TYPES_JSON)["null"]; + ASSERT_SUCCESS(result.error()); + + // Test simdjson_result.is() (error chain) + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_FALSE(result.is()); + ASSERT_TRUE(result.is_null()); + + // Test simdjson_result.type() (error chain) + simdjson::error_code error; + dom::element_type type; + result.type().tie(type, error); + ASSERT_SUCCESS(error); + ASSERT_EQUAL(type, expected_type); + + // Test element.is() + dom::element element = result.first; + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_FALSE(element.is()); + ASSERT_TRUE(element.is_null()); + + // Test element.type() + ASSERT_EQUAL(element.type(), expected_type); + + // Test element.get() + + return true; + } + + bool run() { + return test_array() && + + test_object() && + + test_string() && + + test_int64("0", 0) && + test_int64("1", 1) && + test_int64("-1", -1) && + test_int64("9223372036854775807", 9223372036854775807LL) && + test_int64("-9223372036854775808", -1 - 9223372036854775807LL) && + + test_uint64("9223372036854775808", 9223372036854775808ULL) && + test_uint64("18446744073709551615", 18446744073709551615ULL) && + + test_double("0.0", 0.0) && + test_double("0.1", 0.1) && + test_double("1e0", 1e0) && + test_double("1e100", 1e100) && + + test_bool("true", true) && + test_bool("false", false) && + + test_null() && + + true; + } + +} + namespace format_tests { using namespace simdjson; using namespace simdjson::dom; @@ -1378,6 +1865,7 @@ int main(int argc, char *argv[]) { std::cout << "Running basic tests." << std::endl; if (parse_api_tests::run() && dom_api_tests::run() && + type_tests::run() && format_tests::run() && document_tests::run() && number_tests::run() && diff --git a/tests/readme_examples.cpp b/tests/readme_examples.cpp index 3b36d24f..e87e334f 100644 --- a/tests/readme_examples.cpp +++ b/tests/readme_examples.cpp @@ -64,6 +64,52 @@ void basics_dom_2() { cout << cars.at("0/tire_pressure/1") << endl; // Prints 39.9} } +namespace treewalk_1 { + void print_json(dom::element element) { + switch (element.type()) { + case dom::element_type::ARRAY: + cout << "["; + for (dom::element child : dom::array(element)) { + print_json(child); + cout << ","; + } + cout << "]"; + break; + case dom::element_type::OBJECT: + cout << "{"; + for (dom::key_value_pair field : dom::object(element)) { + cout << "\"" << field.key << "\": "; + print_json(field.value); + } + cout << "}"; + break; + case dom::element_type::INT64: + cout << int64_t(element) << endl; + break; + case dom::element_type::UINT64: + cout << uint64_t(element) << endl; + break; + case dom::element_type::DOUBLE: + cout << double(element) << endl; + break; + case dom::element_type::STRING: + cout << std::string_view(element) << endl; + break; + case dom::element_type::BOOL: + cout << bool(element) << endl; + break; + case dom::element_type::NULL_VALUE: + cout << "null" << endl; + break; + } + } + + void basics_treewalk_1() { + dom::parser parser; + print_json(parser.load("twitter.json")); + } +} + void basics_ndjson() { dom::parser parser; for (dom::element doc : parser.load_many("x.txt")) {