{"id":51842957,"url":"https://github.com/mbolli/php-ron","last_synced_at":"2026-07-23T08:30:54.877Z","repository":{"id":365053246,"uuid":"1268956234","full_name":"mbolli/php-ron","owner":"mbolli","description":"PHP implementation of RON (Readable Object Notation): JSON's value model with lighter syntax (for humans and LLMs)","archived":false,"fork":false,"pushed_at":"2026-06-24T15:32:21.000Z","size":328,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-06-24T17:16:44.725Z","etag":null,"topics":["canonicalization","data-format","json","llm-tools","php","readable-object-notation","rfc8785","ron","serialization"],"latest_commit_sha":null,"homepage":"https://mbolli.github.io/php-ron/","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mbolli.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","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,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-14T06:05:45.000Z","updated_at":"2026-06-24T15:32:44.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/mbolli/php-ron","commit_stats":null,"previous_names":["mbolli/php-ron"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/mbolli/php-ron","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mbolli%2Fphp-ron","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mbolli%2Fphp-ron/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mbolli%2Fphp-ron/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mbolli%2Fphp-ron/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mbolli","download_url":"https://codeload.github.com/mbolli/php-ron/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mbolli%2Fphp-ron/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35796815,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"online","status_checked_at":"2026-07-23T02:00:06.683Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["canonicalization","data-format","json","llm-tools","php","readable-object-notation","rfc8785","ron","serialization"],"created_at":"2026-07-23T08:30:54.304Z","updated_at":"2026-07-23T08:30:54.872Z","avatar_url":"https://github.com/mbolli.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# php-ron\n\n[![PHP](https://img.shields.io/badge/php-%E2%89%A58.3-777BB4?logo=php\u0026logoColor=white)](https://www.php.net/)\n[![CI](https://github.com/mbolli/php-ron/actions/workflows/ci.yml/badge.svg)](https://github.com/mbolli/php-ron/actions/workflows/ci.yml)\n[![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)\n[![Downloads](https://img.shields.io/packagist/dt/mbolli/php-ron)](https://packagist.org/packages/mbolli/php-ron)\n[![Tests](https://img.shields.io/badge/tests-270%20passing-brightgreen)](tests/)\n[![PHPStan](https://img.shields.io/badge/PHPStan-level%209-brightgreen)](.phpstan.neon)\n[![Code style](https://img.shields.io/badge/code%20style-php--cs--fixer-brightgreen)](.php-cs-fixer.php)\n\nA performance-focused PHP implementation of [RON (Readable Object Notation)](https://github.com/starfederation/ron).\n\nRON keeps JSON's value model but drops avoidable syntax: top-level object braces can be\nelided, strings can be bare, commas are optional separators, and quoted strings use repeated\n`'`/`\"` delimiters with no backslash escapes. It converts losslessly to and from JSON and is\ncheaper for humans and LLMs to read and write.\n\nReach for it wherever you'd use JSON but a person or an LLM authors or reads the data — config\nfiles, fixtures, logs, and prompt/context payloads where the saved quotes and braces add up to\nreal tokens. Because every RON document maps 1:1 to a JSON value, you can adopt it only at those\nedges (author or display RON, keep storing and transmitting JSON) without changing your data model.\n\nThis library is a port of the reference Go implementation\n([ron-go](https://github.com/starfederation/ron-go)) and passes the upstream conformance\ncorpus (`testdata/conformance`) and the RFC 8785 corpus (`testdata/rfc8785`).\n\n## Requirements\n\n- PHP \u003e= 8.3 (`ext-hash` for `sha256`, `ext-mbstring`)\n\n## Install\n\n```bash\ncomposer require mbolli/php-ron\n```\n\n## Usage\n\n```php\nuse Mbolli\\Ron\\Ron;\n\n// Encode/decode arbitrary PHP values, like json_encode/json_decode\nRon::encode(['name' =\u003e 'Ada', 'active' =\u003e true]); // active true\\nname Ada\\n\nRon::encode($data, pretty: false);                // compact RON\nRon::decode(\"name Ada\\nactive true\");             // ['active' =\u003e true, 'name' =\u003e 'Ada']\n\n// RON -\u003e JSON (compact by default, canonical key order)\nRon::toJson(\"name Ada\\nactive true\");          // {\"active\":true,\"name\":\"Ada\"}\nRon::toJson($ron, pretty: true);                // multiline JSON\n\n// JSON -\u003e RON (pretty by default, canonical key order)\nRon::fromJson('{\"name\":\"Ada\",\"active\":true}'); // active true\\nname Ada\\n\nRon::fromJson($json, pretty: false);            // compact RON\n\n// Canonical RON and its SHA-256 hash (64 lowercase hex)\nRon::canonicalRon($json);\nRon::canonicalHash($json);\n\n// RFC 8785 (JCS) canonical JSON\nRon::canonicalJson($json);\n```\n\nInvalid input throws `Mbolli\\Ron\\RonException`.\n\n### Typed-value render hooks\n\n`fromJson` accepts an optional hook that rewrites JSON values before rendering, e.g. to emit\ntyped RON forms. The hook receives the path (object keys as strings, array indices as ints;\nroot is `[]`) and the value, and returns `[replacement, replaced]`:\n\n```php\nRon::fromJson($json, mapper: function (array $path, mixed $value): array {\n    if ($path === ['committed']) {\n        return [['#utc' =\u003e $value], true]; // -\u003e committed {#utc ...}\n    }\n    return [$value, false];\n});\n```\n\n### Typed vocabularies\n\nA typed value is a single-key object whose key starts with `#`, e.g. `{\"#utc\": \"...\"}`, which RON\nrenders compactly as `{#utc ...}`. This rendering is always on. Optionally, `fromJson` can validate\ntyped payloads against the [official vocabularies](https://github.com/starfederation/ron/blob/main/docs/vocabularies.md)\n(core, time, network, math, spatial, color, geo). The **core** vocabulary is enabled by default;\nthe rest are opt-in. Pass `vocabularies: []` to disable validation entirely.\n\n```php\nuse Mbolli\\Ron\\Vocabulary\\VocabularyRegistry;\n\n// Core is validated by default: a malformed payload throws RonException.\nRon::fromJson('{\"id\":{\"#uid\":\"not-a-uuid\"}}');                       // throws\n\n// Enable additional vocabularies explicitly.\nRon::fromJson($json, vocabularies: [\n    VocabularyRegistry::CORE_V1,\n    VocabularyRegistry::TIME_V1,\n]);\n\n// Validate without rendering.\nRon::validate($json, [VocabularyRegistry::SPATIAL_V1]);\n\n// Register a custom, namespaced vocabulary. A validator returns true to accept the\n// payload, false to reject it, or any other value to replace it (a transform); to\n// replace it with a literal boolean, return VocabularyRegistry::replace($bool).\n$registry = VocabularyRegistry::official();\n$registry-\u003eregister('https://example.com/vocab/invoice/v1', [\n    '#com.example/money' =\u003e static fn (mixed $payload): bool =\u003e is_array($payload) \u0026\u0026 count($payload) === 2,\n]);\nRon::fromJson($json, vocabularies: ['https://example.com/vocab/invoice/v1'], registry: $registry);\n```\n\nUnknown typed values are left as ordinary objects; enabling a vocabulary the registry does not\nsupport throws.\n\n## Scope\n\nThis implementation matches ron-go: RON\u003c-\u003eJSON conversion (compact/pretty/canonical), RFC 8785\ncanonical JSON, the typed-value render hook, and typed-vocabulary validation (validation runs on\nthe JSON-\u003eRON path; `toJson` is not validated). Validation preserves the value model, so\nvocabulary-tagged objects always round-trip losslessly.\n\n## Performance\n\nThe hot paths scan bytes with native C functions (`strcspn`/`strpos`) instead of per-character\nPHP loops, sort object keys with `array_multisort` (no per-comparison PHP callback), and stream\nRON-\u003eJSON directly without an intermediate tree. The canonical hash uses native `hash('sha256')`.\n\nMeasured throughput (OPcache + JIT), flat and linear from ~1.5 KB to ~320 KB, on the same input\nagainst the Go reference ([ron-go](https://github.com/starfederation/ron-go), compiled):\n\n| Conversion            | php-ron  | ron-go (Go) | vs ron-go    |\n| --------------------- | -------- | ----------- | ------------ |\n| JSON -\u003e RON (compact) | ~18 MB/s | ~45 MB/s    | ~2.5x slower |\n| JSON -\u003e RON (pretty)  | ~15 MB/s | ~18 MB/s    | ~on par      |\n| RON -\u003e JSON (compact) | ~14 MB/s | ~183 MB/s   | ~13x slower  |\n| canonical hash        | ~18 MB/s | —           |              |\n\nOn pretty JSON-\u003eRON the two are ~on par. The compiled reference is faster on the other paths:\nron-go's compact JSON-\u003eRON takes an unordered-decode fast path (~2.5x), and its RON-\u003eJSON streams\nthrough a dedicated hot path with pooled buffers (~13x) — the realistic interpreter tax for\nbyte-level streaming against optimized Go. A 1-10 KB payload still converts in well under a\nmillisecond.\n\nNumbers are from one ~31 KB document on one machine (php-ron on PHP 8.4 + JIT; ron-go `main`,\ncompiled with Go 1.26); run `composer benchmark` (or `php bin/benchmark.php`) to reproduce locally.\n\n## Testing\n\nThe suite runs against three pinned upstream corpora (each a git submodule): the official RON\n[conformance corpus](https://github.com/starfederation/ron) (exact RON \u003c-\u003e JSON byte matches plus\ncanonical SHA-256 hashes, plus the typed-vocabulary fixtures for rendering and validation), its\nRFC 8785 corpus, and [nst/JSONTestSuite](https://github.com/nst/JSONTestSuite), whose every valid\ndocument is round-tripped through both `fromJson`/`toJson` and `encode`/`decode` to prove conversion\nis lossless. Static analysis runs at PHPStan level 9 with php-cs-fixer.\n\n## Development\n\n`composer install` initializes and updates the corpus submodules automatically (via a\npost-install hook), so the following is enough after cloning:\n\n```bash\ncomposer install\ncomposer test     # or: composer check  (lint + phpstan + test)\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmbolli%2Fphp-ron","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmbolli%2Fphp-ron","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmbolli%2Fphp-ron/lists"}