Skip to content

feat: Improve perf and add conveniences#8

Merged
tymondesigns merged 12 commits into
mainfrom
feat/improve-perf-etc
Jun 25, 2026
Merged

feat: Improve perf and add conveniences#8
tymondesigns merged 12 commits into
mainfrom
feat/improve-perf-etc

Conversation

@tymondesigns

Copy link
Copy Markdown
Contributor

This pull request introduces several enhancements and refactorings to the JSON repair library, focusing on improved duplicate key handling, advanced API features, and internal code structure. The most notable changes include the addition of a configurable duplicate key policy, new advanced APIs for detailed repair reports and streaming, and significant internal refactoring for better state management and output tracking.

Feature Enhancements:

  • Added a duplicateKeyPolicy option to the JSON repairer, allowing users to specify whether to keep the first or last value for duplicate object keys, or leave them as-is.
  • Introduced advanced APIs:
    • repairWithDetails() returns a structured report including the repaired JSON, validity status, and a list of fixes.
    • repairAll() supports repairing multiple top-level JSON values (e.g., NDJSON).
    • StreamingJsonRepairer enables streaming repair with best-effort output on partial data.
    • decode() now supports direct decoding of top-level scalar values.

Internal Refactoring and New Utilities:

  • Introduced the OutputTracking trait to efficiently track the last non-whitespace character and manage output string state, improving handling of edge cases like missing values.
  • Refactored state management to use a ParserState class/enum instead of raw constants, making the parser's state transitions clearer and less error-prone. [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14] [15] [16]
  • Added an extractAllTopLevelJson() utility to extract all top-level objects/arrays from input, supporting more robust handling of multi-document or NDJSON-style input.

Logging and Diagnostics:

  • Enhanced the RepairLogging trait to support collecting and returning a list of all fixes applied during repair, enabling detailed diagnostics and structured reporting.

Input Handling Improvements:

  • Improved markdown extraction logic to return the input unchanged if no code blocks are present, preventing unnecessary processing.

These changes collectively improve the flexibility, robustness, and observability of the JSON repair process, making the library more suitable for advanced and streaming scenarios.

@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown

Benchmark results

No performance regression detected (within 25% tolerance).

