首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在不向try/catch块发送垃圾邮件的情况下,抛出指示错误JSON数据位置的异常

在不向try/catch块发送垃圾邮件的情况下,抛出指示错误JSON数据位置的异常
EN

Stack Overflow用户
提问于 2020-10-13 03:11:28
回答 2查看 118关注 0票数 0

我有一个复杂的JSON对象,带有一个设置列表,如下所示(示例是人为设计的):

代码语言:javascript
复制
{
  "configuration" : {
     "name": "<room name>",
     "players": {"min": 3, "max": 5},
     "options": {
        "rounds": {"prompt": "Enter number of rounds", "default": 10},
        "score_to_win": {"prompt": "Enter score to win", "default": 500}
     }
  }
  // ... lots of other information, can get pretty deeply nested
}

我可以使用如下代码处理它(使用nlohmann-json):

代码语言:javascript
复制
void parse_json(const nlohmann::json &j) {
    nlohmann::json config = j.at("configuration");
    std::string name = config.at("name");
    int minplayers = config.at("players").at("min");
    int maxplayers = config.at("players").at("max");
    for (auto& it : config.at("options").items()) {
        std::string key = it.key();
        std::string prompt = it.value().at("prompt");
        int defval = it.value().at("default");
    }
}

但是如果JSON不正确,它不会给出非常有用的错误消息:

代码语言:javascript
复制
try {
    parse_json(nlohmann::json::parse(R"({"configuration" : {
     "name": "cool room name",
     "players": {"min": 4, "max": "6"},
     "options": {
        "rounds": {"prompt": "Enter number of rounds", "default": 10},
        "score_to_win": {"prompt": "Enter score to win", "default": 500}
     }
  }})"));
} catch (std::exception &ex) {
   std::cout << ex.what() << "\n";
}

// [json.exception.type_error.302] type must be number, but is string

我可以将try/catches包含在每一行中,以说明异常发生在何处,但这会使代码变得混乱。有没有一种方法可以报告JSON中的错误位置,而不会把代码弄得太乱?

EN

回答 2

Stack Overflow用户

发布于 2020-10-13 06:44:31

您可以跟踪最后访问的路径(作为JSON指针),并报告其上的错误:

代码语言:javascript
复制
void parse_json(const nlohmann::json &j) {
    nlohmann::json::json_pointer last_accessed;
    auto at = [&j, &last_accessed](const nlohmann::json::json_pointer& ptr) {
        last_accessed = ptr;
        return j.at(ptr);
    };
    try {
        auto config_ptr = "/configuration"_json_pointer;
        nlohmann::json config = at(config_ptr);
        std::string name = at(config_ptr / "name");
        ...
    } catch (...) {
        throw last_accessed;
    }
}

使用稍微智能一点的异常类型,您可以报告底层的nlohmann::json错误,但我希望您能理解它的要点。

票数 1
EN

Stack Overflow用户

发布于 2020-10-15 13:30:05

基于@Botje的想法,我创建了一个帮助器类来更新json指针。这就是它:

代码语言:javascript
复制
#include <string>
#include "nlohmann/json.hpp"
/* 
    LastAccessedHelper(json, json_pointer)
    Construct with a JSON object and a json_pointer to store the access path in
    Supports const or non-const access, change the type alias in the definition accordingly

    "arg" below can be a json_pointer, string, or size_t

    at(arg) - return json at a given position
    at() - access json at root -- effectively "convert" helper to json object
    enter(arg) - move last_accessed to a new location and return child helper based at new location
    exit() - tell child helper to move last_accessed back to the previous location
*/

// helper function to count the number of levels in a pointer, no native way of doing it
int json_ptr_size(const nlohmann::json::json_pointer &_ptr) {
    // make a copy and pop_back() until the root :(
    nlohmann::json::json_pointer ptr(_ptr);
    int size = 0;
    while (!ptr.empty()) {
        ptr.pop_back();
        size++;
    }
    return size;
}

struct LastAccessedHelper {
    using json = const nlohmann::json;      // set to be const or non-const based on your use case
    using json_pointer = nlohmann::json::json_pointer;

    json &j;
    json_pointer &last_accessed;
    int at_level;
    int exit_level;

    LastAccessedHelper(json &j, json_pointer &last_accessed)
        : j{j}, last_accessed{last_accessed}, at_level{0}, exit_level{0}
    {}
    LastAccessedHelper(json &j, json_pointer &last_accessed, int exit_level)
        : j{j}, last_accessed{last_accessed}, at_level{0}, exit_level{exit_level}
    {}
    
    /* at() - return json at a given position */
    json& at(const json_pointer& ptr) {
        reset();
        at_level = json_ptr_size(ptr);
        last_accessed /= ptr;
        return j.at(ptr);
    }
    json& at(const std::string &s) {
        return at(json_pointer() / s);
    }
    json& at(size_t t) {
        return at(json_pointer() / t);
    }

    /* at() no arguments - "convert" LastAccessedHelper to a json object */
    json& at() {
        return at(json_pointer());
    }

    /* enter() - move last_accessed to a new location and return child helper based at new location */
    LastAccessedHelper enter(const json_pointer& ptr) {
        reset();
        last_accessed /= ptr;
        return LastAccessedHelper(j.at(ptr), last_accessed, json_ptr_size(ptr));
    }
    LastAccessedHelper enter(const std::string &s) {
        return enter(json_pointer() / s);
    }
    LastAccessedHelper enter(size_t t) {
        return enter(json_pointer() / t);
    }

    /* exit() - tell child helper to move last_accessed back to the previous location */
    void exit() {
        reset();
        unwind(exit_level);
        exit_level = 0;
    }

    /* helpers */
    void unwind(int levels) {
        for (int i=0; i<levels; i++) {
            last_accessed.pop_back();
        }
    }
    void reset() {
        unwind(at_level);
        at_level = 0;
    }
};

对@Botje解决方案的主要更改是

  • 使用连接和pop_back()就地修改路径
  • 一个“检查点”功能enter()/exit(),该功能在新位置创建帮助器以减少间接级别。

使用它,示例代码如下所示:

代码语言:javascript
复制
void parse_json(const nlohmann::json &j) {
    nlohmann::json::json_pointer last_accessed;
    LastAccessedHelper acc{j, last_accessed};
    try {
        auto config = acc.enter("configuration");
        std::string name = config.at("name");
        int maxplayers = config.at("/players/max"_json_pointer);
        int minplayers = config.at("/players/min"_json_pointer);
        auto options = config.enter("options");
        for (auto& [key,val] : options.at().items()) {
            nlohmann::json::json_pointer key_ptr;
            key_ptr /= key;
            std::string prompt = options.at(key_ptr / "prompt");
            int defval = options.at(key_ptr / "default");
        }
        options.exit();
        config.exit();
    } catch (std::exception &ex) {
        throw std::runtime_error("Exception at " + last_accessed.to_string() + ": " + ex.what());
    }
}

您可以在子函数中使用它,也可以递归使用它:

代码语言:javascript
复制
void parse_tree(const nlohmann::json &j, nlohmann::json::json_pointer &last_accessed) {
    LastAccessedHelper acc{j, last_accessed};
    int value = acc.at("value");
    for (auto& [key,val] : acc.at("children").items()) {
        last_accessed /= key;
        parse_tree(val, last_accessed);
        last_accessed.pop_back();
    }
}

void parse_json(const nlohmann::json &j) {
    nlohmann::json::json_pointer last_accessed;
    LastAccessedHelper acc{j, last_accessed};
    try {
        parse_tree(acc.at("tree"), last_accessed);
    } catch (std::exception &ex) {
        throw std::runtime_error("Exception at " + last_accessed.to_string() + ": " + ex.what());
    }
}

/* Will parse something like
{"tree": {
    "value" : -50,
    "children": [
        {"value": -30, "children": []},
        {"value": 20, "children": [{"value": 10, "children": []}]}
    ]
}}
*/

它并不完美,但它适用于我的用例,而且相对较小;我认为它对有同样问题的人会很有用。我能看到的一些改进点是:

  • RAII enter()/exit()?如果值得的话,可以使用std::uncaught_exceptions()来更好地支持迭代,要么添加一个助手函数,要么降低使用instantiate
  • Improve

json_ptr_size() function的成本

如果您看到它们,请随时提出建议/进行改进:)

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/64324002

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档