如何利用 PostgreSQL 的 JSONB API 作为扩展的轻量级 JSON 剖析器 ...

打印 上一主题 下一主题

主题 970|帖子 970|积分 2910

前言

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

要使用 PostgreSQL 的 JSONB API,您必要在 C 扩展中包含其头文件:
  1. #include "utils/jsonb.h"
复制代码
现在,我们可以开始使用 JSONB 了。假设我们有一个 char * 指针,指向一个完整的 JSON 结构内容。我们必要将其转换为 Jsonb *,然后才能对其进行操作。
  1. /* myjson points to a complete JSON content */
  2. void jsonb_example(const char * myjson)
  3. {
  4.     Datum jsonb_datum;
  5.     Jsonb * jb;
  6.     /* we first convert char * to datum representation */
  7.     jsonb_datum = DirectFunctionCall1(jsonb_in, CStringGetDatum(myjson));
  8.     /* then, we convert it to Jsonb * */
  9.     jb = DatumGetJsonbP(jsonb_datum);
  10. }
复制代码
假设我们的 JSON 如下所示:
  1. {
  2.   "version": "1.0",
  3.   "payload": {
  4.     "name": "exampleapp",
  5.     "ts_ms": 1720811216000,
  6.     "db": "postgresql",
  7.     "table": "mytable",
  8.     "schema": "myschema"
  9.   },
  10.   "queries": [
  11.     {
  12.       "query": "select * from mytable"
  13.     },
  14.     {
  15.       "query": "update mytable set a = 1"
  16.     }
  17.   ]
  18. }
复制代码
为了获取 payload 组下 db 的值,我们可以使用 JSONB 的 jsonb_get_element() 函数,函数原型如下:
  1. Datum jsonb_get_element(Jsonb *jb, Datum *path, int npath,
  2. bool *isnull, bool as_text);
复制代码
该函数担当一个 JSONB 指针(即我们之前创建的表示整个 JSON 消息的指针),以及一个Datum 数组和 npath,用于表示 JSON 元素的路径。请注意,此路径不必一直指向标量值,它可以停在另一个内部组或数组,具体取决于您的用例。它还担当一个 isnull 布尔指针,如果找不到元素,函数会将其设置为 false。最后,as_text 布尔值指示函数是否将结果作为 Text Datum 或 Jsonb Datum 返回。我倾向于将其设置为 false,以便返回 JSONB Datum,从而可以进一步操作。将其转换为字符串表示也很简单(通过 stringinfo 结构)。请参见以下示例。
  1. /* myjson points to a complete JSON content */
  2. void jsonb_example(const char * myjson)
  3. {
  4.     Datum jsonb_datum;
  5.     Jsonb * jb;
  6.     /* variables needed for fetching element */
  7.     Datum datum_elems[2];
  8.     Datum res;
  9.     int numpath = 2;
  10.     bool isnull;
  11.     StringInfoData strinfo;
  12.     /* we first convert char * to datum representation */
  13.     jsonb_datum = DirectFunctionCall1(jsonb_in, CStringGetDatum(myjson));
  14.     /* then, we convert it to Jsonb * */
  15.     jb = DatumGetJsonbP(jsonb_datum);
  16.     /* prepare element paths to fetch, from outer to inner */
  17.     initStringInfo(&strinfo);
  18.     datum_elems[0] = CStringGetTextDatum("payload");
  19.     datum_elems[1] = CStringGetTextDatum("db");
  20.     /* fetch it */
  21.     res = jsonb_get_element(jb, datum_elems, numPaths, &isnull, false);
  22.     if (isnull)
  23.     {
  24.         /* write NULL if element does not exist */
  25.         resetStringInfo(&strinfoo);
  26.         appendStringInfoString(&strinfoo, "NULL");
  27.     }
  28.     else
  29.     {
  30.         Jsonb *resjb = DatumGetJsonbP(res);
  31.         resetStringInfo(strinfoout);
  32.         JsonbToCString(&strinfo, &resjb->root, VARSIZE(resjb));
  33.     }
  34.     /* strinfo contains the value of the element at this point. Print it */
  35.     elog(WARNING, "data = %s", strinfo.data);
  36. }
