Skip to content

Use user-defined exceptions #244

Closed
Closed

Description

@nlohmann

After a small survey on Reddit it seems to be a good idea to replace the standard exceptions used in the library right now with user-defined exceptions.

The motivations for this are:

  • Better differentiation between exceptions raised by the STL and those raised by the JSON library.
  • Clearer semantics of the exceptions rather and the possibility of better diagnosis and documentation.

In the course of this ticket, the list of currently used exceptions need to be classified into user-defined exception classes:

throw std::domain_error("JSON pointer has no parent");
throw std::domain_error("JSON pointer must be empty or begin with '/'");
throw std::domain_error("array index must not begin with '0'");
throw std::domain_error("cannot compare iterators of different containers");
throw std::domain_error("cannot compare order of object iterators");
throw std::domain_error("cannot create object from initializer list");
throw std::domain_error("cannot use at() with " + type_name());
throw std::domain_error("cannot use construct with iterators from " + first.m_object->type_name());
throw std::domain_error("cannot use erase() with " + type_name());
throw std::domain_error("cannot use insert() with " + type_name());
throw std::domain_error("cannot use key() for non-object iterators");
throw std::domain_error("cannot use offsets with object iterators");
throw std::domain_error("cannot use operator[] for object iterators");
throw std::domain_error("cannot use operator[] with " + type_name());
throw std::domain_error("cannot use push_back() with " + type_name());
throw std::domain_error("cannot use swap() with " + type_name());
throw std::domain_error("cannot use value() with " + type_name());
throw std::domain_error("escape error: '~' must be followed with '0' or '1'");
throw std::domain_error("incompatible ReferenceType for get_ref, actual type is " +
throw std::domain_error("invalid value to unflatten");
throw std::domain_error("iterator does not fit current value");
throw std::domain_error("iterators are not compatible");
throw std::domain_error("iterators do not fit current value");
throw std::domain_error("iterators do not fit");
throw std::domain_error("only objects can be unflattened");
throw std::domain_error("passed iterators may not belong to container");
throw std::domain_error("type must be array, but is " + type_name());
throw std::domain_error("type must be number, but is " + type_name());
throw std::domain_error("type must be object, but is " + type_name());
throw std::domain_error("type must be string, but is " + type_name());
throw std::domain_error("unsuccessful: " + val.dump());
throw std::domain_error("values in object must be primitive");
throw std::invalid_argument("JSON patch must be an array of objects");
throw std::invalid_argument("missing low surrogate");
throw std::invalid_argument("missing or wrong low surrogate");
throw std::invalid_argument("operation value '" + op + "' is invalid");
throw std::invalid_argument(error_msg + " must have member '" + member + "'");
throw std::invalid_argument(error_msg + " must have string member '" + member + "'");
throw std::invalid_argument(error_msg);
throw std::out_of_range("array index " + std::to_string(idx) + " is out of range");
throw std::out_of_range("array index '-' (" +
throw std::out_of_range("cannot get value");
throw std::out_of_range("code points above 0x10FFFF are invalid");
throw std::out_of_range("iterator out of range");
throw std::out_of_range("iterators out of range");
throw std::out_of_range("key '" + key + "' not found");
throw std::out_of_range("key '" + last_path + "' not found");
throw std::out_of_range("unresolved reference token '" + reference_token + "'");

Furthermore, STL exceptions need to be caught and rethrown as user-defined exception in several cases.

Activity

  1. whackashoe commented on May 14, 2016

    @whackashoe
    Contributor

    For simplicity's sake I think it is much easier to deal with libs that have fewer rather than more different types of exceptions (as long as there isn't weird overlaps). So, here's a few suggestions on how that could be done:

    
    throw std::domain_error("type must be array, but is " + type_name());
    throw std::domain_error("type must be number, but is " + type_name());
    throw std::domain_error("type must be object, but is " + type_name());
    throw std::domain_error("type must be string, but is " + type_name());
    
    

    These could be type_error

    
    throw std::domain_error("iterator does not fit current value");
    throw std::domain_error("iterators are not compatible");
    throw std::domain_error("iterators do not fit current value");
    throw std::domain_error("iterators do not fit");
    throw std::out_of_range("iterator out of range");
    throw std::out_of_range("iterators out of range");
    
    

    Could be invalid_iterator

    
    throw std::domain_error("cannot use at() with " + type_name());
    throw std::domain_error("cannot use construct with iterators from " + first.m_object->type_name());
    throw std::domain_error("cannot use erase() with " + type_name());
    throw std::domain_error("cannot use insert() with " + type_name());
    throw std::domain_error("cannot use key() for non-object iterators");
    throw std::domain_error("cannot use offsets with object iterators");
    throw std::domain_error("cannot use operator[] for object iterators");
    throw std::domain_error("cannot use operator[] with " + type_name());
    throw std::domain_error("cannot use push_back() with " + type_name());
    throw std::domain_error("cannot use swap() with " + type_name());
    throw std::domain_error("cannot use value() with " + type_name());
    
    
    

    Could be not_supported or invalid_action or invalid_method

    
    throw std::out_of_range("key '" + key + "' not found");
    throw std::out_of_range("key '" + last_path + "' not found");
    
    

    Could be lookup_error or key_error

    Anyways, this way there doesn't need to be this large tree of possible exceptions to keep in head if they are grouped a bit.

  2. gregmarr commented on May 14, 2016

    @gregmarr
    Contributor

    I'd recommend that if you add multiple exception classes derived from any std::exception related class, that you have your own base class first, so that they can all be handled at once if the user desires.

  3. self-assigned this
    on Jul 3, 2016
  4. nlohmann commented on Jul 3, 2016

    @nlohmann
    OwnerAuthor

    I started a feature branch for user-defined exceptions. There is now a class basic_json::exception with a subclass invalid_iterator which is used in all iterator-related scenarios. The user-defined exception class uses an integer error code to facility better documentation of exceptions - see Doxygen-doucumentation for class basic_json::exception.

    I shall replace more standard exceptions in the next days. I would be happy for comments!

  5. markand commented on Aug 8, 2016

    @markand

    @whackashoe there are various reasons why we should add own exceptions. For example when you access a property that does not exist or is it invalid, you can throw an exception like missing_property or invalid_property where you can bundle the property name which is valuable for the user of the library.

    The general error message that comes with what() is always handy, but sometimes you want more information like the line number in a file, the column, many things like that and exceptions are just the way to go for this purpose.

  6. nlohmann commented on Oct 16, 2016

    @nlohmann
    OwnerAuthor

    @krzysztofwos, thanks for the suggestions. Here are my thoughts:

    • I also think it is a good idea to also provide certain information as members of the exception object rather than just inside the what() string.
    • As a consequence, I would leave the what() string rather general which simplifies the code. The in-depth explanation can then be moved to the documentation. (Then again, do we actually need a what() string?) At the same time, users of the library are free to use as much additional information to compose their own diagnostic messages.
    • One could even think about using a single basic_json variable to hold all information on the exception. What do you think? Is it a good idea to create exception objects that again rely on dynamic memory?
  7. gregmarr commented on Oct 16, 2016

    @gregmarr
    Contributor

    As long as it's not due to an out of memory condition, then you should be okay with dynamic memory allocation.

  8. 4 remaining items

  9. nlohmann commented on Nov 16, 2016

    @nlohmann
    OwnerAuthor

    The user-defined exceptions are done now, see the new feature branch. This branch also contains the option to switch off assertions (#296).

  10. jlisee commented on Feb 15, 2017

    @jlisee

    This is really cool to see, do you have any idea when you would be merging this in?

  11. nlohmann commented on Feb 15, 2017

    @nlohmann
    OwnerAuthor

    Not really. There are still 1-2 issues that need to be fixed before I can start with the 3.0.0 release.

  12. nlohmann commented on Mar 8, 2017

    @nlohmann
    OwnerAuthor

    This issue is nearly complete. If anyone would like to have a look at the soon-to-be-merged state, please have a look at branch https://github.com/nlohmann/json/tree/feature/exceptions_3.0.0. It contains user-defined exceptions, and only the documentation needs some final touches.

  13. added
    solution: proposed fixa fix for the issue has been proposed and waits for confirmation
    and removed
    state: please discussplease discuss the issue or vote for your favorite option
    on Mar 12, 2017
  14. nlohmann commented on Mar 12, 2017

    @nlohmann
    OwnerAuthor

    Merged this feature into develop. All there is left to do is to document the changes between old and new exceptions in the wiki page.

  15. removed
    solution: proposed fixa fix for the issue has been proposed and waits for confirmation
    on Mar 12, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions