PHPackages                             k2gl/array-reader - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. k2gl/array-reader

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

k2gl/array-reader
=================

Read typed values from untyped arrays (JSON, CSV, config, request data) in PHP, with strict and lenient modes

2.7.1(2w ago)0289↓85.4%1MITPHPPHP &gt;=8.1CI passing

Since May 27Pushed 2w ago1 watchersCompare

[ Source](https://github.com/k2gl/array-reader)[ Packagist](https://packagist.org/packages/k2gl/array-reader)[ Docs](https://github.com/k2gl/array-reader)[ RSS](/packages/k2gl-array-reader/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (7)Dependencies (8)Versions (11)Used By (1)

k2gl/array-reader
=================

[](#k2glarray-reader)

[![CI](https://camo.githubusercontent.com/f1bc8ab361132293a072d7fe712f3c1b11a05c2c42468fe29e77763f53149ef0/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f6b32676c2f61727261792d7265616465722f63692e796d6c3f6272616e63683d6d61696e266c6162656c3d4349266c6f676f3d676974687562)](https://github.com/k2gl/array-reader/actions/workflows/ci.yml)[![Latest Stable Version](https://camo.githubusercontent.com/48ac1395eaf9ed6d93ab694ea51e70a81b7c4d18c4512d9d31dde904e0926772/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6b32676c2f61727261792d7265616465723f6c6f676f3d7061636b6167697374266c6f676f436f6c6f723d7768697465)](https://packagist.org/packages/k2gl/array-reader)[![Total Downloads](https://camo.githubusercontent.com/6005018bceb13320ab9186a8667be4d52bad434a87a23afc794880aa9bdcd48f/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6b32676c2f61727261792d7265616465723f6c6f676f3d7061636b6167697374266c6f676f436f6c6f723d7768697465)](https://packagist.org/packages/k2gl/array-reader)[![PHPStan Level](https://camo.githubusercontent.com/01c58e66f2fafb70c17613ff2b1da3f549aade3a735b076da5cd9e5c04b945a5/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d6c6576656c253230392d3261356561373f6c6f676f3d706870266c6f676f436f6c6f723d7768697465)](https://phpstan.org)[![License](https://camo.githubusercontent.com/921e430c5d54d13ffcb0fd5a411443f8bcab5e9656ec65575dc946751a8adad2/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f6b32676c2f61727261792d7265616465723f636f6c6f723d79656c6c6f77677265656e)](https://packagist.org/packages/k2gl/array-reader)

Read typed values out of an untyped `array` — query strings, form input, CSV rows, decoded JSON, config, environment — **without** the `isset(...) && is_string(...) ? ... : null` dance, and **without** dropping to `mixed` in the eyes of PHPStan / Psalm.

```
use K2gl\ArrayReader\ArrayReader;

$request = ArrayReader::of($_GET);

$page    = $request->int('page');             // "5"   -> 5   (int)
$active  = $request->bool('active');          // "on"  -> true (bool)
$perPage = $request->intOr('per_page', 20);   // 20 if it is absent or not a valid number
```

Without it you write the same guard for every field and still end up with `mixed`:

```
$page = isset($_GET['page']) && is_numeric($_GET['page']) ? (int) $_GET['page'] : null;
```

**Why install it:** one tiny, **zero-dependency** class family that turns messy input arrays into typed values, lets you choose how forgiving the conversion is, fails loudly on missing data, and keeps static analysis green.

Install
-------

[](#install)

```
composer require k2gl/array-reader
```

Requires PHP 8.1+. No runtime dependencies.

Pick a reader
-------------

[](#pick-a-reader)

There are three readers. They expose the **same methods** and differ only in how they handle a value whose type doesn't match what you asked for. Here is one input read by each:

```
$data = ['count' => '42', 'price' => '9.99', 'enabled' => 'yes'];
```

### `ArrayReader` — safe casting (use this by default)

[](#arrayreader--safe-casting-use-this-by-default)

Converts the obvious string/number/bool representations and **rejects anything ambiguous or lossy**. Ideal for data that arrives as strings: `$_GET`, `$_POST`, CSV rows, environment variables.

```
use K2gl\ArrayReader\ArrayReader;

$request = ArrayReader::of($data);

$request->int('count');        // 42        — "42" is a whole number
$request->float('price');      // 9.99      — numeric string
$request->bool('enabled');     // true      — "yes"
$request->int('price');        // throws TypeMismatchException — "9.99" is not an integer
$request->intOr('price', 0);   // 0         — the lenient variant returns the default, never throws
```

### `StrictArrayReader` — exact type only

[](#strictarrayreader--exact-type-only)

Accepts a value only if it is **already** the requested type (the lone convenience: an `int` may be read as `float`). Ideal for data you already trust to be well-typed, e.g. a decoded JSON document.

```
use K2gl\ArrayReader\StrictArrayReader;

$document = StrictArrayReader::of(['count' => 42, 'name' => 'Ada']);

$document->int('count');       // 42
$document->string('name');     // 'Ada'

StrictArrayReader::of(['count' => '42'])->int('count'); // throws — "42" is a string, not an int
```

### `LooseArrayReader` — PHP's native cast

[](#loosearrayreader--phps-native-cast)

Casts **any scalar** with PHP's own rules and never rejects a scalar — so malformed input passes through silently. Reach for it only when you explicitly want that behaviour.

```
use K2gl\ArrayReader\LooseArrayReader;

$loose = LooseArrayReader::of($data);

$loose->int('price');          // 9         — (int) "9.99"
$loose->bool('enabled');       // true
$loose->int('count');          // 42

LooseArrayReader::of(['x' => 'abc'])->int('x'); // 0   — (int) "abc"
$loose->int('missing');        // throws MissingKeyException — a missing key is always an error
```

Reading values
--------------

[](#reading-values)

For every scalar type each reader offers two accessors:

- **strict** `int($key)` — returns the value, or throws: `MissingKeyException` when the key is absent, `TypeMismatchException` when the value cannot be produced as the requested type.
- **lenient** `intOr($key, $default = null)` — returns the value, or `$default` when the key is absent or the value cannot be produced. Pass a non-null default and the return type is non-null too (conditional return types, so PHPStan / Psalm narrow it for you).

```
$form = ArrayReader::of($_POST);

$email    = $form->string('email');          // string  (throws if missing / not producible)
$nickname = $form->stringOr('nickname');     // ?string (null when absent)
$age      = $form->intOr('age', 0);          // int     (0 when absent / invalid)
$price    = $form->float('price');           // float
$subscribe = $form->boolOr('subscribe', false);
```

Arrays, lists and nesting
-------------------------

[](#arrays-lists-and-nesting)

`array()`, `list()` and `nested()` **never cast** — in every reader they only validate shape:

```
$config = ArrayReader::of($decoded);

$config->array('options');               // array
$config->list('tags');                   // list — sequential, 0-based keys
$config->nested('database')->string('host');   // a reader of the same kind over the nested array

$config->arrayOr('options', []);         // lenient variants return the default instead of throwing
$config->listOr('tags', []);
$config->nestedOr('database');           // ?reader
```

Read a list of nested objects (`{"items": [{...}, {...}]}`) as a list of readers with `nestedList()` / `nestedListOr()`:

```
$payload = ArrayReader::of(['items' => [['id' => 1], ['id' => 2]]]);

foreach ($payload->nestedList('items') as $item) {
    $item->int('id');                    // each element is a reader of the same kind
}

$payload->nestedListOr('missing');       // null when absent, not a list, or an element is not an array
```

Typed scalar lists
------------------

[](#typed-scalar-lists)

`ints()`, `strings()`, `floats()` and `bools()` read a list and produce every element through the same cast pipeline as the scalar accessors, so the reader's mode applies element by element. They turn `ids[]=1&ids[]=2`, CSV columns or JSON arrays into a `list` / `list` / … without a manual `array_map()`.

```
$query = ArrayReader::of(['ids' => ['1', '2', '3'], 'tags' => ['php', 'json']]);

$query->ints('ids');         // list    => [1, 2, 3]   ('1' cast in safe mode)
$query->strings('tags');     // list => ['php', 'json']

// strict variant throws TypeMismatchException if any element cannot be produced;
// lenient *Or returns the default when the key is absent, the value is not a list,
// or any element cannot be produced (all-or-nothing):
$query->intsOr('ids', []);   // list
$query->floatsOr('missing'); // null
```

For lists of enums or dates see the sections below; for any other element type `listOf()` maps each element of a list through a caster of your own — the "array of objects to DTOs" payload:

```
$payload = ArrayReader::of(['points' => [['x' => 1], ['x' => 2]]]);

$payload->listOf('points', fn (mixed $p) => Point::fromArray((array) $p));  // list
$payload->listOfOr('points', $caster, []);   // default when absent / not a list (caster not run)
```

Enums
-----

[](#enums)

`enum()` / `enumOr()` read a **backed enum**: the enum's backing scalar is produced through the same cast pipeline (so the reader's cast mode applies), then resolved with `BackedEnum::tryFrom()`. A value that is absent, cannot be produced as the backing type, or is not one of the enum's cases throws (strict) or returns the default (`enumOr`):

```
enum Suit: string { case Hearts = 'hearts'; case Spades = 'spades'; }

$card = ArrayReader::of($row);

$card->enum('suit', Suit::class);                 // Suit  (throws if missing / not a valid case)
$card->enumOr('suit', Suit::class);               // ?Suit (null when absent / invalid)
$card->enumOr('suit', Suit::class, Suit::Hearts); // Suit  (Suit::Hearts when absent / invalid)

$deck->enums('suits', Suit::class);               // list  (strict: throws on a bad element)
$deck->enumsOr('suits', Suit::class, []);         // list  (default when absent / any element invalid)
```

The cast mode applies to the backing scalar: with `ArrayReader` (safe) a numeric string `"2"`resolves an `int`-backed enum, `StrictArrayReader` requires the exact backing type, and `LooseArrayReader` coerces any scalar. Only backed enums are supported.

Dates
-----

[](#dates)

`dateTime()` / `dateTimeOr()` read a date/time string into a `DateTimeImmutable`. The value is read as a string through the cast pipeline, then parsed. Without a format any `DateTimeImmutable`-parsable string is accepted (ISO-8601, relative, `@timestamp`); pass a format and the input must match it exactly — surplus characters or parse warnings are rejected.

```
$row = ArrayReader::of(['created_at' => '2024-01-15T10:30:00+00:00', 'day' => '15/01/2024']);

$row->dateTime('created_at');              // DateTimeImmutable (throws if missing / unparsable)
$row->dateTime('day', 'd/m/Y');            // DateTimeImmutable, strict to the format
$row->dateTimeOr('missing');               // ?DateTimeImmutable (null when absent / unparsable)
$row->dateTimeOr('day', null, 'Y-m-d');    // pass a format as the third argument

$log->dateTimes('timestamps');             // list (strict: throws on a bad element)
$log->dateTimesOr('timestamps', []);       // list (default when absent / any unparsable)
```

Nested keys (dot notation)
--------------------------

[](#nested-keys-dot-notation)

Every key-based accessor (`has()`, the scalar getters, `enum()`, `dateTime()`, `nested()`, the list accessors, …) accepts a dot path into nested arrays. A **literal key always wins** — a key that contains a dot keeps resolving to itself — and the path is only walked when no literal key matches, so this is fully backward compatible.

```
$reader = ArrayReader::of(['user' => ['profile' => ['age' => 30]]]);

$reader->int('user.profile.age');     // 30
$reader->has('user.profile.age');     // true
$reader->nested('user.profile')->int('age'); // 30
$reader->intOr('user.profile.missing', 0);   // 0

// A literal "a.b" key still wins over the a -> b path:
ArrayReader::of(['a.b' => 1, 'a' => ['b' => 2]])->int('a.b'); // 1
```

Lazy defaults and required keys
-------------------------------

[](#lazy-defaults-and-required-keys)

`stringOrElse()` / `intOrElse()` / `floatOrElse()` / `boolOrElse()` work like the `*Or` accessors, but the default comes from a callback that runs **only** when the value cannot be produced — handy when computing the default is expensive.

`require()` asserts that a set of keys (dot paths allowed) is present, failing once with **all** the missing keys instead of one at a time, and returns the reader for chaining.

```
$reader->intOrElse('page', fn (): int => $this->countPages());  // callback only runs when 'page' is absent / invalid

ArrayReader::of($payload)
    ->require(['id', 'user.email'])     // throws MissingKeyException listing every missing key
    ->string('user.email');
```

Helpers and JSON
----------------

[](#helpers-and-json)

```
$config = ArrayReader::of($decoded);
$config->has('debug');                   // bool — is the key present? (true even if its value is null)
$config->toArray();                      // the underlying array

$request = ArrayReader::fromJson($body); // decode a JSON object/array straight into a reader
```

Error handling
--------------

[](#error-handling)

Every exception implements `K2gl\ArrayReader\Exception\ArrayReaderException`, so you can catch the whole family at once:

```
use K2gl\ArrayReader\ArrayReader;
use K2gl\ArrayReader\Exception\ArrayReaderException;

try {
    $email = ArrayReader::fromJson($body)->string('email');
} catch (ArrayReaderException $e) {
    // MissingKeyException | TypeMismatchException | InvalidJsonException
}
```

Safe casting reference (`ArrayReader`)
--------------------------------------

[](#safe-casting-reference-arrayreader)

What `ArrayReader` accepts beyond the exact type — anything else throws (strict) or returns the default (`*Or`):

AccessorAlso accepts`string``int`/`float` → string, `bool` → `"1"`/`"0"`, `Stringable``int`integer numeric string (`"5"`, `"-3"`), `bool` → `1`/`0` — rejects floats, `"5.5"`, `"abc"``float``int`, numeric string (`"1.5"`, `"2"`)`bool``"1"`/`"true"`/`"on"`/`"yes"` → true, `"0"`/`"false"`/`"off"`/`"no"`/`""` → false, `int` `0`/`1`Zero dependencies
-----------------

[](#zero-dependencies)

`array-reader` has **no runtime dependencies** — its only `require` is `php` itself. Installing it pulls nothing else into your dependency tree: no transitive packages to audit, no version conflicts to resolve, and it is safe to drop into any application or library, including ones that must stay dependency-light.

License
-------

[](#license)

MIT © Nick Harin. See [LICENSE](LICENSE).

###  Health Score

45

—

FairBetter than 91% of packages

Maintenance96

Actively maintained with recent releases

Popularity16

Limited adoption so far

Community13

Small or concentrated contributor base

Maturity49

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 94.3% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~5 days

Total

9

Last Release

20d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/6bc4aa529c7f13ea593297497f6eae20d5c07f476baa0a551960d7e6ff1e5413?d=identicon)[k2gl](/maintainers/k2gl)

---

Top Contributors

[![k2gl](https://avatars.githubusercontent.com/u/2846079?v=4)](https://github.com/k2gl "k2gl (33 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (2 commits)")

---

Tags

jsonPHPStanarraydtocastingtype-safepsalmtyped

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/k2gl-array-reader/health.svg)

```
[![Health](https://phpackages.com/badges/k2gl-array-reader/health.svg)](https://phpackages.com/packages/k2gl-array-reader)
```

###  Alternatives

[nette/utils

🛠 Nette Utils: lightweight utilities for string &amp; array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.

2.1k430.4M1.7k](/packages/nette-utils)[cuyz/valinor

Dependency free PHP library that helps to map any input into a strongly-typed structure.

1.5k13.2M181](/packages/cuyz-valinor)[jbzoo/data

An extended version of the ArrayObject object for working with system settings or just for working with data arrays

851.6M23](/packages/jbzoo-data)[lukascivil/treewalker

TreeWalker is a simple and small Library that will help you to work faster with manipulation of structures in PHP

731.1M7](/packages/lukascivil-treewalker)[selective/transformer

A strictly typed array transformer with dot-access, fluent interface and filters.

3819.8k1](/packages/selective-transformer)[zakirullin/mess

Convenient array-related routine &amp; better type casting

21330.7k2](/packages/zakirullin-mess)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
