arnapou/json-parser

库 - JSON流解析器和写入器,现代,易于使用,无依赖。

v2.3 2024-04-14 15:42 UTC

This package is auto-updated.

Last update: 2024-09-08 14:51:03 UTC


README

pipeline coverage

此库允许您以流的形式读取写入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大小时间内存字节率JITJIT提升
JsonReader100 MB12.66秒4 MB7.9 MB/s
JsonReader100 MB10.64秒4 MB9.4 MB/s+19%
JsonLeafIterator100 MB15.15秒4 MB6.6 MB/s
JsonLeafIterator100 MB10.42秒4 MB9.6 MB/s+45%
json_decode()100 MB0.75秒310 MB133.7 MB/s
json_decode()100 MB0.75秒310 MB133.9 MB/s+0%
写入Json大小时间内存字节率JITJIT提升
JsonWriter100 MB1.23秒4 MB81 MB/s
JsonWriter100 MB0.80秒4 MB124 MB/s+50%
json_encode()100 MB0.47秒420 MB210 MB/s
json_encode()100 MB0.45秒420 MB220 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的所有实现都在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.38.2
25/11/20232.x,主分支×
07/03/20231.x×