nette / neon
🍸 Nette NEON: encodes and decodes NEON file format.
Requires
- php: 8.0 - 8.5
- ext-json: *
Requires (Dev)
- nette/tester: ^2.4
- phpstan/phpstan-nette: ^2.0@stable
- tracy/tracy: ^2.7
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- dev-master / 4.0.x-dev
- v3.4.x-dev
- v3.4.8
- v3.4.7
- v3.4.6
- v3.4.5
- v3.4.4
- v3.4.3
- v3.4.1
- v3.4.0
- v3.3.x-dev
- v3.3.4
- v3.3.3
- v3.3.2
- v3.3.1
- v3.3.0
- v3.2.x-dev
- v3.2.2
- v3.2.1
- v3.2.0
- v3.1.x-dev
- v3.1.2
- v3.1.1
- v3.1.0
- v3.0.x-dev
- v3.0.1
- v3.0.0
- v2.4.x-dev
- v2.4.3
- v2.4.2
- v2.4.1
- v2.4.0
- v2.3.x-dev
- v2.3.5
- v2.3.4
- v2.3.3
- v2.3.2
- v2.3.1
- v2.3.0
- 2.2.x-dev
- v2.2.6
- v2.2.5
- v2.2.4
- v2.2.3
- v2.2.2
- v2.2.1
- v2.2.0
- dev-format-preserve
This package is auto-updated.
Last update: 2026-10-11 20:04:01 UTC
README
✅ A NEON parser that keeps every comment and space
✅ Edit a configuration file and the diff is only your change
✅ Your own values for entities, constants or parameters
✅ neon-lint for editors, CI and AI agents
NEON parser, encoder and lossless editor for PHP. NEON is the human-friendly configuration format of the Nette framework, a YAML alternative with entities. This library parses it into a concrete syntax tree in which every character of the file has its place, so printing the tree gives the file back byte for byte:
$document = Nette\Neon\Neon::parse($text); $document->print() === $text; // true, for any input NEON accepts
That is what makes editing safe. Change a value, add a key, remove an item, and the rest of the file stays as its author wrote it:
$document->setValue(['database', 'host'], 'db.example.com');
- host: localhost # the server
+ host: db.example.com # the server
On that you build configuration editors and admin panels, installers that register an extension
in config.neon, migrations that rename a service, and AI agents that change configuration
without wrecking it.
Support Neon
Do you like NEON? Are you looking forward to the new features?
Thank you!
NEON in one minute
# mappings and sequences, indented by tabs or spaces database: host: localhost port: 3306 roles: [guest, member, admin] # inline arrays # entities: a value with attributes, the way Nette writes services mailer: App\Mailer(@smtp, timeout: 30) note: ''' multiline strings without escaping '''
JSON is a subset of NEON. The format is described in the manual, and you can try it in the sandbox.
▶ Runnable examples: examples/edge-cases
Installation
composer require nette/neon
It requires PHP 8.4 up to 8.6 with the json and mbstring extensions.
Decoding and encoding
use Nette\Neon\Neon; $value = Neon::decode('hello: world'); // ['hello' => 'world'] $value = Neon::decodeFile('config.neon'); $neon = Neon::encode(['a' => [1, 2]]); // '{a: [1, 2]}' $neon = Neon::encode(['a' => [1, 2]], true); // "a:\n\t- 1\n\t- 2\n\n" $neon = Neon::encode($config, fn($v, $path) => !$path); // blocks for the first level only
| NEON | PHP |
|---|---|
null, true, yes ... |
null, bool |
| integer | int, beyond the 64-bit range a string of its digits |
| float | float |
| string | string |
| date | DateTimeImmutable; an invalid date such as 2024-02-30 stays a string |
| entity, chain | Nette\Neon\Entity |
| list, map | array |
Errors throw Nette\Neon\Exception with the position of the problem. What encode() cannot write
without loss, such as invalid UTF-8, INF or a cycle, is an error that names the path to the value.
▶ Runnable examples: examples/parsing, examples/encoding
Format-preserving editing
Every edit changes the tokens it edits and nothing else, and the guarantees of the specification (the file reads back as the same tree, with the expected value, with the change only where it was made) are checked after every operation of a large corpus of edits.
use Nette\Neon\CommentPolicy; use Nette\Neon\Neon; $document = Neon::parse($text); $document->setValue(['database', 'port'], 3307); // 0x0CEA stays hexadecimal: 0x0CEB $document->setValue(['database', 'debug'], false); // yes becomes no, not false $document->setValue(['timeout'], 30); // the same value: no change at all $document->setValue(['parameters', 'admin'], 'a@b.cz'); // a new key, indented like its siblings $document->addItem(['services'], 'App\OrderFacade'); // a new item of a list $document->removeItem(['database', 'driver']); // with the comment that describes it $document->removeItem(['mail'], CommentPolicy::MoveToNextToken); file_put_contents('config.neon', $document->print());
A missing key in the middle of a path is an error, so a typo does not create a new section.
Removing the last item of a collection leaves it empty (services: []), because in a
configuration empty and missing often mean different things; an item marked nullable loses its
value instead. A refused edit changes nothing, not even half of itself.
▶ Runnable examples: examples/editing, examples/config-editor
The tree: nodes, slots, tokens, trivia
$item = $document->findItem(['database', 'user']); $item->getValue(); // 'root' $item->getTrailingComments(); // the comment on its line $item->value->getFirstToken()->text; // the text exactly as written $item->getPosition(); // line, column and offset, computed from the current tree
Eight node classes (DocumentNode, BlockArrayNode, InlineArrayNode, ArrayItemNode,
EntityNode, EntityChainNode, StringNode, LiteralNode) with named slots, tokens with their
trivia (whitespace, line endings, comments), find(), findAncestor(), siblings, and
Traverser for a walk with replacements.
▶ Runnable examples: examples/tree
Your own values: the Evaluator
Neon::decode() is the tree projected onto PHP values by Nette\Neon\Evaluator. A subclass
changes how literals, strings, entities and items become values, while the keys and the order of
the arrays stay those of the document; that is how the DI container of Nette reads its
configuration.
final class ConstantEvaluator extends Nette\Neon\Evaluator { protected function evaluateLiteral(Nette\Neon\Nodes\LiteralNode $node, Nette\Neon\Place $place): mixed { $value = parent::evaluateLiteral($node, $place); return is_string($value) && defined($value) ? constant($value) : $value; } } $config = new ConstantEvaluator()->evaluate(Nette\Neon\Neon::parse($text)->value);
▶ Runnable examples: examples/evaluator
For editors, CI and AI agents
vendor/bin/neon-lint config/
config/local.neon:3:2: Duplicated key 'user'
One line per finding, the exit code as the verdict (0 valid, 1 findings, 2 a failure of the tool),
--json for tools, --check to verify that the file reads back byte for byte. An AI agent that
runs it after it saves a .neon file fixes a broken one before it goes on.
▶ Runnable examples: examples/linting
One format, one specification
The format, the value model, the tree and the semantics of editing are written down in a language-neutral specification with a conformance corpus, and every implementation runs that corpus in its tests. The same specification is implemented for JavaScript and TypeScript by @nette/neon; the two implementations were checked against each other and against nette/neon 3.4 over tens of thousands of real-world files.
Upgrading from 3.x
Neon::decode(), decodeFile() and encode() work as before and give the same values. What
changed:
- it requires PHP 8.4 or newer with the
mbstringextension; - some error messages are more precise, so code that matches their text may need a change;
- the tree is new:
Neon::parse()gives a losslessDocumentNode;Decoder,Encoder,Node\*and the oldTraverserare gone, and so are the constantsNeon::BLOCKandNeon::CHAIN(useNeon::Chain); encode()refuses invalid UTF-8 instead of replacing it, and writes floats and dates without loss;- a subclass of
Evaluatorthat projects two different keys of one array onto the same key gets an error, not a silently overwritten value.
Limits
- No formatter: the library keeps your formatting, it does not impose one.
- No schema or validation of values.
- No error recovery: an invalid file is reported, not partially parsed.
- Positions are computed on request, from the current tree.
Documentation
The examples are the documentation of the API, each chapter with the real output of its programs. The format itself is documented in the Nette manual.