Json: Check value for existence by json_pointer

Created on 14 Aug 2018  路  23Comments  路  Source: nlohmann/json

I have found no proper way to check element existence.
I can use json.at("..."_json_pointer) but it will throw an exception if it is missed.
I can use json.value() method. But I have to do it twice to check all cases (or use default that will not used in my json).

needs more info stale

Most helpful comment

You may try to get value() work without exceptions. It will help in most cases. Additional, you can add some value, that can't be found in json, and use it as default.
Or you can look at std::optional

All 23 comments

Internally, the JSON Pointer implementation also relies on exceptions. That is, there would be no real difference between implementing a find(const json_pointer&) interface as it would be literally the same as a calling at and coping with possible exceptions.

yep, now I have looked in code. So, looks like I have to avoid such logics...

Related: #1017

@nlohmann I need this json.find("..."_json_pointer)

json.find("..."_json_pointer)

Just one question, what value should be for end() in this case?

@nlohmann Hmm good question, in mind answer is json.end(), but this is way not obvious.

Problem is what json.value("..."_json_pointer, /*default*/) throws exception when object in path not found.

I was forced to write this on my side:

std::istringstream s(key);
std::string k;

const Data* ptr = nullptr;

while (std::getline(s, k, '/'))
{
    if (k.empty())
    {
        ptr = &m_cData;
    }
    else if (ptr == nullptr)
    {
        return false;
    }
    else
    {
        auto it = ptr->find(k);

        if (it == ptr->end())
        {
            return false;
        }
        else
        {
            ptr = &it.value();
        }
    }
}

The issue is that iterators of different containers cannot be compared as this would be undefined behavior.

Therefore, writing code like

if (j.find("/foo/bar"_json_pointer) != j.end())
  ...

is not possible.

@nlohmann Ok, maybe this: json.search<std::string>("/foo/bar"_json_pointer, [](const auto& value) {});?

Passing a lambda feels strange. I'd rather pass a std::pair<bool, json::iterator> with the bool indicating whether the search was successful (asserting the iterator can be dereferenced).

@nlohmann You said that with iterator no possible. Maybe this:

std::string value;
if (json.try_get(/foo/bar"_json_pointer, value))
{
 //do something when found
}

What would value mean in this situation?

You may try to get value() work without exceptions. It will help in most cases. Additional, you can add some value, that can't be found in json, and use it as default.
Or you can look at std::optional

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

will it resolved or just closed? ;(

There is no solution for the issue yet. That's why it was closed eventually.

std::optional cannot be used as we are targeting C++11. I still have not understood how the function should look like.

  1. You can do something like std::string::npos for your find (in this case all containers should have same end())
  2. You can make it by using std::optional but only for c++17 :)
  3. You can make your own "optional" class. And implement it by using std::optional; if c++17 or higher was used (stl already have implemented std::optional template)
  1. What should the function return in case of success?
  2. That is not an option.
  3. That is not really an option.
  1. the same as a typical find method returns (iterator). Yes. I haven't see absolutely good solution to.

Why you do not want to do your own std::optional? It would be modern solution (but should be not called find I think) :)

@nlohmann

Maybe this?
float* value = json.get_if<float>("..."_json_pointer);

I don't like the idea that it would return a pair<bool, json::iterator> because at that point i would expect the iterator to have some kind of _meaning_. For example, if I took that iterator and did iterator-ish things to it, what does it mean? Sure, I can ask "it == end" to mean "not found", but what is it the _end_ of? What does it mean if I increment that iterator? Which iterator concept does it model?

If you really want _iterators_, then I think you need to have a different kind of iterator, because you're modeling the structure very different. I think this is a bit like directory_iterator versus recursive_directory_iterator, where the first can only iterate across one level (like json::iterator), and the second can iterate across a full tree. I think drawing inspiration from might be helpful.

On the other hand, doing the get_if like @ArnCarveris suggests seems simple enough, but there's something about it that feels like it's too loose. For example, I'm effectively getting an interior pointer into the object, but knowing the rules on how long that pointer is valid would be very hard to pin down. But you could just say "the pointer is valid until any part of it or its surrounding containers (and so forth) are modified", and maybe that fixes that? I don't know. I think we'd have to think this through.

Does anyone have any prior art on other ways that boost or STL or some other solid library has offered a "reach inside some arbitrary heterogenous tree structure" has approached this? I can't imagine we're the first ones :)

I would not like to add our own implementation of std::optional to the library. As @jaredgrubb also pointed out: JSON pointers are not iterators - especially since they lack a means to describe the "past the end" position to indicate "not found". Unless we find a way to express the result of such a function, we cannot really proceed here.

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

Was this page helpful?
0 / 5 - 0 ratings