前言

在基于 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音视频开发5-vlc事件订阅

    一.前言 事件订阅可以拿到文件长度.播放进度.播放状态改变等信息,vlc的事件订阅机制封装的比较友好,只需要先创建一个事件管理器,然后逐个订阅自己感兴趣的需要的事件,不感兴趣的可以不要订阅,只有订阅了 ...

  2. Optional的使用与解析

    引言 今天在项目中看到了大量Optional的使用,之前我也了解过Optional,是Java8中的新特性,并且便利地为空指针问题提供了处理方法,可以避免繁琐的if/else. 但是并没有真正在项目中 ...

  3. 【宝塔】搭建随机图API

    创建站点 首先,我们打开服务器的宝塔面板,如果没安装的推荐安装一个,因为这个对建站小白来说非常的方便. 我们参加一个 API 站点 然后到站点设置里申请一个 ssl 认证,再打开强制 https 添加 ...

  4. 使用iText对PDF文件签章和验章

    PDF是国际板式文件标准,使用的范围很广.OFD为国产板式文件标准,设计起点很高,天然支持国产签名算法(SM2.SM3):具有后发优势,目前市场占有率仍然很低,前途光明而又漫长.PDF标准并不支持国产 ...

  5. G1原理—3.G1是如何提升垃圾回收效率

    大纲 1.G1为了提升GC的效率设计了哪些核心机制 2.G1中的记忆集是什么 3.G1中的位图和卡表 4.记忆集和卡表有什么关系 5.RSet记忆集是怎么更新的 6.DCQ机制的底层原理是怎样的 7. ...

  6. CyclicBarrier底层实现和原理

    1.CyclicBarrier 字面意思是可循环(Cyclic)使用的屏障(Barrier).它要做的事情是让一组线程到达一个屏障(同步点)时被阻塞,直到最后一个线程到达屏障时候,屏障才会开门.所有被 ...

  7. C 将十进制数转换成二~十六进制数中的任意一种

    问题:将一个十进制整数转换成二~十六进制数中的任意一种进制数 代码: #include <stdio.h> #include <stdlib.h> int b; int i = ...

  8. Paillier算法

    介绍 1999年欧密会上,首次提出Paillier算法,2001年,Damgard等人对该方案简化,推出当前最优的Paillier方案. 加密方案 Carmichael函数 困难问题 合数剩余类问题( ...

  9. 一篇关于c语言的大补帖

    一晃今年又开始了,作为一个失意的中年技术男,现在的心境真的是五味杂陈.赶紧写一篇吧,我怕过了这个点,今年就在没有那个心情去写了. 因为是基础嘛,从事软件开发以来c或者c++相关的东西断断续续 也刷了差 ...

  10. Jenkins插件:Git

    Jenkins插件:Git Jenkins,作为一款备受欢迎的持续集成和持续交付工具,在软件开发领域发挥着举足轻重的作用.它不仅能够与Git无缝集成,还能实现代码的自动化拉取.构建与部署,极大地提升了 ...