arnapou / json-parser
库 - JSON流解析器和写入器,现代,易于使用,无依赖。
Requires
- php: ~8.3.0
- arnapou/stream: ^1.3
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.52
- phpstan/extension-installer: ^1.3
- phpstan/phpstan: ^1.10
- phpstan/phpstan-deprecation-rules: ^1.1
- phpstan/phpstan-phpunit: ^1.3
- phpstan/phpstan-strict-rules: ^1.5
- phpunit/php-code-coverage: ^11.0
- phpunit/phpunit: ^11.0
README
此库允许您以流的形式读取或写入json。
这是基于RFC-8259构建的,且没有依赖。
安装
composer require arnapou/json-parser
packagist 👉️ arnapou/json-parser
示例
查看更多示例,请参阅📁 example文件夹。
动态缩进文件。
$reader = new Arnapou\Json\JsonReader(
input: new Arnapou\Stream\Input\FileInput($input_filename),
visitor: new Arnapou\Json\Visitor\WhitespacesVisitor(
pretty: Arnapou\Json\Core\Pretty::Indented,
output: new Arnapou\Stream\Output\FileOutput($output_filename),
)
);
$reader->read();
遍历json的2级。
$reader = new Arnapou\Json\Iterator\JsonLeafIterator(
new Arnapou\Stream\Input\FileInput($input_filename),
maxDepth: 2
);
foreach ($reader as $node) {
// This is an Arnapou\Json\JsonNode\ValueNode with properties :
// - parents
// - depth
// - key
// - value (json decode of the leaf)
}
何时使用此库
- 需要非常小的内存占用
- 文档流(同一"body"中有多个)
- 一个输入流对应多个输出
- 或者更动态的多输入、访问者、输出架构
性能
⚠️ 重要的是要记住,与原生的json_encode和json_decode相比,此库较慢!
👉️ 主要目标是进行流操作,以获得非常小的内存占用。
使用Intel® Core™ i7-10510U CPU @ 1.80GHz × 8
进行的指标
读取 | Json大小 | 时间 | 内存 | 字节率 | JIT | JIT提升 |
---|---|---|---|---|---|---|
JsonReader | 100 MB | 12.66秒 | 4 MB | 7.9 MB/s | ❌ | |
JsonReader | 100 MB | 10.64秒 | 4 MB | 9.4 MB/s | ✅ | +19% |
JsonLeafIterator | 100 MB | 15.15秒 | 4 MB | 6.6 MB/s | ❌ | |
JsonLeafIterator | 100 MB | 10.42秒 | 4 MB | 9.6 MB/s | ✅ | +45% |
json_decode() | 100 MB | 0.75秒 | 310 MB | 133.7 MB/s | ❌ | |
json_decode() | 100 MB | 0.75秒 | 310 MB | 133.9 MB/s | ✅ | +0% |
写入 | Json大小 | 时间 | 内存 | 字节率 | JIT | JIT提升 |
---|---|---|---|---|---|---|
JsonWriter | 100 MB | 1.23秒 | 4 MB | 81 MB/s | ❌ | |
JsonWriter | 100 MB | 0.80秒 | 4 MB | 124 MB/s | ✅ | +50% |
json_encode() | 100 MB | 0.47秒 | 420 MB | 210 MB/s | ❌ | |
json_encode() | 100 MB | 0.45秒 | 420 MB | 220 MB/s | ✅ | +5% |
测试JIT的示例
php -d opcache.enable_cli=1 -d opcache.jit_buffer_size=256m example/bandwidth_reader.php
php -d opcache.enable_cli=1 -d opcache.jit_buffer_size=256m example/bandwidth_writer.php
这通常是CPU和内存之间的权衡。
I/O考虑:相对于互联网Web服务器上的网络速度,JsonReader的字节率可能并不那么糟糕(我在一家公司工作,我们Web SaaS基础设施管理的互联网网关平均为10 MB/s)。
面向对象编程(OOP)
此库使用一些模式:访问者、装饰器、适配器、迭代器。
代码设计高度解耦且简单。但您可能需要完全理解这些模式才能充分利用所有功能。
主要接口
输入(来自arnapou/stream
)
您“读取”的流。
namespace Arnapou\Stream\Input;
interface Input
{
public function open(): void;
public function read(): string;
public function close(): void;
}
输出(来自arnapou/stream
)
您“写入”的流。
namespace Arnapou\Stream\Output;
interface Output
{
public function write(string $data): void;
}
访问者
要注入到读取器中以监视流的对象。
namespace Arnapou\Json\Core;
use Arnapou\Json\JsonNode\Key\{ArrayKeyNode, ObjectKeyNode};
use Arnapou\Json\JsonNode\Nested\{ArrayNode, ObjectNode};
use Arnapou\Json\JsonNode\Scalar\{LiteralNode, NumberNode, StringNode};
use Arnapou\Json\JsonNode\Structure\{StructureCharacterNode, WhitespaceNode};
interface Visitor
{
public function beginNode(ObjectNode|ArrayNode $node): void;
public function endNode(ObjectNode|ArrayNode $node): void;
public function enterStructure(WhitespaceNode|StructureCharacterNode $node): void;
public function enterKey(ObjectKeyNode|ArrayKeyNode $node): void;
public function enterValue(NumberNode|StringNode|LiteralNode $node): void;
}
不要忘记从json解析的角度来看。
主要具体类
JsonReader
解析Input
流并调用访问者
方法。
$input = new Arnapou\Stream\Input\StringInput('{"id": 42, "text": "Hello World"}');
$visitor = new FullDecodeVisitor();
$reader = new JsonReader($input, $visitor);
$reader->read();
print_r($visitor->getDecoded());
JsonWriter
将数据写入到Output
(显然,对于流,请使用生成器)。
$output = new EchoOutput();
$writer = new JsonWriter($output);
$writer->writeValue(
[
'id' => 42,
'text' => 'Hello World',
]
);
JsonStreamUtils
用于非常简单用例的简单静态函数。
Arnapou\Json\JsonStreamUtils::pretty(
new Arnapou\Stream\Input\FileInput($input_filename),
new Arnapou\Stream\Output\FileOutput($output_filename)
);
迭代器
它们使用php Fibers将访问者适配到迭代器模式。
这以易于使用为代价带来了一点性能损失(没有JIT)。
JsonLeafIterator
迭代“叶子”节点的小工具。
要遍历叶子节点,您必须提供一个 "最大深度"。更深的节点被解码为数组值。
这使用了一个抽象的LeafVisitor类,该类强制实现以下方法
abstract class LeafVisitor implements Visitor
{
public function enterLeaf(ValueNode $node): void;
}
JsonDecodeIterator
这是一个简单的foreach
遍历Input
流中过滤后的节点的实用工具。
要遍历叶子节点,您必须提供一个ShouldDecodeCallback。这会选择无论深度如何都应该解码的节点。
这使用了一个抽象的DecodeVisitor类,该类强制实现以下方法
abstract class DecodeVisitor implements Visitor
{
protected function shouldDecode(ObjectNode|ArrayNode|LiteralNode|NumberNode|StringNode $node): bool;
protected function isDecoded(ValueNode $node): void;
}
节点
继承树下方,🔶是接口,🟦是具体实现
- 🔶 JsonNode
- 🟦 ValueNode
- 🔶 KeyNode
- 🔶 NestedNode
- 🟦 ArrayNode
- 🟦 ObjectNode
- 🔶 ScalarNode
- 🟦 NumberNode
- 🟦 StringNode
- 🟦 LiteralNode
- 🔶 StructureNode
JsonNode的所有实现都在Visitor内部使用,除了ValueNode,它由ValueNodeIterator使用。
每个"节点"都携带其上下文
$node->parents
:父键数组$node->depth
:节点的深度级别$node->key
:当前键$node->fullPath()
:返回完整路径的字符串表示(例如:items.3.name
)
限制
您的想象力。
您可以在混合输入、输出、访问者时做一些愚蠢的事情。
示例
- 一个
Input
- 将流发送到
JsonReader
- 将原始流并行写入到
Output 1
- 将流发送到
JsonReader
有一个包含以下内容的MultipleVisitor
- 一个
BandwidthVisitor
来收集关于流的度量 - 一个
WhitespacesVisitor
将格式化输出到Output 2
- 一个用于提取特定节点的
LeafVisitor
实现p>
- 一个
如果你想知道我是如何获取关于我的解析器速度的一些指标,请查看 Bandwidth
接口、RepeatInput
等... 🙂
PHP版本
日期 | 引用 | 8.3 | 8.2 |
---|---|---|---|
25/11/2023 | 2.x,主分支 | × | |
07/03/2023 | 1.x | × |