From bd3d67e889915f0d1dd7e07ac28c4f44ecc42ef8 Mon Sep 17 00:00:00 2001 From: Daniel Lemire Date: Sat, 9 Jul 2022 10:54:28 -0400 Subject: [PATCH] Documenting a specific use case where you need a value if and only if it is another key is not present (#1865) * Documenting a specific use case. * Adding more comments and documentation. --- doc/basics.md | 18 ++-- include/simdjson/generic/ondemand/object.h | 6 +- include/simdjson/generic/ondemand/value.h | 3 +- tests/ondemand/ondemand_object_tests.cpp | 103 +++++++++++++++++++++ 4 files changed, 121 insertions(+), 9 deletions(-) diff --git a/doc/basics.md b/doc/basics.md index fe68e3653..fb6960cc0 100644 --- a/doc/basics.md +++ b/doc/basics.md @@ -261,11 +261,15 @@ idiomatic C++ iterators, operators and casts. Besides the document instances and native types (`double`, `uint64_t`, `int64_t`, `bool`), we also access Unicode (UTF-8) strings (`std::string_view`), objects (`simdjson::ondemand::object`) and arrays (`simdjson::ondemand::array`). -We also have a generic type (`simdjson::ondemand::value`) which represents a potential +We also have a generic ephemeral type (`simdjson::ondemand::value`) which represents a potential array or object, or scalar type (`double`, `uint64_t`, `int64_t`, `bool`, `null`, string) inside an array or an object. Both generic types (`simdjson::ondemand::document` and `simdjson::ondemand::value`) have a `type()` method returning a `json_type` value describing the -value (`json_type::array`, `json_type::object`, `json_type::number`, `json_type::string`, `json_type::boolean`, `json_type::null`). +value (`json_type::array`, `json_type::object`, `json_type::number`, `json_type::string`, +`json_type::boolean`, `json_type::null`). A generic value (`simdjson::ondemand::value`) +is only valid temporarily, as soon as you access other values, other keys in objects, etc. +it becomes invalid: you should therefore consume the value immediately by converting it to a +scalar type, an array or an object. Advanced users who need to determine the number types (integer or float) dynamically, should review our section [dynamic number types](#dynamic-number-types). Indeed, @@ -300,10 +304,12 @@ support for users who avoid exceptions. See [the simdjson error handling documen comparison. For efficiency reason, you should avoid looking up the same field repeatedly: e.g., do not do `object["foo"]` followed by `object["foo"]` with the same `object` instance. If you consume an object twice: `std::string_view(object["foo"]` followed by `std::string_view(object["foo"]`, your code - is in error. Furthermore, you can only consume one field at a time, on the same object. Thus - if you have retrieved `content["bids"].get_array()` and you later call `content["asks"].get_array()`, then the - first array should no longer be accessed: it would be unsafe to do so. You can detect such mistakes by first - compiling and running the code in Debug mode: an OUT_OF_ORDER_ITERATION error is generated. + is in error. Furthermore, you can only consume one field at a time, on the same object. The + value instance you get from `content["bids"]` becomes invalid when you call `content["asks"]`. + If you have retrieved `content["bids"].get_array()` and you later call + `content["asks"].get_array()`, then the first array should no longer be accessed: it would be + unsafe to do so. You can detect such mistakes by first compiling and running the code in + Debug mode: an OUT_OF_ORDER_ITERATION error is generated. > NOTE: JSON allows you to escape characters in keys. E.g., the key `"date"` may be written as > `"\u0064\u0061\u0074\u0065"`. By default, simdjson does *not* unescape keys when matching by default. diff --git a/include/simdjson/generic/ondemand/object.h b/include/simdjson/generic/ondemand/object.h index e16624816..fc758f936 100644 --- a/include/simdjson/generic/ondemand/object.h +++ b/include/simdjson/generic/ondemand/object.h @@ -38,7 +38,8 @@ public: * e.g. `object["a"]` will match `{ "a": 1 }`, but will *not* match `{ "\u0061": 1 }`. * * You must consume the fields on an object one at a time. A request for a new key - * invalidates previous field values: it makes them unsafe. E.g., the array + * invalidates previous field values: it makes them unsafe. The value instance you get + * from `content["bids"]` becomes invalid when you call `content["asks"]`. The array * given by content["bids"].get_array() should not be accessed after you have called * content["asks"].get_array(). You can detect such mistakes by first compiling and running * the code in Debug mode (or with the macro `SIMDJSON_DEVELOPMENT_CHECKS` set to 1): an @@ -75,7 +76,8 @@ public: * that only one field is returned. * * You must consume the fields on an object one at a time. A request for a new key - * invalidates previous field values: it makes them unsafe. E.g., the array + * invalidates previous field values: it makes them unsafe. The value instance you get + * from `content["bids"]` becomes invalid when you call `content["asks"]`. The array * given by content["bids"].get_array() should not be accessed after you have called * content["asks"].get_array(). You can detect such mistakes by first compiling and running * the code in Debug mode (or with the macro `SIMDJSON_DEVELOPMENT_CHECKS` set to 1): an diff --git a/include/simdjson/generic/ondemand/value.h b/include/simdjson/generic/ondemand/value.h index 967a55b26..4f424606f 100644 --- a/include/simdjson/generic/ondemand/value.h +++ b/include/simdjson/generic/ondemand/value.h @@ -11,7 +11,8 @@ class object; class raw_json_string; /** - * An ephemeral JSON value returned during iteration. + * An ephemeral JSON value returned during iteration. It is only valid for as long as you do + * not access more data in the JSON document. */ class value { public: diff --git a/tests/ondemand/ondemand_object_tests.cpp b/tests/ondemand/ondemand_object_tests.cpp index ddb550003..5f1a62e18 100644 --- a/tests/ondemand/ondemand_object_tests.cpp +++ b/tests/ondemand/ondemand_object_tests.cpp @@ -1074,8 +1074,111 @@ namespace object_tests { TEST_SUCCEED(); } + + bool test_strager() { + // Find exactly one of a/myint or b/myint, if both or none are present, + // return false. + auto process = [](ondemand::object root) -> std::pair { + /////////////////////////////////////////////////////////// + // The core idea with On-Demand is that you should visit everything only + // once. + // + // Thus, with On-Demand, you must consume a field once you have accessed it. + // Thus if you do root["a"], you must full consume the corresponding + // value (if present) before you move to another field (e.g., root["a"]). + // + // If, somehow, you only want the value corresponding to "a" if some + // other key is not present, then you must still parse/consume the value + // corresponding to "a", and later throw the result away. + /////////////////////////////////////////////////////////// + uint64_t myint; + // Notice in the line below that I use chaining... I do not + // first convert root["a"] to an object and then seek + // "myint" into. Chaining works in this instance because + // I only need to access the object 'root["a"]' for a single + // key, if I needed to do more work on root["a"], then I should + // create an object. + if(root["a"]["myint"].get(myint) == SUCCESS) { + // We want myint to be discarded if "b" is also present. + if(! root["b"].error() ) { + // Oops, the other key is also present!!! We must throw + // away 'myint' and return false. + // Notice how I do not parse the value corresponding to + // '"b"', as it is unneeded. + return std::make_pair(false, 0); + } + // All is good, + return std::make_pair(true,myint); + } + // Now we know that "a" is not present, so seek "b". + if(root["b"]["myint"].get(myint) == SUCCESS) { + return std::make_pair(true, myint); + } + // None are present. + return std::make_pair(false, 0); + }; + + TEST_START(); + + { + ondemand::parser parser; + padded_string json = R"({ "a": { "myint": 42 } })"_padded; + std::cout << " - Subtest 'a' - JSON: " << (json) << " ..." << std::endl; + ondemand::document doc; + ASSERT_SUCCESS(parser.iterate(json).get(doc)); + ondemand::object obj; + ASSERT_SUCCESS(doc.get_object().get(obj)); + std::pair ret = process(obj); + ASSERT_TRUE(ret.first); + ASSERT_EQUAL(ret.second, 42); + } + + { + ondemand::parser parser; + padded_string json = R"({ "b": { "myint": 42 } })"_padded; + std::cout << " - Subtest 'b' - JSON: " << (json) << " ..." << std::endl; + ondemand::document doc; + ASSERT_SUCCESS(parser.iterate(json).get(doc)); + ondemand::object obj; + ASSERT_SUCCESS(doc.get_object().get(obj)); + std::pair ret = process(obj); + ASSERT_TRUE(ret.first); + ASSERT_EQUAL(ret.second, 42); + } + + + { + ondemand::parser parser; + padded_string json = R"({ "b": { "myint": 42 }, "a": { "myint": 42 } })"_padded; + std::cout << " - Subtest 'b&a' - JSON: " << (json) << " ..." << std::endl; + ondemand::document doc; + ASSERT_SUCCESS(parser.iterate(json).get(doc)); + ondemand::object obj; + ASSERT_SUCCESS(doc.get_object().get(obj)); + std::pair ret = process(obj); + ASSERT_FALSE(ret.first); + ASSERT_EQUAL(ret.second, 0); + } + + { + ondemand::parser parser; + padded_string json = R"({ "z": { "myint": 42 }, "y": { "myint": 42 } })"_padded; + std::cout << " - Subtest 'none' - JSON: " << (json) << " ..." << std::endl; + ondemand::document doc; + ASSERT_SUCCESS(parser.iterate(json).get(doc)); + ondemand::object obj; + ASSERT_SUCCESS(doc.get_object().get(obj)); + std::pair ret = process(obj); + ASSERT_FALSE(ret.first); + ASSERT_EQUAL(ret.second, 0); + } + + TEST_SUCCEED(); + } + bool run() { return + test_strager() && issue1745() && issue1742() && issue1742_value() &&