用函数模板,写一个简单高效的 JSON 查询器的方法介绍

yizhihongxing

使用函数模板来写一个简单高效的 JSON 查询器,需要以下步骤:

1. 定义 JSON 数据结构

首先需要定义一个JSON数据结构,以便对其进行查询。这里我们将使用一个基于std::map的存储结构来表示JSON对象。其中,每个JSON对象的键值对都将被存储为std::map中的一对键-值。对于嵌套的JSON对象,我们可以将其表示为std::map的嵌套结构。

class Json
{
public:
    using Dict = std::map<std::string, Json>;
    using List = std::vector<Json>;

    enum Type
    {
        Null,
        Boolean,
        Number,
        String,
        Array,
        Object
    };

    Json() : type_(Null) {}
    Json(std::nullptr_t) : type_(Null) {}
    Json(bool value) : type_(Boolean), bool_v_(value) {}
    Json(int value) : type_(Number), int_v_(value) {}
    Json(double value) : type_(Number), double_v_(value) {}
    Json(const std::string& value) : type_(String), string_v_(value) {}
    Json(const Dict& value) : type_(Object), dict_v_(value) {}
    Json(const List& value) : type_(Array), list_v_(value) {}

    template <typename T>
    T get() const;

    Type type() const { return type_; }

    bool is_null() const { return type_ == Null; }
    bool is_boolean() const { return type_ == Boolean; }
    bool is_number() const { return type_ == Number; }
    bool is_string() const { return type_ == String; }
    bool is_array() const { return type_ == Array; }
    bool is_object() const { return type_ == Object; }

    std::string to_string() const;

    // do query using  a JsonPath expression
    Json query(const std::string& path) const;

private:
    Type type_;
    union
    {
        bool bool_v_;
        int int_v_;
        double double_v_;
        std::string string_v_;
        Dict dict_v_;
        List list_v_;
    };
};

2. 定义 JsonPath

然后,需要定义一种查询语言——JsonPath,用于查询JSON中的元素。JsonPath是一种基于字符串的语言,可以按照指定的路径从JSON对象中检索出相应的元素,类似于XPath在XML文档中的使用。

这里,我们将采用由Jayway公司制定的JsonPath标准,其中包含了十分丰富的查询选项,可以准确定位和检索JSON中的数据。例如,使用JsonPath可以对列表中的子元素,取出特定的键值对,或者在多嵌套层次的JSON数据中取出指定深度的子元素等。

以下是JsonPath的常见符号和使用方法:

符号 意义
$ 根元素
. 点,后接属性名称
[] 下标或属性名称
* 所有
[start:end] 切片选取
[?(expr)] 过滤器

例如,我们要从JSON数据中取出第二个元素,可以写为:$[1]

3. 编写 Json 查询器函数模板

接下来,我们将定义查询函数模板。查询函数模板可以接受任何类型的JSON结构作为输入参数,并按照给定的JsonPath表达式进行查询。查询结果将以Json对象的形式返回给用户。

代码实现:

class JsonParseError
{
public:
    JsonParseError(const std::string& msg) : message_(msg) {}
    const std::string& message() const { return message_; }

private:
    std::string message_;
};

template <typename T>
T Json::get() const
{
    if constexpr (std::is_same_v<T, bool>)
    {
        return bool_v_;
    }
    else if constexpr (std::is_same_v<T, int>)
    {
        return int_v_;
    }
    else if constexpr (std::is_same_v<T, double>)
    {
        return double_v_;
    }
    else if constexpr (std::is_same_v<T, std::string>)
    {
        return string_v_;
    }
    else if constexpr (std::is_same_v<T, Dict>)
    {
        if (is_object())
            return dict_v_;
        else
            throw JsonParseError("Json object expected");
    }
    else if constexpr (std::is_same_v<T, List>)
    {
        if (is_array())
            return list_v_;
        else
            throw JsonParseError("Json array expected");
    }
    else if constexpr (std::is_same_v<T, Json>)
    {
        return *this;
    }
    else
    {
        throw std::invalid_argument("Unsupported type");
    }
}

// 递归函数实现JsonPath的语法解析和查询
template <typename T>
void query_path(const T& value, std::deque<std::string>& path, Json& result)
{
    if (path.empty())
    {
        result = value;
        return;
    }

    std::string name = path.front();
    path.pop_front();

    if (value.is_object())
    {
        const auto& dict = value.get<Dict>();
        auto it = dict.find(name);
        if (it == dict.end())
        {
            result = Json();
            return;
        }
        else
        {
            query_path(it->second, path, result);
            return;
        }
    }
    else if (value.is_array())
    {
        // 获取数组下标
        if (name == "*")
        {
            // 如果表达式中包含*表示查询所有元素,注意这里返回的是一个数组
            const auto& list = value.get<List>();
            Json::List result_list;
            for (const auto& item : list)
            {
                Json temp;
                query_path(item, path, temp);
                if (!temp.is_null())
                    result_list.push_back(temp);
            }
            result = Json(result_list);
            return;
        }
        else
        {
            int index = std::stoi(name);
            const auto& list = value.get<List>();
            if (index < 0 || index >= static_cast<int>(list.size()))
            {
                result = Json();
                return;
            }
            else
            {
                query_path(list[index], path, result);
                return;
            }
        }
    }
    else
    {
        result = Json();
        return;
    }
}

Json Json::query(const std::string& path_expr) const
{
    if (path_expr.empty())
        return Json();

    if (path_expr.front() != '$')
        throw JsonParseError("JsonPath must start with $");

    std::deque<std::string> path;

    // 解析JsonPath
    for (auto it = path_expr.begin() + 1; it != path_expr.end();)
    {
        if (*it == '.')
        {
            ++it;
            auto pos = path_expr.find_first_of(".$[", it - path_expr.begin());
            if (pos == std::string::npos)
            {
                path.emplace_back(it, path_expr.end());
                it = path_expr.end();
            }
            else
            {
                path.emplace_back(it, it + pos - (it - path_expr.begin()));
                it += pos - (it - path_expr.begin());
            }
        }
        else if (*it == '[')
        {
            ++it;
            if (*it == '\'' || *it == '\"')
            {
                auto pos = path_expr.find_first_of("\'\"", it + 1 - path_expr.begin());
                if (pos == std::string::npos || *(it + pos + 1) != ']')
                    throw JsonParseError("Invalid JsonPath expression");
                path.emplace_back(it + 1, it + pos);
                it = it + pos + 2;
            }
            else if (std::isdigit(*it))
            {
                auto pos = path_expr.find_first_of("]", it - path_expr.begin());
                if (pos == std::string::npos)
                    throw JsonParseError("Invalid JsonPath expression");
                path.emplace_back(it, it + pos - (it - path_expr.begin()));
                it += pos - (it - path_expr.begin()) + 1;
            }
            else if (*it == '*')
            {
                path.emplace_back("*");
                it += 2;
            }
            else
            {
                throw JsonParseError("Invalid JsonPath expression");
            }
        }
        else
        {
            throw JsonParseError("Invalid JsonPath expression");
        }
    }

    Json result;
    query_path(*this, path, result);
    return result;
}

4. 测试 Json 查询器

使用函数模板编写的 Json 查询器实现后,我们需要进行一些测试来确保其正常工作。以下是两个示例:

// 测试JsonPath:$.name
Json test_json = Json("{\"name\": \"Tom\", \"age\": 20}");
Json result = test_json.query("$.name");
std::cout << "Result: " << result.to_string() << std::endl;
// 预期输出:Result: "Tom"

// 测试JsonPath:$..name
Json test_json = Json("{\"name\": \"Tom\", \"age\": 20, \"friends\": [{\"name\": \"Mike\", \"age\": 21}]}");
Json result = test_json.query("$..name");
std::cout << "Result: " << result.to_string() << std::endl;
// 预期输出:Result: ["Tom", "Mike"]

以上是使用函数模板,写一个简单高效的 JSON 查询器的方法介绍,包含定义JSON数据结构,定义JsonPath,编写Json查询器函数模板,以及测试Json查询器的完整攻略。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:用函数模板,写一个简单高效的 JSON 查询器的方法介绍 - Python技术站

