2

Using boost::json::value, how do I check if a certain key exists?

The following code:

void checkMessage(std::string msg)
{
    boost::json::value json = boost::json::parse(payload);

    auto type = json.at("type").as_string();
    auto param1 = json.at("param1").as_string();
    auto param1 = json.at("param2").as_int64(); <<=== IT CHASHES HERE

    std::cout << "Received: " << std::endl;
    std::cout << "type: " << type << std::endl;
    std::cout << "param1: " << param1 << std::endl;
    std::cout << "param2: " << param2 << std::endl;
}

Crashes when trying to get the param2 from my json data:

terminate called after throwing an instance of 'boost::wrapexcept<std::out_of_range>'
  what():  out of range
Aborted (core dumped)

When the param2 is missing:

std::string msg = "{\"type\", \"t1\", \"param1\": \"test parameter 1\"}";

Is there a way to check if the parameter exists before copying the json key value to the variable?

Is there a better way to do it?

Mendes
  • 17,489
  • 35
  • 150
  • 263
  • Does an exception get thrown? Maybe you can try catching the exvception to see if the key exists.. – Irelia Jul 19 '23 at 19:32
  • Please post working code (compiling) because there's entirely too many sloppy mistakes and illegal comments in there. The JSON isn't [valid JSON](https://jsonlint.com/) either ¯\\_(ツ)_/¯ – sehe Jul 19 '23 at 20:07

2 Answers2

0

As the commented suggested you can catch the out of range error (system_error exception).

Or you can be more cautious and check first:

auto* param1 = json.as_object().if_contains("param1");
auto* param2 = json.as_object().if_contains("param2");

Likewise you can be more cautious about the string interpretation too:

Live On Coliru

#include <boost/json/src.hpp>
#include <iostream>

void checkMessage(std::string msg) {
    auto json = boost::json::parse(msg);

    auto type   = json.at("type").as_string();
    auto* param1 = json.as_object().if_contains("param1");
    auto* param2 = json.as_object().if_contains("param2");
    auto* value1 = (param1? param1->if_string() : nullptr);
    auto* value2 = (param2? param2->if_string() : nullptr);

    std::cout << "Received: " << std::endl;
    std::cout << "type:     " << type       << std::endl;
    std::cout << "value1:   " << (value1 ? *value1 : "(MISSING)") << std::endl;
    std::cout << "value2:   " << (value2 ? *value2 : "(MISSING)") << std::endl;
}

int main() {//
    checkMessage(R"({"type": "t1", "param1": "test parameter 1"})");
}

Printing

type:     "t1"
value1:   "test parameter 1"
value2:   "(MISSING)"
sehe
  • 374,641
  • 47
  • 450
  • 633
0

You can use object::if_contains method:

#include <boost/json.hpp>
#include <boost/json/src.hpp>

#include <iostream>

void Check(::boost::json::object const & object, ::boost::json::string_view const name)
{
    ::boost::json::value const * const p_value{object.if_contains(name)};
    if (p_value)
    {
        ::std::cout << name << ": " << p_value->as_string();
    }
    else
    {
        ::std::cout << name << " is missing";
    }
    ::std::cout << "\n";
}

int main()
{
    ::boost::json::value json{::boost::json::parse(R"({"param1": "foo", "param3": "bar"})")};
    ::boost::json::object const & root{json.as_object()};
    Check(root, "param1");
    Check(root, "param2");
    Check(root, "param3");
}

param1: "foo"
param2 is missing
param3: "bar"

online compiler

user7860670
  • 35,849
  • 4
  • 58
  • 84
  • That's weird code. Why did you use the initializer-list constructor to force `json` iinto an array? Please consider fixing, and also make things readable: http://coliru.stacked-crooked.com/a/57052ae7d7840d1b (Are you using AI to generate the code-snippet?) – sehe Jul 19 '23 at 20:33
  • Interesting! It seems that GCC and clang disagree about whether the initializer-list overload should take precedence. I'd expect it should, like GCC thinks: https://godbolt.org/z/698caMPqd Of course, you can still avoid the entire confusion by writing it as per my comment :) – sehe Jul 19 '23 at 22:22
  • @sehe I think gcc should not even consider selecting constructor overload (because no constructor should be invoked) and just materialize prvalue. https://godbolt.org/z/ecs51YaY8 It seems gcc has a general tendency to prefer initializer_list, i.e. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=83264 – user7860670 Jul 19 '23 at 22:34
  • I'm not talking about preferences, here. My personal preference agrees with you. However, the C++ standard mandates that initializer_list constructors take precedence when using uniform-initialization syntax (so `value{x}` instead of `value(x)`). I'm not 100% convinced that applies here (there's a defaulted `storage_ptr` parameter trailing), but anyways, it's better to avoid the whole mess as long as compilers don't agree. – sehe Jul 19 '23 at 22:37
  • Filed an issue for awareness https://github.com/boostorg/json/issues/920 – sehe Jul 19 '23 at 22:46
  • @sehe Also now i've realized that you've posted your answer suggesting `if_contains` prior to mine, making it kinda unnecessary. Yeah... But here you've wrote "also make things readable" and then post a really bizarre kind of code. copy-initialization everywhere? `auto json = json::parse(R"...` - shadowing existing name with a different kind of entity while using old one in the same statement? That's probably one on the most evil things imaginable, on par with omitted curly braces after `if`. I have no idea where your ideas of "readable" come from, but they are certainly opposite to mine. – user7860670 Jul 19 '23 at 22:49
  • Let's not debate style. If your style is your taste, that's fine with me. I've never seen anyone consistently ::qualify toplevel namespaces. The conditional-with initializer was weird to me `if (T x = f(); x)` is exactly equivalent to the simpler c++03 `if (T x = f())`. The array work-around was the tipping point making me ask you whether the code was generated :) No harm intended. Cheers, thanks for answering the [tag:boost] tag! – sehe Jul 19 '23 at 22:56