JSON5E - 人类友好的JSON5

Lobsters Hottest 工具

摘要

libpdjson5 是一个公共领域的 C 语言库,用于解析 JSON、JSON5 和 JSON5E,具有完整的 Unicode 支持、极小的内存占用和流式 API。它是 pdjson 的一个分支,进行了多项改进,包括对 JSON5E 的支持。

<p><a href="https://lobste.rs/s/6zlnpk/json5e_json5_for_humans">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/07/20 09:34

boris-kolpackov/libpdjson5 源代码: https://github.com/boris-kolpackov/libpdjson5 # 面向 C 语言的公共领域 JSON/JSON5 解析器

一个专注于正确性、符合 ANSI C99 标准、完整 Unicode (UTF-8) 支持、最小内存占用以及简单 API 的公共领域 JSON、JSON5 (https://json5.org/) 和 JSON5E 解析器。作为流式 API,可以处理任意大的 JSON,且仅需少量内存(内存大小等于 JSON 中最大字符串的长度)。

大多数 C 语言的 JSON 库似乎在一些重要方面存在不足:字符串支持有问题(如果字符串包含 \u0000 怎么办?)、Unicode 支持有缺陷或缺失、许可证限制严格、测试/模糊测试不充分。本库旨在避免这些缺陷。另请注意,由于 API 简洁性的约束,它并非最快的 JSON 解析器。

libpdjson5 库是 pdjson (https://github.com/skeeto/pdjson) 的一个分支,进行了以下更改和改进,包括:

  • 支持 JSON5 和 JSON5E。
  • 支持使用新流重新打开解析器(但重用已分配的内存)。
  • 支持传播 IO 错误。
  • 为对象成员名称使用独立的 JSON_NAME 事件(而不是 JSON_STRING)。
  • 移除了对数字的解析支持,将解析工作留给调用者。
  • 流式多文档模式变为可选,而非默认模式。
  • 各种 API 改进(constbooluint32/64_tsize_t 等)。
  • 性能改进。

如果您正在寻找相同解析方法的 C++ 版本,请参考 libstud-json (https://github.com/libstud/libstud-json),它基于本库进行解析,并包含序列化支持。

默认情况下,解析器期望标准 JSON,不多也不少,因此即使是稍微不符合规范的 JSON 也会被拒绝。输入预期为 UTF-8,并且库返回的所有字符串都是 UTF-8 格式,中间可能包含 \0 字符,这就是为什么 size 输出参数很重要。编码字符(\uxxxx)会被解码并重新编码为 UTF-8。支持以相邻编码字符形式表示的 UTF-16 代理对。如果启用了 JSON5 和 JSON5E 解析,同样适用上述规则,但目前仅将 Unicode Zs 类别的常见子集识别为 JSON5 空白字符。

为此规则做了一个例外以支持“流式”模式。当 JSON“流”包含多个 JSON 值时(可选地由 JSON 空白分隔),如果启用了流式模式,解析器将允许“重置”流并继续解析后续的值。

用法概述

要在 build2 项目中开始使用 libpdjson5,请在 manifest 文件中添加以下 depends 值,并根据需要调整版本约束:

depends: libpdjson5 ^1.0.0

然后在 buildfile 中导入库:

import libs = libpdjson5%lib{pdjson5}

该库提供两个头文件:<pdjson5/pdjson5.h> 定义了解析器 API,<pdjson5/version.h> 提供了详细的库版本信息。

请注意,以下是高级概述,有关完整的 API 详细信息,请参阅 <pdjson5/pdjson5.h>

所有解析器状态都附加在 pdjson_stream 结构体上。不应直接访问其字段。要初始化,可以在输入 FILE * 流、内存缓冲区、C 字符串或自定义 IO 回调上“打开”它。通过“关闭”来释放。

struct pdjson_stream { ... };
typedef struct pdjson_stream pdjson_stream;

struct pdjson_user_io {
    int (*peek) (void *user_data);
    int (*get) (void *user_data);
    bool (*error) (void *user_data);
};
typedef struct pdjson_user_io pdjson_user_io;

void pdjson_open_buffer (pdjson_stream *json, const void *buffer, size_t size);
void pdjson_open_string (pdjson_stream *json, const char *string);
void pdjson_open_stream (pdjson_stream *json, FILE *stream);
void pdjson_open_user (pdjson_stream *json, const pdjson_user_io *user_io, void *user_data);
void pdjson_close (pdjson_stream *json);

默认情况下,解析器仅接受严格 JSON。要同时接受 JSON5 或 JSON5E,请使用 pdjson_set_language() 函数指定所需的语言:

enum pdjson_language {
    PDJSON_LANGUAGE_JSON,    // 严格 JSON。
    PDJSON_LANGUAGE_JSON5,   // 严格 JSON5。
    PDJSON_LANGUAGE_JSON5E,  // 扩展 JSON5。
};
typedef struct pdjson_allocator pdjson_allocator;

void pdjson_set_language (pdjson_stream *json, enum pdjson_language language);

打开流后,可以指定自定义分配器回调,以防分配不应来自系统提供的 malloc()

struct pdjson_allocator {
    void *(*malloc) (size_t, void *user_data);
    void *(*realloc) (void *, size_t, void *user_data);
    void (*free) (void *, size_t, void *user_data);
};

void pdjson_set_allocator (pdjson_stream *json, const pdjson_allocator *a, void *user_data);

默认情况下,解析器严格遵循 JSON 标准,任何非空白的尾部数据都会触发解析错误。如果需要,可以通过调用 pdjson_set_streaming() 启用流式模式。这会导致非空白的尾部数据被解析并报告为额外的 JSON 值。

void pdjson_set_streaming (pdjson_stream *json, bool mode);

在流式模式下,每次从流中读取一个 JSON 值。可以重置解析器以读取更多值。总的行/列号和位置会保留。

void pdjson_reset (pdjson_stream *json);

JSON 被解析为事件流(enum pdjson_type)。流处于指示的状态,在此期间可以查询和检索相关数据。

enum pdjson_type {
    PDJSON_ERROR = 1,
    PDJSON_DONE,
    PDJSON_OBJECT,
    PDJSON_OBJECT_END,
    PDJSON_ARRAY,
    PDJSON_ARRAY_END,
    PDJSON_NAME,      // 对象成员名称。
    PDJSON_STRING,
    PDJSON_NUMBER,
    PDJSON_TRUE,
    PDJSON_FALSE,
    PDJSON_NULL
};

enum pdjson_type pdjson_next (pdjson_stream *json);
enum pdjson_type pdjson_peek (pdjson_stream *json);

const char *pdjson_get_name (const pdjson_stream *json, size_t *size);
const char *pdjson_get_value (const pdjson_stream *json, size_t *size);

字符串和数字都通过 pdjson_get_value() 获取。对于数字,它将返回 JSON 输入文本中出现的原始数字文本。如果需要,您需要自行将其解析为合适的数字类型。

如果发生解析错误,则返回 PDJSON_ERROR 事件。在重置之前无法再次使用该流。发生错误时,可以获取人类可读的英文错误消息,以及行/列号和字节位置。(行/列号和字节位置始终可用。)

const char *pdjson_get_error (const pdjson_stream *json);
uint64_t pdjson_get_line(const pdjson_stream *json);
uint64_t pdjson_get_column (const pdjson_stream *json);
uint64_t pdjson_get_position (const pdjson_stream *json);
size_t pdjson_get_depth (const pdjson_stream *json);

除错误外,PDJSON_OBJECT 事件之后总会跟零个或多个 PDJSON_NAME(成员名称)事件及其关联的值事件对。也就是说,事件流将始终逻辑一致。

在流式模式下,输入的结尾通过返回第二个 PDJSON_DONE 事件来指示。另请注意,在此模式下,零个 JSON 值的输入也是有效的,由单个立即返回的 PDJSON_DONE 事件表示。

流中的 JSON 值可以由零个或多个 JSON 空白字符分隔。可以使用以下函数读取和分析值之间的字符来实现更严格或替代的分隔。

int pdjson_source_get (pdjson_stream *json);
int pdjson_source_peek (pdjson_stream *json);
bool pdjson_source_error (pdjson_stream *json);
bool pdjson_is_space (const pdjson_stream *json, int byte);

例如,以下代码片段确保值之间至少有一个换行符分隔。

enum pdjson_type e = pdjson_next (json);
if (e == PDJSON_DONE) {
    int c = '\0';
    while (pdjson_is_space (json, c = pdjson_source_peek (json))) {
        pdjson_source_get (json);
        if (c == '\n')
            break;
    }
    if (c != '\n' && c != EOF) {
        // 错误
    }
    pdjson_reset (json);
}

相似文章

JavaScript 精简

Lobsters Hottest

LispE 是 NAVER 开发的一个紧凑的 Lisp 方言,它结合了函数式和数组语言特性,并支持 PyTorch 和 llama.cpp 等 AI 库。

GeoJSON

Hacker News Top

GeoJSON 是一种开放标准格式,用于使用 JSON 编码地理数据结构,支持多种几何类型,并于 2016 年由 IETF 标准化为 RFC 7946。