前言

在基于 C 语言的 PostgreSQL 扩展开发中,您可能会遇到需要处理 JSON 等结构化数据的情况。通常,您可能会在扩展中引入第三方 JSON 解析库,例如 cJSON 或 libjansson。这些库功能强大、易于使用且提供了丰富的特性,但如果我们并未充分利用这些库的高级功能,引入它们则会显得多余。很多时候,我们只是希望从 JSON 中读取某个特定值或简单地遍历它。PostgreSQL 本身已经具备了处理 JSON 数据的足够能力,尽管这些功能可能不如第三方库那样直观。如果您能够充分掌握 PostgreSQL 已有的功能,或许可以避免引入第三方 JSON 库的成本。

在本文中,将向您展示如何使用 PostgreSQL 的 JSONB API 来解析、提取和遍历 JSON 结构。

解析和获取

要使用 PostgreSQL 的 JSONB API,您需要在 C 扩展中包含其头文件:

#include "utils/jsonb.h"

现在,我们可以开始使用 JSONB 了。假设我们有一个 char * 指针,指向一个完整的 JSON 结构内容。我们需要将其转换为 Jsonb *,然后才能对其进行操作。

/* myjson points to a complete JSON content */
void jsonb_example(const char * myjson)
{
Datum jsonb_datum;
Jsonb * jb; /* we first convert char * to datum representation */
jsonb_datum = DirectFunctionCall1(jsonb_in, CStringGetDatum(myjson)); /* then, we convert it to Jsonb * */
jb = DatumGetJsonbP(jsonb_datum);
}

假设我们的 JSON 如下所示:

{
"version": "1.0",
"payload": {
"name": "exampleapp",
"ts_ms": 1720811216000,
"db": "postgresql",
"table": "mytable",
"schema": "myschema"
},
"queries": [
{
"query": "select * from mytable"
},
{
"query": "update mytable set a = 1"
}
]
}

为了获取 payload 组下 db 的值,我们可以使用 JSONB 的 jsonb_get_element() 函数,函数原型如下:

Datum jsonb_get_element(Jsonb *jb, Datum *path, int npath,
bool *isnull, bool as_text);

该函数接受一个 JSONB 指针(即我们之前创建的表示整个 JSON 消息的指针),以及一个Datum 数组和 npath,用于表示 JSON 元素的路径。请注意,此路径不必一直指向标量值,它可以停在另一个内部组或数组,具体取决于您的用例。它还接受一个 isnull 布尔指针,如果找不到元素,函数会将其设置为 false。最后,as_text 布尔值指示函数是否将结果作为 Text DatumJsonb Datum 返回。我倾向于将其设置为 false,以便返回 JSONB Datum,从而可以进一步操作。将其转换为字符串表示也很简单(通过 stringinfo 结构)。请参见以下示例。

/* myjson points to a complete JSON content */
void jsonb_example(const char * myjson)
{
Datum jsonb_datum;
Jsonb * jb; /* variables needed for fetching element */
Datum datum_elems[2];
Datum res;
int numpath = 2;
bool isnull;
StringInfoData strinfo; /* we first convert char * to datum representation */
jsonb_datum = DirectFunctionCall1(jsonb_in, CStringGetDatum(myjson)); /* then, we convert it to Jsonb * */
jb = DatumGetJsonbP(jsonb_datum); /* prepare element paths to fetch, from outer to inner */
initStringInfo(&strinfo);
datum_elems[0] = CStringGetTextDatum("payload");
datum_elems[1] = CStringGetTextDatum("db"); /* fetch it */
res = jsonb_get_element(jb, datum_elems, numPaths, &isnull, false);
if (isnull)
{
/* write NULL if element does not exist */
resetStringInfo(&strinfoo);
appendStringInfoString(&strinfoo, "NULL");
}
else
{
Jsonb *resjb = DatumGetJsonbP(res);
resetStringInfo(strinfoout);
JsonbToCString(&strinfo, &resjb->root, VARSIZE(resjb));
} /* strinfo contains the value of the element at this point. Print it */
elog(WARNING, "data = %s", strinfo.data);
}

现在,如果我们想从数组中的特定索引处获取特定值。例如,queries 数组下索引为 1 的 query 值(update mytable set a = 1)。我们只需要修改描述此路径的 datum_elems。我们可以在数组元素后的 datum_elems 中直接放入一个数字(作为字符串),以告诉函数我们要获取特定索引。请参见以下示例:

/* myjson points to a complete JSON content */
void jsonb_example(const char * myjson)
{
Datum jsonb_datum;
Jsonb * jb; /* variables needed for fetching element */
Datum datum_elems[3];
Datum res;
int numpath = 3;
bool isnull;
StringInfoData strinfo; /* we first convert char * to datum representation */
jsonb_datum = DirectFunctionCall1(jsonb_in, CStringGetDatum(myjson)); /* then, we convert it to Jsonb * */
jb = DatumGetJsonbP(jsonb_datum); /* prepare element paths to fetch, from outer to inner */
initStringInfo(&strinfo);
datum_elems[0] = CStringGetTextDatum("queries");
datum_elems[1] = CStringGetTextDatum("1");
datum_elems[2] = CStringGetTextDatum("query"); /* fetch it */
res = jsonb_get_element(jb, datum_elems, numPaths, &isnull, false);
if (isnull)
{
/* write NULL if element does not exist */
resetStringInfo(&strinfoo);
appendStringInfoString(&strinfoo, "NULL");
}
else
{
Jsonb *resjb = DatumGetJsonbP(res);
resetStringInfo(strinfoout);
JsonbToCString(&strinfo, &resjb->root, VARSIZE(resjb));
} /* strinfo contains the value of the element at this point. Print it */
elog(WARNING, "data = %s", strinfo.data);
}

如你所见,获取特定元素非常简单。我们只需要准备正确的 datum_elems 数组来描述通向某个值的路径,其他部分保持不变。我们可以编写一个辅助函数,通过从单个字符串自动创建 datum_elems 来简化此过程,该字符串用点分隔每个层次结构(例如:“payload.name”,“queries.0.query” 等)。

void getPathElementString(Jsonb * jb, char * path, StringInfoData strinfoout)
{
Datum * datum_elems = NULL;
char * str_elems = NULL, * p = path;
int numPaths = 0, curr = 0;
char * pathcopy = pstrdup(path);
Datum res;
bool isnull; if (!strinfoout)
{
elog(WARNING, "strinfo is null");
return -1;
} /* Count the number of elements in the path */
if (strstr(pathcopy, "."))
{
while (*p != '\0')
{
if (*p == '.')
{
numPaths++;
}
p++;
}
numPaths++; /* Add the last one */
}
else
{
numPaths = 1;
} datum_elems = palloc0(sizeof(Datum) * numPaths);
/* Parse the path into elements */
if (strstr(pathcopy, "."))
{ str_elems= strtok(pathcopy, ".");
if (str_elems)
{
datum_elems[curr] = CStringGetTextDatum(str_elems);
curr++;
while ((str_elems = strtok(NULL, ".")))
{
datum_elems[curr] = CStringGetTextDatum(str_elems);
curr++;
}
}
}
else
{
/* only one level, just use pathcopy*/
datum_elems[curr] = CStringGetTextDatum(pathcopy);
} /* Get the element from JSONB */
res = jsonb_get_element(jb, datum_elems, numPaths, &isnull, false);
if (isnull)
{
resetStringInfo(strinfoout);
appendStringInfoString(strinfoout, "NULL");
}
else
{
Jsonb *resjb = DatumGetJsonbP(res);
resetStringInfo(strinfoout);
JsonbToCString(strinfoout, &resjb->root, VARSIZE(resjb));
} pfree(datum_elems);
pfree(pathcopy);
} /* myjson points to a complete JSON content */
void jsonb_example(const char * myjson)
{
Datum jsonb_datum;
Jsonb * jb;
StringInfoData strinfo; /* we first convert char * to datum representation */
jsonb_datum = DirectFunctionCall1(jsonb_in, CStringGetDatum(myjson)); /* then, we convert it to Jsonb * */
jb = DatumGetJsonbP(jsonb_datum); initStringInfo(&strinfo); getPathElementString(jb, "payload.db", &strinfo);
elog(WARNING, "payload.db= %s", strinfo.data); getPathElementString(jb, "queries.0.query", &strinfo);
elog(WARNING, "queries.0.query= %s", strinfo.data);
}

遍历整个 JSON 结构

现在,我们已经知道如何在知道目标的情况下从 JSON 中获取特定值。有时,我们需要遍历整个 JSON 以构建内部数据结构以满足某些需求。在这种情况下,我们可以利用 JSONB 的迭代函数。以下示例代码将创建一个 JSONB 迭代器,然后尝试遍历其中的每个元素。它会在迭代器即将进入组或数组时以及即将退出组或数组时进行指示。您可以根据需要保存键和值。JSONB 读取的值可以表示为不同的数据类型,例如字符串、二进制、数字等。以下示例尝试将它们转换为字符串(二进制除外)以进行输出。

/* myjson points to a complete JSON content */
void jsonb_iterate_example(const char * myjson)
{
Datum jsonb_datum;
Jsonb * jb; /* iterator related */
JsonbIterator *it;
JsonbValue v;
JsonbIteratorToken r;
char * key = NULL;
char * value = NULL; /* we first convert char * to datum representation */
jsonb_datum = DirectFunctionCall1(jsonb_in, CStringGetDatum(myjson)); /* then, we convert it to Jsonb * */
jb = DatumGetJsonbP(jsonb_datum); it = JsonbIteratorInit(&jb->root);
while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE)
{
switch (r)
{
case WJB_BEGIN_OBJECT:
elog(WARNING, "begin group --------------------");
break;
case WJB_END_OBJECT:
elog(WARNING, "end group --------------------");
break;
case WJB_BEGIN_ARRAY:
elog(WARNING, "begin array --------------------");
break;
case WJB_END_ARRAY:
elog(WARNING, "end array --------------------");
break;
case WJB_KEY:
key = pnstrdup(v.val.string.val, v.val.string.len);
elog(WARNING, "key: %s", key);
break;
case WJB_VALUE:
case WJB_ELEM:
switch (v.type)
{
case jbvNull:
elog(WARNING, "value: NULL");
break;
case jbvString:
value = pnstrdup(v.val.string.val, v.val.string.len);
elog(WARNING, "string value: %s", value);
break;
case jbvNumeric:
{
value = DatumGetCString(DirectFunctionCall1(numeric_out, PointerGetDatum(v.val.numeric)));
elog(WARNING, "numeric value: %s", value);
break;
}
case jbvBool:
elog(WARNING, "boolean value: %s", v.val.boolean ? "true" : "false");
if (v.val.boolean)
value = pnstrdup("true", strlen("true"));
else
value = pnstrdup("false", strlen("false"));
break;
case jbvBinary:
elog(WARNING, "binary value");
break;
default:
elog(WARNING, "unknown value type: %d", v.type);
break;
}
break;
default:
elog(WARNING, "Unknown token: %d", r);
break;
} if (key != NULL && value != NULL)
{
pfree(key);
pfree(value);
key = NULL;
value = NULL;
}
}

总结

PostgreSQL 中的 JSONB API 能做的不仅仅是简单的获取和遍历。例如,它还可以将额外的值推送到现有的 JSONB 结构中。今天我们主要关注获取和遍历,在我看来,这是处理 JSON 时最常见的用例。我希望这里分享的代码示例能对你有所帮助,并防止你在扩展开发中引入第三方 JSON 解析器,因为那可能是多余的。

关于 IvorySQL

lvorySQL 是由瀚高股份主导研发的一款开源的兼容 Oracle 的 PostgreSQL。IvorySQL 与 PostgreSQL 国际社区紧密合作,保持与最新 PG 版本内核同步,为用户提供便捷的升级体验。基于双 Parser 架构设计,100% 与原生 PostgreSQL 兼容,支持丰富的 PostgreSQL 周边工具和扩展,并根据用户需求提供定制化工具。同时,IvorySQL 4.0 提供更全面灵活的 Oracle 兼容功能,具备高度的 SQL 和 PL/SQL 兼容性能够为企业构建更加高效、稳定和灵活的数据库解决方案。

本文由博客一文多发平台 OpenWrite 发布!