复制代码
现在,如果我们想从数组中的特定索引处获取特定值。例如,queries 数组下索引为 1 的 query 值(update mytable set a = 1)。我们只必要修改描述此路径的 datum_elems。我们可以在数组元素后的 datum_elems 中直接放入一个数字(作为字符串),以告诉函数我们要获取特定索引。请参见以下示例:
  1. /* myjson points to a complete JSON content */
  2. void jsonb_example(const char * myjson)
  3. {
  4.     Datum jsonb_datum;
  5.     Jsonb * jb;
  6.     /* variables needed for fetching element */
  7.     Datum datum_elems[3];
  8.     Datum res;
  9.     int numpath = 3;
  10.     bool isnull;
  11.     StringInfoData strinfo;
  12.     /* we first convert char * to datum representation */
  13.     jsonb_datum = DirectFunctionCall1(jsonb_in, CStringGetDatum(myjson));
  14.     /* then, we convert it to Jsonb * */
  15.     jb = DatumGetJsonbP(jsonb_datum);
  16.     /* prepare element paths to fetch, from outer to inner */
  17.     initStringInfo(&strinfo);
  18.     datum_elems[0] = CStringGetTextDatum("queries");
  19.     datum_elems[1] = CStringGetTextDatum("1");
  20.     datum_elems[2] = CStringGetTextDatum("query");
  21.     /* fetch it */
  22.     res = jsonb_get_element(jb, datum_elems, numPaths, &isnull, false);
  23.     if (isnull)
  24.     {
  25.         /* write NULL if element does not exist */
  26.         resetStringInfo(&strinfoo);
  27.         appendStringInfoString(&strinfoo, "NULL");
  28.     }
  29.     else
  30.     {
  31.         Jsonb *resjb = DatumGetJsonbP(res);
  32.         resetStringInfo(strinfoout);
  33.         JsonbToCString(&strinfo, &resjb->root, VARSIZE(resjb));
  34.     }
  35.     /* strinfo contains the value of the element at this point. Print it */
  36.     elog(WARNING, "data = %s", strinfo.data);
  37. }
复制代码
如你所见,获取特定元素非常简单。我们只必要准备正确的 datum_elems 数组来描述通向某个值的路径,其他部分保持稳固。我们可以编写一个辅助函数,通过从单个字符串自动创建 datum_elems 来简化此过程,该字符串用点分隔每个层次结构(例如:“payload.name”,“queries.0.query” 等)。
  1. void getPathElementString(Jsonb * jb, char * path, StringInfoData strinfoout)
  2. {
  3.     Datum * datum_elems = NULL;
  4.     char * str_elems = NULL, * p = path;
  5.     int numPaths = 0, curr = 0;
  6.     char * pathcopy = pstrdup(path);
  7.     Datum res;
  8.     bool isnull;
  9.     if (!strinfoout)
  10.     {
  11.         elog(WARNING, "strinfo is null");
  12.         return -1;
  13.     }
  14.     /* Count the number of elements in the path */
  15.     if (strstr(pathcopy, "."))
  16.     {
  17.         while (*p != '\0')
  18.         {
  19.             if (*p == '.')
  20.             {
  21.                  numPaths++;
  22.             }
  23.             p++;
  24.         }
  25.         numPaths++; /* Add the last one */
  26.     }
  27.     else
  28.     {
  29.         numPaths = 1;
  30.     }
  31.     datum_elems = palloc0(sizeof(Datum) * numPaths);
  32.     /* Parse the path into elements */
  33.     if (strstr(pathcopy, "."))
  34.     {
  35.         str_elems= strtok(pathcopy, ".");
  36.         if (str_elems)
  37.         {
  38.             datum_elems[curr] = CStringGetTextDatum(str_elems);
  39.             curr++;
  40.             while ((str_elems = strtok(NULL, ".")))
  41.             {
  42.                 datum_elems[curr] = CStringGetTextDatum(str_elems);
  43.                 curr++;
  44.             }
  45.         }
  46.     }
  47.     else
  48.     {
  49.         /* only one level, just use pathcopy*/
  50.         datum_elems[curr] = CStringGetTextDatum(pathcopy);
  51.     }
  52.     /* Get the element from JSONB */
  53.     res = jsonb_get_element(jb, datum_elems, numPaths, &isnull, false);
  54.     if (isnull)
  55.     {
  56.         resetStringInfo(strinfoout);
  57.         appendStringInfoString(strinfoout, "NULL");
  58.     }
  59.     else
  60.     {
  61.         Jsonb *resjb = DatumGetJsonbP(res);
  62.         resetStringInfo(strinfoout);
  63.         JsonbToCString(strinfoout, &resjb->root, VARSIZE(resjb));
  64.     }
  65.     pfree(datum_elems);
  66.     pfree(pathcopy);
  67. }
  68. /* myjson points to a complete JSON content */
  69. void jsonb_example(const char * myjson)
  70. {
  71.     Datum jsonb_datum;
  72.     Jsonb * jb;
  73.     StringInfoData strinfo;
  74.     /* we first convert char * to datum representation */
  75.     jsonb_datum = DirectFunctionCall1(jsonb_in, CStringGetDatum(myjson));
  76.     /* then, we convert it to Jsonb * */
  77.     jb = DatumGetJsonbP(jsonb_datum);
  78.     initStringInfo(&strinfo);
  79.     getPathElementString(jb, "payload.db", &strinfo);
  80.     elog(WARNING, "payload.db= %s", strinfo.data);
  81.     getPathElementString(jb, "queries.0.query", &strinfo);
  82.     elog(WARNING, "queries.0.query= %s", strinfo.data);
  83. }
