{"id":13404845,"url":"https://github.com/halaxa/json-machine","last_synced_at":"2025-05-13T17:09:22.891Z","repository":{"id":33274924,"uuid":"156270872","full_name":"halaxa/json-machine","owner":"halaxa","description":"Efficient, easy-to-use, and fast PHP JSON stream parser","archived":false,"fork":false,"pushed_at":"2025-04-25T09:07:09.000Z","size":1594,"stargazers_count":1187,"open_issues_count":10,"forks_count":66,"subscribers_count":17,"default_branch":"master","last_synced_at":"2025-04-25T15:48:22.424Z","etag":null,"topics":["json-iterator","json-parser","json-stream","parsing","php","stream-processing"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/halaxa.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"ko_fi":"halaxa"}},"created_at":"2018-11-05T19:25:46.000Z","updated_at":"2025-04-25T09:07:12.000Z","dependencies_parsed_at":"2023-11-18T19:23:36.495Z","dependency_job_id":"aa924e18-190c-4191-8c60-a9ef92df0678","html_url":"https://github.com/halaxa/json-machine","commit_stats":{"total_commits":478,"total_committers":13,"mean_commits":36.76923076923077,"dds":0.09205020920502094,"last_synced_commit":"fa261d25231c8bfe1ea0a29da9033f575d0860a8"},"previous_names":[],"tags_count":28,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/halaxa%2Fjson-machine","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/halaxa%2Fjson-machine/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/halaxa%2Fjson-machine/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/halaxa%2Fjson-machine/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/halaxa","download_url":"https://codeload.github.com/halaxa/json-machine/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253990468,"owners_count":21995774,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["json-iterator","json-parser","json-stream","parsing","php","stream-processing"],"created_at":"2024-07-30T19:01:52.211Z","updated_at":"2025-05-13T17:09:17.879Z","avatar_url":"https://github.com/halaxa.png","language":"PHP","readme":"# \u003cimg align=\"right\" src=\"img/github.png\" alt=\"JSON Machine\" /\u003e\n\nVery easy to use and memory efficient drop-in replacement for inefficient iteration of big JSON files or streams\nfor PHP \u003e=7.2. See [TL;DR](#tl-dr). No dependencies in production except optional `ext-json`. README in sync with the code\n\n[![Build Status](https://github.com/halaxa/json-machine/actions/workflows/makefile.yml/badge.svg)](https://github.com/halaxa/json-machine/actions)\n[![codecov](https://img.shields.io/codecov/c/gh/halaxa/json-machine?label=phpunit%20%40covers)](https://codecov.io/gh/halaxa/json-machine)\n[![Latest Stable Version](https://img.shields.io/github/v/release/halaxa/json-machine?color=blueviolet\u0026include_prereleases\u0026logoColor=white)](https://packagist.org/packages/halaxa/json-machine)\n[![Monthly Downloads](https://img.shields.io/packagist/dt/halaxa/json-machine?color=%23f28d1a)](https://packagist.org/packages/halaxa/json-machine)\n\n---\nNEW in version `1.2.0` - [Recursive iteration](#recursive)\n\n---\n\n* [TL;DR](#tl-dr)\n* [Introduction](#introduction)\n* [Parsing JSON documents](#parsing-json-documents)\n  + [Parsing a document](#simple-document)\n  + [Parsing a subtree](#parsing-a-subtree)\n  + [Parsing nested values in arrays](#parsing-nested-values)\n  + [Parsing a single scalar value](#getting-scalar-values)\n  + [Parsing multiple subtrees](#parsing-multiple-subtrees)\n  + [Recursive iteration](#recursive)\n  + [What is JSON Pointer anyway?](#json-pointer)\n* [Options](#options)\n* [Parsing streaming responses from a JSON API](#parsing-json-stream-api-responses)\n  + [GuzzleHttp](#guzzlehttp)\n  + [Symfony HttpClient](#symfony-httpclient)\n* [Tracking the progress](#tracking-parsing-progress)\n* [Decoders](#decoders)\n  + [Available decoders](#available-decoders)\n* [Error handling](#error-handling)\n  + [Catching malformed items](#malformed-items)\n* [Parser efficiency](#on-parser-efficiency)\n  + [Streams / files](#streams-files)\n  + [In-memory JSON strings](#in-memory-json-strings)\n* [Troubleshooting](#troubleshooting)\n  + [\"I'm still getting Allowed memory size ... exhausted\"](#step1)\n  + [\"That didn't help\"](#step2)\n  + [\"I am still out of luck\"](#step3)\n* [Installation](#installation)\n* [Development](#development)\n  + [Non containerized](#non-containerized)\n  + [Containerized](#containerized)\n* [Support](#support)\n* [License](#license)\n\n---\n\n\u003ca name=\"tl-dr\"\u003e\u003c/a\u003e\n## TL;DR\n```diff\n\u003c?php\n\nuse \\JsonMachine\\Items;\n\n// this often causes Allowed Memory Size Exhausted,\n// because it loads all the items in the JSON into memory\n- $users = json_decode(file_get_contents('500MB-users.json'));\n\n// this has very small memory footprint no matter the file size\n// because it loads items into memory one by one\n+ $users = Items::fromFile('500MB-users.json');\n\nforeach ($users as $id =\u003e $user) {\n    // just process $user as usual\n    var_dump($user-\u003ename);\n}\n```\n\nRandom access like `$users[42]` is not yet possible.\nUse above-mentioned `foreach` and find the item or use [JSON Pointer](#parsing-a-subtree).\n\nCount the items via [`iterator_count($users)`](https://www.php.net/manual/en/function.iterator-count.php).\nRemember it will still have to internally iterate the whole thing to get the count and thus will take about the same time\nas iterating it and counting by hand.\n\nRequires `ext-json` if used out of the box but doesn't if a custom decoder is used. See [Decoders](#decoders).\n\nFollow [CHANGELOG](CHANGELOG.md).\n\n\u003ca name=\"introduction\"\u003e\u003c/a\u003e\n## Introduction\nJSON Machine is an efficient, easy-to-use and fast JSON stream/pull/incremental/lazy (whatever you name it) parser\nbased on generators developed for unpredictably long JSON streams or documents. Main features are:\n\n- Constant memory footprint for unpredictably large JSON documents.\n- Ease of use. Just iterate JSON of any size with `foreach`. No events and callbacks.\n- Efficient iteration on any subtree of the document, specified by [JSON Pointer](#json-pointer)\n- Speed. Performance critical code contains no unnecessary function calls, no regular expressions\nand uses native `json_decode` to decode JSON document items by default. See [Decoders](#decoders).\n- Parses not only streams but any iterable that produces JSON chunks.\n- Thoroughly tested. More than 200 tests and 1000 assertions.\n\n\u003ca name=\"parsing-json-documents\"\u003e\u003c/a\u003e\n## Parsing JSON documents\n\n\u003ca name=\"simple-document\"\u003e\u003c/a\u003e\n### Parsing a document\nLet's say that `fruits.json` contains this huge JSON document:\n```json\n// fruits.json\n{\n    \"apple\": {\n        \"color\": \"red\"\n    },\n    \"pear\": {\n        \"color\": \"yellow\"\n    }\n}\n``` \nIt can be parsed this way:\n```php\n\u003c?php\n\nuse \\JsonMachine\\Items;\n\n$fruits = Items::fromFile('fruits.json');\n\nforeach ($fruits as $name =\u003e $data) {\n    // 1st iteration: $name === \"apple\" and $data-\u003ecolor === \"red\"\n    // 2nd iteration: $name === \"pear\" and $data-\u003ecolor === \"yellow\"\n}\n```\n\nParsing a json array instead of a json object follows the same logic.\nThe key in a foreach will be a numeric index of an item.\n\nIf you prefer JSON Machine to return arrays instead of objects, use `new ExtJsonDecoder(true)` as a decoder.\n```php\n\u003c?php\n\nuse JsonMachine\\JsonDecoder\\ExtJsonDecoder;\nuse JsonMachine\\Items;\n\n$objects = Items::fromFile('path/to.json', ['decoder' =\u003e new ExtJsonDecoder(true)]);\n```\n\n\n\u003ca name=\"parsing-a-subtree\"\u003e\u003c/a\u003e\n### Parsing a subtree\nIf you want to iterate only `results` subtree in this `fruits.json`:\n```json\n// fruits.json\n{\n    \"results\": {\n        \"apple\": {\n            \"color\": \"red\"\n        },\n        \"pear\": {\n            \"color\": \"yellow\"\n        }\n    }\n}\n```\nuse JSON Pointer `/results` as `pointer` option:\n```php\n\u003c?php\n\nuse \\JsonMachine\\Items;\n\n$fruits = Items::fromFile('fruits.json', ['pointer' =\u003e '/results']);\nforeach ($fruits as $name =\u003e $data) {\n    // The same as above, which means:\n    // 1st iteration: $name === \"apple\" and $data-\u003ecolor === \"red\"\n    // 2nd iteration: $name === \"pear\" and $data-\u003ecolor === \"yellow\"\n}\n```\n\n\u003e Note:\n\u003e\n\u003e Value of `results` is not loaded into memory at once, but only one item in\n\u003e `results` at a time. It is always one item in memory at a time at the level/subtree\n\u003e you are currently iterating. Thus, the memory consumption is constant.\n\n\u003ca name=\"parsing-nested-values\"\u003e\u003c/a\u003e\n### Parsing nested values in arrays\nThe JSON Pointer spec also allows to use a hyphen (`-`) instead of a specific array index. JSON Machine interprets\nit as a wildcard which matches any **array index** (not any object key). This enables you to iterate nested values in\narrays without loading the whole item.\n\nExample:\n```json\n// fruitsArray.json\n{\n    \"results\": [\n        {\n            \"name\": \"apple\",\n            \"color\": \"red\"\n        },\n        {\n            \"name\": \"pear\",\n            \"color\": \"yellow\"\n        }\n    ]\n}\n```\n\nTo iterate over all colors of the fruits, use the JSON Pointer `\"/results/-/color\"`.\n\n```php\n\u003c?php\n\nuse \\JsonMachine\\Items;\n\n$fruits = Items::fromFile('fruitsArray.json', ['pointer' =\u003e '/results/-/color']);\n\nforeach ($fruits as $key =\u003e $value) {\n    // 1st iteration:\n    $key == 'color';\n    $value == 'red';\n    $fruits-\u003egetMatchedJsonPointer() == '/results/-/color';\n    $fruits-\u003egetCurrentJsonPointer() == '/results/0/color';\n\n    // 2nd iteration:\n    $key == 'color';\n    $value == 'yellow';\n    $fruits-\u003egetMatchedJsonPointer() == '/results/-/color';\n    $fruits-\u003egetCurrentJsonPointer() == '/results/1/color';\n}\n```\n\n\u003ca name=\"getting-scalar-values\"\u003e\u003c/a\u003e\n### Parsing a single scalar value\nYou can parse a single scalar value anywhere in the document the same way as a collection. Consider this example:\n```json\n// fruits.json\n{\n    \"lastModified\": \"2012-12-12\",\n    \"apple\": {\n        \"color\": \"red\"\n    },\n    \"pear\": {\n        \"color\": \"yellow\"\n    },\n    // ... gigabytes follow ...\n}\n``` \nGet the scalar value of `lastModified` key like this:\n```php\n\u003c?php\n\nuse \\JsonMachine\\Items;\n\n$fruits = Items::fromFile('fruits.json', ['pointer' =\u003e '/lastModified']);\nforeach ($fruits as $key =\u003e $value) {\n    // 1st and final iteration:\n    // $key === 'lastModified'\n    // $value === '2012-12-12'\n}\n```\nWhen parser finds the value and yields it to you, it stops parsing. So when a single scalar value is in the beginning\nof a gigabytes-sized file or stream, it just gets the value from the beginning in no time and with almost no memory\nconsumed.\n\nThe obvious shortcut is:\n```php\n\u003c?php\n\nuse \\JsonMachine\\Items;\n\n$fruits = Items::fromFile('fruits.json', ['pointer' =\u003e '/lastModified']);\n$lastModified = iterator_to_array($fruits)['lastModified'];\n```\nSingle scalar value access supports array indices in JSON Pointer as well.\n\n\u003ca name=\"parsing-multiple-subtrees\"\u003e\u003c/a\u003e\n### Parsing multiple subtrees\n\nIt is also possible to parse multiple subtrees using multiple JSON Pointers. Consider this example:\n```json\n// fruits.json\n{\n    \"lastModified\": \"2012-12-12\",\n    \"berries\": [\n        {\n          \"name\": \"strawberry\", // not a berry, but whatever ...\n          \"color\": \"red\"\n        },\n        {\n          \"name\": \"raspberry\", // the same ...\n          \"color\": \"red\"\n        }\n    ],\n    \"citruses\": [\n      {\n          \"name\": \"orange\",\n          \"color\": \"orange\"\n      },\n      {\n          \"name\": \"lime\",\n          \"color\": \"green\"\n      }\n    ]\n}\n``` \nTo iterate over all berries and citrus fruits, use the JSON pointers `[\"/berries\", \"/citrus\"]`. The order of pointers\ndoes not matter. The items will be iterated in the order of appearance in the document.\n```php\n\u003c?php\n\nuse \\JsonMachine\\Items;\n\n$fruits = Items::fromFile('fruits.json', [\n    'pointer' =\u003e ['/berries', '/citruses']\n]);\n\nforeach ($fruits as $key =\u003e $value) {\n    // 1st iteration:\n    $value == [\"name\" =\u003e \"strawberry\", \"color\" =\u003e \"red\"];\n    $fruits-\u003egetCurrentJsonPointer() == '/berries';\n\n    // 2nd iteration:\n    $value == [\"name\" =\u003e \"raspberry\", \"color\" =\u003e \"red\"];\n    $fruits-\u003egetCurrentJsonPointer() == '/berries';\n\n    // 3rd iteration:\n    $value == [\"name\" =\u003e \"orange\", \"color\" =\u003e \"orange\"];\n    $fruits-\u003egetCurrentJsonPointer() == '/citruses';\n\n    // 4th iteration:\n    $value == [\"name\" =\u003e \"lime\", \"color\" =\u003e \"green\"];\n    $fruits-\u003egetCurrentJsonPointer() == '/citruses';\n}\n```\n\n\u003ca name=\"recursive\"\u003e\u003c/a\u003e\n### Recursive iteration\nUse `RecursiveItems` instead of `Items` when the JSON structure is difficult or even impossible to handle with `Items`\nand JSON pointers or the individual items you iterate are too big to handle.\nOn the other hand it's notably slower than `Items`, so bear that in mind.\n\nWhen `RecursiveItems` encounters a list or dict in the JSON, it returns a new instance of itself\nwhich can then be iterated over and the cycle repeats.\nThus, it never returns a PHP array or object, but only either scalar values or `RecursiveItems`.\nNo JSON dict nor list will ever be fully loaded into memory at once.\n\nLet's see an example with many, many users with many, many friends:\n```json\n// users.json\n[\n  {\n    \"username\": \"user\",\n    \"e-mail\": \"user@example.com\",\n    \"friends\": [\n      {\n        \"username\": \"friend1\",\n        \"e-mail\": \"friend1@example.com\"\n      },\n      {\n        \"username\": \"friend2\",\n        \"e-mail\": \"friend2@example.com\"\n      }\n    ]\n  }\n]\n```\n\n```php\n\u003c?php\n\nuse JsonMachine\\RecursiveItems\n\n$users = RecursiveItems::fromFile('users.json');\nforeach ($users as $user) {\n    /** @var $user RecursiveItems */\n    foreach ($user as $field =\u003e $value) {\n        if ($field === 'friends') {\n            /** @var $value RecursiveItems */\n            foreach ($value as $friend) {\n                /** @var $friend RecursiveItems */\n                foreach ($friend as $friendField =\u003e $friendValue) {\n                    $friendField == 'username';\n                    $friendValue == 'friend1';\n                }\n            }\n        }\n    }\n}\n```\n\n\u003e If you break an iteration of such lazy deeper-level (i.e. you skip some `\"friends\"` via `break`)\n\u003e and advance to a next value (i.e. next `user`), you will not be able to iterate it later.\n\u003e JSON Machine must iterate it in the background to be able to read next value.\n\u003e Such an attempt will result in closed generator exception.\n\n#### Convenience methods of `RecursiveItems`\n- `toArray(): array`\nIf you are sure that a certain instance of RecursiveItems is pointing to a memory-manageable data structure\n(for example, $friend), you can call `$friend-\u003etoArray()`, and the item will materialize into a plain PHP array.\n\n- `advanceToKey(int|string $key): scalar|RecursiveItems`\nWhen searching for a specific key in a collection (for example, `'friends'` in `$user`),\nyou do not need to use a loop and a condition to search for it.\nInstead, you can simply call `$user-\u003eadvanceToKey(\"friends\")`.\nIt will iterate for you and return the value at this key. Calls can be chained.\nIt also supports **array like syntax** for advancing to and getting following indices.\nSo `$user['friends']` would be an alias for `$user-\u003eadvanceToKey('friends')`. Calls can be chained.\nKeep in mind that it's just an alias - **you won't be able to random-access previous indices**\nafter using this directly on `RecursiveItems`. It's just a syntax sugar.\nUse `toArray()` if you need random access to indices on a record/item.\n\nThe previous example could thus be simplified as follows:\n```php\n\u003c?php\n\nuse JsonMachine\\RecursiveItems\n\n$users = RecursiveItems::fromFile('users.json');\nforeach ($users as $user) {\n    /** @var $user RecursiveItems */\n    foreach ($user['friends'] as $friend) { // or $user-\u003eadvanceToKey('friends')\n        /** @var $friend RecursiveItems */\n        $friendArray = $friend-\u003etoArray();\n        $friendArray['username'] === 'friend1';\n    }\n}\n```\nChaining allows you to do something like this:\n```php\n\u003c?php\n\nuse JsonMachine\\RecursiveItems\n\n$users = RecursiveItems::fromFile('users.json');\n$users[0]['friends'][1]['username'] === 'friend2';\n\n```\n\n#### Also `RecursiveItems implements \\RecursiveIterator`\nSo you can use for example PHP's builtin tools to work over `\\RecursiveIterator` like those:\n\n- [RecursiveCallbackFilterIterator](https://www.php.net/manual/en/class.recursivecallbackfilteriterator.php) \n- [RecursiveFilterIterator](https://www.php.net/manual/en/class.recursivefilteriterator.php) \n- [RecursiveRegexIterator](https://www.php.net/manual/en/class.recursiveregexiterator.php) \n- [RecursiveTreeIterator](https://www.php.net/manual/en/class.recursivetreeiterator.php)\n\n\u003ca name=\"json-pointer\"\u003e\u003c/a\u003e\n### What is JSON Pointer anyway?\nIt's a way of addressing one item in JSON document. See the [JSON Pointer RFC 6901](https://tools.ietf.org/html/rfc6901).\nIt's very handy, because sometimes the JSON structure goes deeper, and you want to iterate a subtree,\nnot the main level. So you just specify the pointer to the JSON array or object (or even to a scalar value) you want to iterate and off you go.\nWhen the parser hits the collection you specified, iteration begins. You can pass it as `pointer` option in all\n`Items::from*` functions. If you specify a pointer to a non-existent position in the document, an exception is thrown.\nIt can be used to access scalar values as well. **JSON Pointer itself must be a valid JSON string**. Literal comparison\nof reference tokens (the parts between slashes) is performed against the JSON document keys/member names.\n\nSome examples:\n\n| JSON Pointer value       | Will iterate through                                                                                      |\n|--------------------------|-----------------------------------------------------------------------------------------------------------|\n| (empty string - default) | `[\"this\", \"array\"]` or `{\"a\": \"this\", \"b\": \"object\"}` will be iterated (main level)                       |\n| `/result/items`          | `{\"result\": {\"items\": [\"this\", \"array\", \"will\", \"be\", \"iterated\"]}}`                                      |\n| `/0/items`               | `[{\"items\": [\"this\", \"array\", \"will\", \"be\", \"iterated\"]}]` (supports array indices)                       |\n| `/results/-/status`      | `{\"results\": [{\"status\": \"iterated\"}, {\"status\": \"also iterated\"}]}` (a hyphen as an array index wildcard)|\n| `/` (gotcha! - a slash followed by an empty string, see the [spec](https://tools.ietf.org/html/rfc6901#section-5)) | `{\"\":[\"this\",\"array\",\"will\",\"be\",\"iterated\"]}` |\n| `/quotes\\\"`              | `{\"quotes\\\"\": [\"this\", \"array\", \"will\", \"be\", \"iterated\"]}`                                               |\n\n\n\u003ca name=\"options\"\u003e\u003c/a\u003e\n## Options\nOptions may change how a JSON is parsed. Array of options is the second parameter of all `Items::from*` functions.\nAvailable options are:\n- `pointer` - A JSON Pointer string that tells which part of the document you want to iterate.\n- `decoder` - An instance of `ItemDecoder` interface.\n- `debug` - `true` or `false` to enable or disable the debug mode. When the debug mode is enabled, data such as line,\ncolumn and position in the document are available during parsing or in exceptions. Keeping debug disabled adds slight\nperformance advantage.\n\n\u003ca name=\"parsing-json-stream-api-responses\"\u003e\u003c/a\u003e\n## Parsing streaming responses from a JSON API\nA stream API response or any other JSON stream is parsed exactly the same way as file is. The only difference\nis, you use `Items::fromStream($streamResource)` for it, where `$streamResource` is the stream\nresource with the JSON document. The rest is the same as with parsing files. Here are some examples of\npopular http clients which support streaming responses:\n\n\u003ca name=\"guzzlehttp\"\u003e\u003c/a\u003e\n### GuzzleHttp\nGuzzle uses its own streams, but they can be converted back to PHP streams by calling\n`\\GuzzleHttp\\Psr7\\StreamWrapper::getResource()`. Pass the result of this function to\n`Items::fromStream` function, and you're set up. See working\n[GuzzleHttp example](examples/guzzleHttp.php).\n\n\u003ca name=\"symfony-httpclient\"\u003e\u003c/a\u003e\n### Symfony HttpClient\nA stream response of Symfony HttpClient works as iterator. And because JSON Machine is\nbased on iterators, the integration with Symfony HttpClient is very simple. See\n[HttpClient example](examples/symfonyHttpClient.php).\n\n\n\u003ca name=\"tracking-parsing-progress\"\u003e\u003c/a\u003e\n## Tracking the progress (with `debug` enabled)\nBig documents may take a while to parse. Call `Items::getPosition()` in your `foreach` to get current\ncount of the processed bytes from the beginning. Percentage is then easy to calculate as `position / total * 100`.\nTo find out the total size of your document in bytes you may want to check:\n- `strlen($document)` if you parse a string\n- `filesize($file)` if you parse a file\n- `Content-Length` http header if you parse a http stream response\n- ... you get the point\n\nIf `debug` is disabled, `getPosition()` always returns `0`.\n\n```php\n\u003c?php\n\nuse JsonMachine\\Items;\n\n$fileSize = filesize('fruits.json');\n$fruits = Items::fromFile('fruits.json', ['debug' =\u003e true]);\nforeach ($fruits as $name =\u003e $data) {\n    echo 'Progress: ' . intval($fruits-\u003egetPosition() / $fileSize * 100) . ' %'; \n}\n```\n\n\n\u003ca name=\"decoders\"\u003e\u003c/a\u003e\n## Decoders\n`Items::from*` functions also accept `decoder` option. It must be an instance of\n`JsonMachine\\JsonDecoder\\ItemDecoder`. If none is specified, `ExtJsonDecoder` is used by\ndefault. It requires `ext-json` PHP extension to be present, because it uses\n`json_decode`. When `json_decode` doesn't do what you want, implement `JsonMachine\\JsonDecoder\\ItemDecoder`\nand make your own.\n\n\u003ca name=\"available-decoders\"\u003e\u003c/a\u003e\n### Available decoders\n- **`ExtJsonDecoder`** - **Default.** Uses `json_decode` to decode keys and values.\nConstructor has the same parameters as `json_decode`.\n\n- **`PassThruDecoder`** - Does no decoding. Both keys and values are produced as pure JSON strings.\nUseful when you want to parse a JSON item with something else directly in the foreach\nand don't want to implement `JsonMachine\\JsonDecoder\\ItemDecoder`. Since `1.0.0` does not use `json_decode`.\n\nExample:\n```php\n\u003c?php\n\nuse JsonMachine\\JsonDecoder\\PassThruDecoder;\nuse JsonMachine\\Items;\n\n$items = Items::fromFile('path/to.json', ['decoder' =\u003e new PassThruDecoder]);\n```\n\n- **`ErrorWrappingDecoder`** - A decorator which wraps decoding errors inside `DecodingError` object\nthus enabling you to skip malformed items instead of dying on `SyntaxError` exception.\nExample:\n```php\n\u003c?php\n\nuse JsonMachine\\Items;\nuse JsonMachine\\JsonDecoder\\DecodingError;\nuse JsonMachine\\JsonDecoder\\ErrorWrappingDecoder;\nuse JsonMachine\\JsonDecoder\\ExtJsonDecoder;\n\n$items = Items::fromFile('path/to.json', ['decoder' =\u003e new ErrorWrappingDecoder(new ExtJsonDecoder())]);\nforeach ($items as $key =\u003e $item) {\n    if ($key instanceof DecodingError || $item instanceof DecodingError) {\n        // handle error of this malformed json item\n        continue;\n    }\n    var_dump($key, $item);\n}\n```\n\n\n\u003ca name=\"error-handling\"\u003e\u003c/a\u003e\n## Error handling\nSince 0.4.0 every exception extends `JsonMachineException`, so you can catch that to filter any error from JSON Machine library.\n\n\u003ca name=\"malformed-items\"\u003e\u003c/a\u003e\n### Skipping malformed items\nIf there's an error anywhere in a json stream, `SyntaxError` exception is thrown. That's very inconvenient,\nbecause if there is an error inside one json item you are unable to parse the rest of the document\nbecause of one malformed item. `ErrorWrappingDecoder` is a decoder decorator which can help you with that.\nWrap a decoder with it, and all malformed items you are iterating will be given to you in the foreach via\n`DecodingError`. This way you can skip them and continue further with the document. See example in\n[Available decoders](#available-decoders). Syntax errors in the structure of a json stream between the iterated\nitems will still throw `SyntaxError` exception though.\n\n\n\u003ca name=\"on-parser-efficiency\"\u003e\u003c/a\u003e\n## Parser efficiency\nThe time complexity is always `O(n)`\n\n\u003ca name=\"streams-files\"\u003e\u003c/a\u003e\n### Streams / files\nTL;DR: The memory complexity is `O(2)`\n\nJSON Machine reads a stream (or a file) 1 JSON item at a time and generates corresponding 1 PHP item at a time.\nThis is the most efficient way, because if you had say 10,000 users in JSON file and wanted to parse it using\n`json_decode(file_get_contents('big.json'))`, you'd have the whole string in memory as well as all the 10,000\nPHP structures. Following table shows the difference:\n\n|                        | String items in memory at a time | Decoded PHP items in memory at a time | Total |\n|------------------------|---------------------------------:|--------------------------------------:|------:|\n| `json_decode()`        |                            10000 |                                 10000 | 20000 |\n| `Items::from*()`       |                                1 |                                     1 |     2 |\n\nThis means, that JSON Machine is constantly efficient for any size of processed JSON. 100 GB no problem.\n\n\u003ca name=\"in-memory-json-strings\"\u003e\u003c/a\u003e\n### In-memory JSON strings\nTL;DR: The memory complexity is `O(n+1)`\n\nThere is also a method `Items::fromString()`. If you are\nforced to parse a big string, and the stream is not available, JSON Machine may be better than `json_decode`.\nThe reason is that unlike `json_decode`, JSON Machine still traverses the JSON string one item at a time and doesn't\nload all resulting PHP structures into memory at once.\n\nLet's continue with the example with 10,000 users. This time they are all in string in memory.\nWhen decoding that string with `json_decode`, 10,000 arrays (objects) is created in memory and then the result\nis returned. JSON Machine on the other hand creates single structure for each found item in the string and yields it back\nto you. When you process this item and iterate to the next one, another single structure is created. This is the same\nbehaviour as with streams/files. Following table puts the concept into perspective:\n\n|                             | String items in memory at a time | Decoded PHP items in memory at a time | Total |\n|-----------------------------|---------------------------------:|--------------------------------------:|------:|\n| `json_decode()`             |                            10000 |                                 10000 | 20000 |\n| `Items::fromString()`       |                            10000 |                                     1 | 10001 |\n\nThe reality is even better. `Items::fromString` consumes about **5x less memory** than `json_decode`. The reason is\nthat a PHP structure takes much more memory than its corresponding JSON representation.\n\n\n\u003ca name=\"troubleshooting\"\u003e\u003c/a\u003e\n## Troubleshooting\n\n\u003ca name=\"step1\"\u003e\u003c/a\u003e\n### \"I'm still getting Allowed memory size ... exhausted\"\nOne of the reasons may be that the items you want to iterate over are in some sub-key such as `\"results\"`\nbut you forgot to specify a JSON Pointer. See [Parsing a subtree](#parsing-a-subtree).\n\n\u003ca name=\"step2\"\u003e\u003c/a\u003e\n### \"That didn't help\"\nThe other reason may be, that one of the items you iterate is itself so huge it cannot be decoded at once.\nFor example, you iterate over users and one of them has thousands of \"friend\" objects in it.\nThe most efficient solution is to use [Recursive iteration](#recursive).\n\n\u003ca name=\"step3\"\u003e\u003c/a\u003e\n### \"I am still out of luck\"\nIt probably means that a single JSON scalar string itself is too big to fit in memory.\nFor example very big base64-encoded file.\nIn that case you will probably be still out of luck until JSON Machine supports yielding of scalar values as PHP streams.\n\n\u003ca name=\"installation\"\u003e\u003c/a\u003e\n## Installation\n\n### Using Composer\n```bash\ncomposer require halaxa/json-machine\n```\n\n### Without Composer\nClone or download this repository and add the following to your bootstrap file:\n```php\nspl_autoload_register(require '/path/to/json-machine/src/autoloader.php');\n```\n\n\u003ca name=\"development\"\u003e\u003c/a\u003e\n## Development\nClone this repository. This library supports two development approaches:\n1. non containerized (PHP and composer already installed on your machine)\n1. containerized (Docker on your machine)\n\n\u003ca name=\"non-containerized\"\u003e\u003c/a\u003e\n### Non containerized\nRun `composer run -l` in the project dir to see available dev scripts. This way you can run some steps\nof the build process such as tests.\n\n\u003ca name=\"containerized\"\u003e\u003c/a\u003e\n### Containerized\n[Install Docker](https://docs.docker.com/install/) and run `make` in the project dir on your host machine\nto see available dev tools/commands. You can run all the steps of the build process separately as well\nas the whole build process at once. Make basically runs composer dev scripts inside containers in the background.\n\n`make build`: Runs complete build. The same command is run via GitHub Actions CI.\n\n\n\n\u003ca name=\"support\"\u003e\u003c/a\u003e\n## Support\nDo you like this library? Star it, share it, show it  :)\nIssues and pull requests are very welcome.\n\n[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/G2G57KTE4)\n\n\u003ca name=\"license\"\u003e\u003c/a\u003e\n## License\nApache 2.0\n\nCogwheel element: Icons made by [TutsPlus](https://www.flaticon.com/authors/tutsplus)\nfrom [www.flaticon.com](https://www.flaticon.com/)\nis licensed by [CC 3.0 BY](http://creativecommons.org/licenses/by/3.0/)\n\n\u003ci\u003e\u003ca href='http://ecotrust-canada.github.io/markdown-toc/'\u003eTable of contents generated with markdown-toc\u003c/a\u003e\u003c/i\u003e\n","funding_links":["https://ko-fi.com/halaxa","https://ko-fi.com/G2G57KTE4"],"categories":["PHP","目录","Table of Contents"],"sub_categories":["数据结构和存储 Data Structure and Storage","Data Structure and Storage"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhalaxa%2Fjson-machine","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhalaxa%2Fjson-machine","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhalaxa%2Fjson-machine/lists"}