如何利用 PostgreSQL 的 JSONB API 作为扩展的轻量级 JSON 解析器的更多相关文章

  1. 接口API测试和返回值JSON解析的插件

    火狐插件1.   HttpRequest作用:接口API测试例子:http://192.168.10.61:8080/ZHCS/user/loginApp.do?phone=admin&pwd ...

  2. 利用扩展方法重写JSON序列化和反序列化

    利用.NET 3.5以后的扩展方法重写JSON序列化和反序列化,在代码可读性和可维护性上更加加强了. 首先是不使用扩展方法的写法 定义部分: /// <summary>  /// JSON ...

  3. 利用Swashbuckle生成Web API Help Pages

    利用Swashbuckle生成Web API Help Pages 这系列文章是参考了.NET Core文档和源码,可能有人要问,直接看官方的英文文档不就可以了吗,为什么还要写这些文章呢? 原因如下: ...

  4. 利用Swashbuckle生成Web API Help Pages

    利用Swashbuckle生成Web API Help Pages 本文将通过Swagger的.NET Core的实现封装工具Swashbuckle来生成上一篇-<创建ASP.NET Core ...

  5. postgresql C/C++ API 接口

    1,postgresql学习uri推荐 http://www.php100.com/manual/PostgreSQL8/ http://www.php100.com/manual/PostgreSQ ...

  6. 如何利用【百度地图API】,制作房产酒店地图?(下)——结合自己的数据库

    原文:如何利用[百度地图API],制作房产酒店地图?(下)--结合自己的数据库 摘要:应广大API爱好者要求,写了一篇利用自己数据库标点的文章…… -------------------------- ...

  7. 如何利用【百度地图API】进行定位?非GPS定位

    原文:如何利用[百度地图API]进行定位?非GPS定位 如果你可以上网,如果你有火狐浏览器,那么恭喜你.你能很容易使用以下代码进行定位! ------------------------------- ...

  8. 利用koa打造restful API

    概述 最近学习利用koa搭建API接口,小有所得,现在记录下来,供以后开发时参考,相信对其他人也有用. 就目前我所知道的而言,API有2种,一种是jsonp这种API,前端通过ajax来进行跨域请求获 ...

  9. Web Api 内部数据思考 和 利用http缓存优化 Api

    在上篇<Web Api 端点设计 与 Oauth>后,接着我们思考Web Api 的内部数据: 其他文章:<API接口安全加强设计方法> 第一  实际使用应该返回怎样的数据 ? ...

  10. 利用 Android 系统原生 API 实现分享功能

    利用 Android 系统原生 API 实现分享功能 这篇文章提供一个封装好的 Share2 库供大家参考. GitHub 项目地址:Share2 大家知道,要调用 Android 系统内建的分享功能 ...

随机推荐

  1. Qt编写安防视频监控系统29-掉线重连

    一.前言 掉线重连在很早很早以前就做了,基本上的方法都是搞个变量存储最后收到图片的时间,然后开个定时器判断,如果不在暂停模式下,当前时间和最后收到图片的时间差值超过了设定的超时时间,比如5s则认为掉线 ...

  2. SHA256 64 位加密

    /// <summary> /// SHA256 64位加密 /// </summary> /// <param name="input">&l ...

  3. Java 中toString方法在枚举中的应用:展示枚举字段信息

    在Java编程中,枚举(enum)是一种特殊的数据类型,它允许程序员定义一组固定的常量.枚举类型在Java中非常有用,尤其是在需要表示一组固定选项(如星期.月份.方向等)时.尽管枚举类型在定义时看起来 ...

  4. c# Lamda表达式 简化语法例子

    看到一个老代码里的方法,是判断两个string 数组是否存在相同的元素: 快一百行代码了..... public bool HasRole(string[] roleList) { bool resu ...

  5. 【刷题】牛客模拟面试 > 模拟面试报告

    https://www.nowcoder.com/interview/ai/index 1-TCP协议的流量控制和拥塞控制 TCP的流量控制是基于窗口机制实现的: 在建立连接时, 发送方和接收方都会建 ...

  6. java线程用法和区别

    从操作系统的角度讲,os会维护一个ready queue(就绪的线程队列).并且在某一时刻cpu只为ready queue中位于队列头部的线程服务.但是当前正在被服务的线程可能觉得cpu的服务质量不够 ...

  7. MongoDB:【索引类型】单字段索引、复合索引、其他索引

    查询性能分析:

  8. 记一次 .NET某数字化协同管理系统 内存暴涨分析

    一:背景 1. 讲故事 高级调试训练营里的一位朋友找到我,说他们跑在linux上的.NET程序出现了内存泄露的情况,上windbg观察发现内存都是IMAGE给吃掉了,那些image都标记了 doubl ...

  9. ceph数据重构原理

    本文分享自天翼云开发者社区<ceph数据重构原理>,作者:x****n 在分布式存储系统Ceph中,硬盘故障是一种常见问题.为了保证数据安全,当发生硬盘故障后,分布式存储系统会依据算法对故 ...

  10. 如何在WPS和Word/Excel中直接使用DeepSeek功能

    以下是将DeepSeek功能集成到WPS中的详细步骤,无需本地部署模型,直接通过官网连接使用:1. 下载并安装OfficeAI插件 (1)访问OfficeAI插件下载地址:https://www.of ...