Aggregate report vs. base
+-------------------+---------------------------------+------------------------+------+-----+----------------+---------------------+-----------------+
| benchmark         | subject                         | set                    | revs | its | mem_peak       | mode                | rstdev          |
+-------------------+---------------------------------+------------------------+------+-----+----------------+---------------------+-----------------+
| JsonRepairerBench | benchRepairSimpleJson           | single_quotes          | 100  | 10  | 1.947mb +2.14% | 7.380μs +1.34%      | ±2.68% -7.65%   |
| JsonRepairerBench | benchRepairSimpleJson           | unquoted_keys          | 100  | 10  | 1.947mb +2.14% | 12.460μs +9.10%     | ±1.91% -44.19%  |
| JsonRepairerBench | benchRepairSimpleJson           | trailing_comma         | 100  | 10  | 1.947mb +2.14% | 8.587μs +1.66%      | ±1.64% -30.42%  |
| JsonRepairerBench | benchRepairSimpleJson           | missing_comma          | 100  | 10  | 1.947mb +2.14% | 13.076μs +0.49%     | ±2.78% +17.67%  |
| JsonRepairerBench | benchRepairSimpleJson           | mixed_issues           | 100  | 10  | 1.947mb +2.14% | 19.261μs +10.36%    | ±1.83% -56.41%  |
| JsonRepairerBench | benchRepairComplexJson          | nested_object          | 100  | 10  | 1.947mb +2.15% | 35.709μs -18.77%    | ±0.98% -35.87%  |
| JsonRepairerBench | benchRepairComplexJson          | complex_nested         | 100  | 10  | 1.948mb +2.16% | 94.473μs -17.28%    | ±0.96% -55.63%  |
| JsonRepairerBench | benchRepairComplexJson          | markdown_wrapped       | 100  | 10  | 1.947mb +2.14% | 10.906μs -0.11%     | ±1.83% -32.72%  |
| JsonRepairerBench | benchRepairComplexJson          | multiple_issues        | 100  | 10  | 1.947mb +2.14% | 39.409μs +9.16%     | ±0.91% -85.11%  |
| JsonRepairerBench | benchRepairValidJson            | simple_object          | 100  | 10  | 1.947mb +2.14% | 0.660μs +4.29%      | ±1.26% -91.42%  |
| JsonRepairerBench | benchRepairValidJson            | simple_array           | 100  | 10  | 1.947mb +2.14% | 0.545μs +6.77%      | ±2.63% +101.57% |
| JsonRepairerBench | benchRepairValidJson            | nested_valid           | 100  | 10  | 1.947mb +2.14% | 0.720μs +5.65%      | ±0.83% -48.95%  |
| JsonRepairerBench | benchRepairLargeJson            | large_array_broken     | 10   | 5   | 2.047mb +4.23% | 13,922.067μs -2.53% | ±0.95% +36.16%  |
| JsonRepairerBench | benchRepairAndDecodeSimpleJson  | single_quotes          | 100  | 10  | 1.947mb +2.14% | 7.714μs -1.64%      | ±1.89% +24.70%  |
| JsonRepairerBench | benchRepairAndDecodeSimpleJson  | unquoted_keys          | 100  | 10  | 1.947mb +2.14% | 12.985μs +6.96%     | ±2.24% -16.74%  |
| JsonRepairerBench | benchRepairAndDecodeSimpleJson  | trailing_comma         | 100  | 10  | 1.947mb +2.14% | 9.136μs +3.33%      | ±1.45% -37.62%  |
| JsonRepairerBench | benchRepairAndDecodeSimpleJson  | missing_comma          | 100  | 10  | 1.947mb +2.14% | 14.122μs +3.84%     | ±2.07% -48.45%  |
| JsonRepairerBench | benchRepairAndDecodeSimpleJson  | mixed_issues           | 100  | 10  | 1.947mb +2.14% | 20.109μs +9.92%     | ±1.40% -21.80%  |
| JsonRepairerBench | benchRepairAndDecodeComplexJson | nested_object          | 100  | 10  | 1.947mb +2.15% | 37.691μs -17.52%    | ±0.99% -43.83%  |
| JsonRepairerBench | benchRepairAndDecodeComplexJson | complex_nested         | 100  | 10  | 1.948mb +2.16% | 99.557μs -15.25%    | ±1.07% +29.58%  |
| JsonRepairerBench | benchRepairAndDecodeComplexJson | markdown_wrapped       | 100  | 10  | 1.947mb +2.14% | 11.726μs +1.92%     | ±1.89% -56.48%  |
| JsonRepairerBench | benchRepairAndDecodeComplexJson | multiple_issues        | 100  | 10  | 1.947mb +2.14% | 40.119μs +6.47%     | ±1.42% +0.46%   |
| JsonRepairerBench | benchRepairWithClass            | single_quotes          | 100  | 10  | 1.947mb +2.14% | 7.161μs -2.33%      | ±1.92% -5.17%   |
| JsonRepairerBench | benchRepairWithClass            | unquoted_keys          | 100  | 10  | 1.947mb +2.14% | 12.027μs +5.07%     | ±1.57% +11.60%  |
| JsonRepairerBench | benchRepairWithClass            | trailing_comma         | 100  | 10  | 1.947mb +2.14% | 8.819μs +5.49%      | ±2.30% -19.47%  |
| JsonRepairerBench | benchRepairWithClass            | missing_comma          | 100  | 10  | 1.947mb +2.14% | 13.202μs +3.25%     | ±2.37% +46.37%  |
| JsonRepairerBench | benchRepairWithClass            | mixed_issues           | 100  | 10  | 1.947mb +2.14% | 19.359μs +9.67%     | ±2.41% -15.50%  |
| JsonRepairerBench | benchRepairStreamingJson        | cut_off_mid_string     | 100  | 10  | 1.947mb +2.14% | 12.663μs -25.56%    | ±2.52% +34.84%  |
| JsonRepairerBench | benchRepairStreamingJson        | cut_off_mid_number     | 100  | 10  | 1.947mb +2.14% | 6.962μs +8.16%      | ±2.49% -47.32%  |
| JsonRepairerBench | benchRepairStreamingJson        | cut_off_after_colon    | 100  | 10  | 1.947mb +2.14% | 11.215μs +8.22%     | ±1.93% -41.64%  |
| JsonRepairerBench | benchRepairStreamingJson        | cut_off_mid_key        | 100  | 10  | 1.947mb +2.14% | 9.411μs -5.11%      | ±1.75% +3.21%   |
| JsonRepairerBench | benchRepairStreamingJson        | cut_off_mid_object     | 100  | 10  | 1.947mb +2.14% | 16.090μs +5.26%     | ±2.13% -5.68%   |
| JsonRepairerBench | benchRepairStreamingJson        | cut_off_mid_array      | 100  | 10  | 1.947mb +2.14% | 12.236μs +17.86%    | ±2.37% -35.89%  |
| JsonRepairerBench | benchRepairStreamingJson        | cut_off_after_comma    | 100  | 10  | 1.947mb +2.14% | 14.388μs +13.41%    | ±1.65% -4.79%   |
| JsonRepairerBench | benchRepairStreamingJson        | cut_off_mid_escape     | 100  | 10  | 1.947mb +2.14% | 6.938μs -15.13%     | ±1.86% -15.48%  |
| JsonRepairerBench | benchRepairStreamingJson        | cut_off_mid_scientific | 100  | 10  | 1.947mb +2.14% | 7.633μs +4.22%      | ±2.27% -65.52%  |
| JsonRepairerBench | benchRepairStreamingJson        | multiple_incomplete    | 100  | 10  | 1.947mb +2.14% | 18.032μs -18.30%    | ±2.11% +11.20%  |
| JsonRepairerBench | benchNativeJsonDecodeBaseline   | simple_object          | 100  | 10  | 1.947mb 0.00%  | 0.459μs +1.87%      | ±2.03% -84.87%  |
| JsonRepairerBench | benchNativeJsonDecodeBaseline   | simple_array           | 100  | 10  | 1.947mb 0.00%  | 0.270μs -0.22%      | ±1.82% -37.27%  |
| JsonRepairerBench | benchNativeJsonDecodeBaseline   | nested_valid           | 100  | 10  | 1.947mb 0.00%  | 0.560μs +0.61%      | ±0.72% -94.07%  |
+-------------------+---------------------------------+------------------------+------+-----+----------------+---------------------+-----------------+