(0)
上一篇 2023年5月23日
下一篇 2023年5月23日

相关文章

  • C语言中递增和递减运算符的区别

    下面详细讲解C语言中递增和递减运算符的区别。 什么是递增和递减运算符 在C语言中,递增运算符++和递减运算符–分别可以将变量的值增加或者减少1。它们可以作用于整型、浮点型、字符型等基本数据类型的变量。 递增和递减运算符可以在变量前面或者后面使用,使用的方式决定了它们的执行顺序,也影响了最终计算出的结果。 前置和后置运算符的区别 递增和递减运算符可以前置(放…

    C 2023年5月10日
    00
  • Python中的取模运算方法

    当我们需要计算两数之间的余数时,可以使用 Python 中的取模运算符 “%”(百分号). 其中,运算符左侧为被除数,右侧为除数。 示例1: a = 10 b = 3 print(a % b) # 输出为1 上面的代码中,a 为被除数,b 为除数,取模运算符 “%” 计算出 a 除以 b 的余数是 1。 示例2: x = -10 y = 3 print(x …

    C 2023年5月22日
    00
  • C语言给应用程序传递参数

    下面是关于C语言给应用程序传递参数的完整使用攻略,包含以下几个方面的内容: 参数传递方式 使用系统变量 argc 和 argv 获取参数 示例说明 使用 getopt 函数解析参数 参数传递方式 C语言中,给应用程序传递参数可以通过以下两种方式: 通过命令行传递参数 通过环境变量传递参数 通常较常见的是通过命令行传递参数。 使用系统变量 argc 和 arg…

    C 2023年5月9日
    00
  • C语言system函数使用方法详解

    C语言system函数使用方法详解 什么是system函数 system函数是C语言中的标准库函数之一,用于在程序中调用shell命令。 使用方法 system函数的声明如下: int system(const char* command); 其中,参数command表示要执行的shell命令。 system函数返回一个整数值,表示执行命令后的返回值。在Li…

    C 2023年5月23日
    00
  • 基于Turbo C(V2.0)编译错误信息的详细介绍

    首先,我们需要了解Turbo C(V2.0)是一种针对DOS操作系统的C语言编译器。在使用过程中,由于各种原因可能会出现编译错误,需要及时查找并修复问题。 以下是详细介绍Turbo C(V2.0)编译错误信息的攻略: 1. 查看编译错误信息 在编译过程中,Turbo C会输出错误信息,包括错误类型、错误位置、错误描述等等。我们需要认真查看这些信息,需要特别关…

    C 2023年5月23日
    00
  • 安全账户管理器初始化失败 lsass.exe 0XC0000(SAM文件问题)

    安全账户管理器(LSASS,Local Security Authority Subsystem Service)是Windows操作系统中非常重要的一个组件,负责用户身份鉴定、安全策略实施等工作。如果在启动或者使用Windows操作系统时,出现了“安全账户管理器初始化失败 lsass.exe 0XC0000(SAM文件问题)”的错误提示,这通常是由于系统文…

    C 2023年5月23日
    00
  • 解析Java的Jackson库中Streaming API的使用

    解析Java的Jackson库中Streaming API的使用 简介 Jackson是一种Java库,用于在Java对象和JSON之间进行相互转换。Jackson具有多种API用于读取和编写JSON结构。其中,Jackson Streaming API提供了一种更高效和灵活的方式来解析和生成大型JSON文档。本文将介绍Jackson Streaming A…

    C 2023年5月23日
    00
  • C语言中实现itoa函数的实例

    C语言中实现itoa函数的实例 什么是itoa函数? itoa函数是C++的标准库函数,可以将整型数据转换成对应的字符串。但在C中并没有该函数,为了方便C程序员的编程,我们需要自己实现该函数。 实现itoa函数的过程 实现itoa函数主要包括以下几个步骤: 判断待转换的整数是否为负数,如果是负数,则需要在最终的字符串前面添加负号。 将整型数按位分解,得到每个…

    C 2023年5月23日
    00
合作推广
合作推广
分享本页
返回顶部