复制代码
遍历整个 JSON 结构

现在,我们已经知道如何在知道目标的情况下从 JSON 中获取特定值。有时,我们必要遍历整个 JSON 以构建内部数据结构以满足某些需求。在这种情况下,我们可以利用 JSONB 的迭代函数。以下示例代码将创建一个 JSONB 迭代器,然后实验遍历其中的每个元素。它会在迭代器即将进入组或数组时以及即将退出组或数组时进行指示。您可以根据必要生存键和值。JSONB 读取的值可以表示为差别的数据范例,例如字符串、二进制、数字等。以下示例实验将它们转换为字符串(二进制除外)以进行输出。
  1. /* myjson points to a complete JSON content */
  2. void jsonb_iterate_example(const char * myjson)
  3. {
  4.     Datum jsonb_datum;
  5.     Jsonb * jb;
  6.     /* iterator related */
  7.     JsonbIterator *it;
  8.     JsonbValue v;
  9.     JsonbIteratorToken r;
  10.     char * key = NULL;
  11.     char * value = NULL;
  12.     /* we first convert char * to datum representation */
  13.     jsonb_datum = DirectFunctionCall1(jsonb_in, CStringGetDatum(myjson));
  14.     /* then, we convert it to Jsonb * */
  15.     jb = DatumGetJsonbP(jsonb_datum);
  16.     it = JsonbIteratorInit(&jb->root);
  17.     while ((r = JsonbIteratorNext(&it, &v, false)) != WJB_DONE)
  18.     {
  19.         switch (r)
  20.         {
  21.             case WJB_BEGIN_OBJECT:
  22.                 elog(WARNING, "begin group --------------------");
  23.                 break;
  24.             case WJB_END_OBJECT:
  25.                 elog(WARNING, "end group --------------------");
  26.                 break;
  27.             case WJB_BEGIN_ARRAY:
  28.                 elog(WARNING, "begin array --------------------");
  29.                 break;
  30.             case WJB_END_ARRAY:
  31.                 elog(WARNING, "end array --------------------");
  32.                 break;
  33.             case WJB_KEY:
  34.                 key = pnstrdup(v.val.string.val, v.val.string.len);
  35.                 elog(WARNING, "key: %s", key);
  36.                 break;
  37.             case WJB_VALUE:
  38.             case WJB_ELEM:
  39.                 switch (v.type)
  40.                 {
  41.                     case jbvNull:
  42.                         elog(WARNING, "value: NULL");
  43.                         break;
  44.                     case jbvString:
  45.                         value = pnstrdup(v.val.string.val, v.val.string.len);
  46.                         elog(WARNING, "string value: %s", value);
  47.                         break;
  48.                     case jbvNumeric:
  49.                     {
  50.                         value = DatumGetCString(DirectFunctionCall1(numeric_out, PointerGetDatum(v.val.numeric)));
  51.                         elog(WARNING, "numeric value: %s", value);
  52.                         break;
  53.                     }
  54.                     case jbvBool:
  55.                         elog(WARNING, "boolean value: %s", v.val.boolean ? "true" : "false");
  56.                         if (v.val.boolean)
  57.                             value = pnstrdup("true", strlen("true"));
  58.                         else
  59.                             value = pnstrdup("false", strlen("false"));
  60.                         break;
  61.                     case jbvBinary:
  62.                         elog(WARNING, "binary value");
  63.                         break;
  64.                     default:
  65.                         elog(WARNING, "unknown value type: %d", v.type);
  66.                         break;
  67.                 }
  68.                 break;
  69.             default:
  70.                 elog(WARNING, "Unknown token: %d", r);
  71.                 break;
  72.         }
  73.         if (key != NULL && value != NULL)
  74.         {
  75.             pfree(key);
  76.             pfree(value);
  77.             key = NULL;
  78.             value = NULL;
  79.         }
  80. }
复制代码
总结

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 发布!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

盛世宏图

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表