Compared against base 07e92bb.

@tymondesigns
tymondesigns marked this pull request as ready for review June 25, 2026 08:19
@tymondesigns
tymondesigns requested a review from Copilot June 25, 2026 08:19

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR expands the JSON repair library with advanced APIs (structured repair results, multi-document repair, streaming repair), adds configurable duplicate-key handling, and refactors the internal parser for better state/output management and improved numeric/keyword normalization.

Changes:

  • Added advanced/public APIs: repairWithDetails(), repairAll(), StreamingJsonRepairer, scalar-capable decode(), and RepairResult.
  • Introduced duplicate-key handling via DuplicateKeyPolicy and supporting parser changes.
  • Refactored internals with ParserState and OutputTracking, plus input handling improvements and benchmark automation.

Reviewed changes

Copilot reviewed 24 out of 25 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
tests/Unit/ValuesAndStructuresTest.php New focused tests for scalar values, escaping, unicode, and structures.
tests/Unit/StreamingJsonRepairerTest.php Adds incremental streaming repair coverage.
tests/Unit/OptionsTest.php Adds option-focused tests for omit behaviors.
tests/Unit/LoggingTest.php Adds logging/assertions for repair diagnostics and logger integration.
tests/Unit/JsonRepairsTest.php Splits core repair behavior tests from the former monolithic suite.
tests/Unit/JsonRepairerTest.php Removes the old monolithic test file (suite split across new files).
tests/Unit/EdgeCasesTest.php Adds coverage for markdown extraction, comments, and complex edge cases.
tests/Unit/ApiUsageTest.php Covers new APIs (repairWithDetails, repairAll, duplicate-key policy, scalar decode).
tests/Datasets/JsonRepair.php Extends datasets for markdown/text extraction and invalid/non-finite number cases.
src/StreamingJsonRepairer.php Implements streaming “feed/current/reset” repair wrapper with logger passthrough.
src/RepairResult.php Adds structured result object for repairWithDetails().
src/ParserState.php Centralizes parser state constants for traits/state machine.
src/JsonRepairer.php Core implementation changes: new APIs, duplicate key policy hooks, output finalization, output tracking.
src/functions.php Extends helper functions with duplicateKeyPolicy and updates decode return type to mixed.
src/DuplicateKeyPolicy.php Adds enum for keep-first/keep-last duplicate key strategies.
src/Concerns/StringHeuristics.php Updates state references to new ParserState constants.
src/Concerns/StateMachine.php Refactors parser state machine, keyword/number handling, and duplicate-key hooks.
src/Concerns/RepairLogging.php Adds fix-collection support for structured reporting.
src/Concerns/OutputTracking.php Adds efficient “last non-whitespace char” tracking for output manipulation.
src/Concerns/InputSanitization.php Improves markdown extraction fast-path and adds top-level segment extraction for repairAll().
phpbench.json Tunes phpbench config and storage path.
docs/json-repair/configuration.mdx Documents duplicate key policy and advanced APIs.
composer.json Adds benchmark scripts for baseline/CI comparison.
.gitignore Ignores phpbench artifacts directory.
.github/workflows/benchmark.yml Adds PR benchmark regression workflow and reporting.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/Concerns/StateMachine.php Outdated
Comment thread src/Concerns/StateMachine.php
Comment thread src/JsonRepairer.php
Comment thread src/JsonRepairer.php
Comment thread src/Concerns/InputSanitization.php Outdated
Comment thread docs/json-repair/configuration.mdx
Comment thread src/JsonRepairer.php
Comment thread tests/Unit/ApiUsageTest.php

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 25 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

src/Concerns/StateMachine.php:54

  • The PHPDoc block starting at /** Handle the starting state of parsing... */ is not attached to any symbol (it’s followed by another docblock and then tryOpenObjectOrArray()), so it becomes an orphaned PHPDoc comment. Consider converting it to a regular block comment (/* ... */) or moving it directly above the method it documents to avoid tooling confusion.
    /**
     * Handle the starting state of parsing.
     *
     * Processes the first character of the JSON, expecting either an object
     * opening brace or an array opening bracket.

Comment thread src/Concerns/StateMachine.php Outdated
Comment thread src/JsonRepairer.php Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 25 changed files in this pull request and generated 2 comments.

Comment thread src/Concerns/StateMachine.php Outdated
Comment thread src/Concerns/StateMachine.php Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 25 changed files in this pull request and generated 2 comments.

Comment thread src/Concerns/StateMachine.php
Comment thread src/JsonRepairer.php Outdated
@tymondesigns
tymondesigns merged commit 9693a8c into main Jun 25, 2026
18 checks passed
@tymondesigns
tymondesigns deleted the feat/improve-perf-etc branch June 25, 2026 23:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants