Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Php Json Schema Generator Laravel Package

evaisse/php-json-schema-generator

evaisse/php-json-schema-generator is a PHP library that builds JSON Schema definitions from classes using reflection and annotations. It maps property types, required fields, nested objects, and arrays, helping generate API contracts, validation schemas, and client models.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a small, single-purpose package: feed it a decoded JSON structure and it walks it recursively, inferring JSON Schema (draft V4) types. There is no config file, no service provider, and no runtime dependency on Laravel — treat it as a dev-time utility you invoke from an artisan command, a tinker session, or a test.

Install and locate the entry point

composer require evaisse/php-json-schema-generator --dev

Open src/ first (the repo is tiny — usually one generator class plus a small type/inference helper). Read the constructor and the public generate()/getSchema() methods before anything else; because the API surface is small and the package is lightly documented, the source is the README. Confirm the exact namespace and method names in composer.json's autoload section and in the class file, then pin your first call against a fixture.

First use case: snapshot a payload into a schema

The realistic day-one job is "I have a sample API response and I want a draft-V4 schema I can commit to the repo." Decode the payload and hand it to the generator:

use Evaisse\PhpJsonSchemaGenerator\SchemaGenerator; // verify FQCN in src/

$payload = json_decode(file_get_contents(storage_path('fixtures/orders.json')), true);

$generator = new SchemaGenerator();
$generator->generate($payload);

file_put_contents(
    base_path('schemas/orders.schema.json'),
    json_encode($generator->getSchema(), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
);

Inspect the emitted schema immediately. The output is a starting point — not a spec — so your first task after generating is deciding which inferred constraints (required, type, pattern) you actually trust and which you must hand-tune.

Where to look next

  • The type-inference branch (int vs float, empty array vs array-of-object) — this determines everything you'll have to patch later.
  • Whether the root is emitted as type: object unconditionally or mirrors the input.
  • Whether $schema is set to http://json-schema.org/draft-04/schema# and $id/id is written.

Implementation Patterns

Wrap it in an artisan command, never call it in request handlers

Generation cost scales with payload size and you'll regenerate rarely. A dedicated command keeps it out of the hot path:

// app/Console/Commands/GenerateSchema.php
protected $signature = 'schema:generate {fixture} {--out=}';

public function handle()
{
    $data = json_decode(file_get_contents($this->argument('fixture')), true);
    if (json_last_error() !== JSON_ERROR_NONE) {
        $this->error('Invalid JSON: '.json_last_error_msg());
        return 1;
    }

    $g = new SchemaGenerator();
    $g->generate($data);

    $out = $this->option('out') ?? base_path('schemas/'.basename($this->argument('fixture')));
    file_put_contents($out, json_encode($g->getSchema(), JSON_PRETTY_PRINT));
    $this->info("Wrote $out");
}

Merge multiple samples before generating

A single sample cannot reveal optional fields. The robust pattern is to collect several representative payloads and normalize them into one structure before handing anything to the generator — either merge keys (union of all objects) or, if you need per-field optionality, generate one schema per sample and diff the required arrays yourself. The package infers from what it sees; it will not discover that customer_id is sometimes absent.

function mergeSamples(array $samples): array {
    // recursively union object keys, keep first-seen scalar type for conflicts
    $merged = [];
    foreach ($samples as $s) { $merged = array_replace_recursive($merged, $s); }
    return $merged;
}

Feed the merged shape to generate(). Expect type conflicts (e.g. "total": 12 in one sample, "total": "12.00" in another) to collapse to whichever value arrives last — flag those manually.

Round-trip against a validator as a sanity check

The fastest way to catch a bad generation is to validate the original sample against the generated schema. Pair with justinrainbow/json-schema (dev dependency):

$validator = new JsonSchema\Validator();
$validator->validate(
    json_decode($rawJson),
    (object) ['$ref' => 'file://'.base_path('schemas/orders.schema.json')]
);
if (!$validator->isValid()) {
    // your generator produced something the source data doesn't satisfy
}

Any failure here is a generator limitation you now know about — pin it as a regression test.

Treat emitted schemas as fixtures, not build artifacts

Commit the JSON. Diff it in code review. Generate it in CI only in a check mode that fails when the committed schema drifts from regeneration. This turns the schema into a contract for your HTTP tests and OpenAPI-adjacent tooling without coupling your app runtime to this package.

Use it for one-off contracts you then harden

The pattern that actually earns its keep: generate, then hand-edit to add "required", "format": "email", "minLength", enums, and numeric bounds. The generator gives you the skeleton (type, properties, nesting); you supply the semantics. Re-running generation later will overwrite your edits, so keep a schemas/_generated/ and schemas/curated/ split if you intend to regenerate.

Gotchas and Tips

One sample = one opinion, not a contract. Fields seen exactly once are treated as present. There is no required inference beyond "keys that appeared." Do not ship generated schemas as API contracts without a manual required pass.

Ambiguous JSON values collapse silently.

  • null has no inferable type in draft V4; expect either "null" in the type union or an omitted type — verify and patch to "type": ["string", "null"] where legitimately nullable.
  • [] (empty array) — most naive generators emit "type": "array" with no items. That accepts anything. Add items manually.
  • 12.0 decoded as PHP float may emit "type": "number" while 12 emits "type": "integer". If you need integer-or-number tolerance, widen to "type": "number".

Arrays of mixed objects. Generators typically inspect only the first element or the union of types. If $items[0] lacks a key that $items[1] has, that property vanishes. Always test with an array containing a deliberately heterogeneous set of records.

Draft V4 only. No const, no if/then/else, no exclusiveMinimum as a number (V4 wants exclusiveMinimum: true alongside minimum), no $defs (draft V4 uses definitions). If you later feed the schema into a tool expecting draft 2019-09 or 2020-12, reserialize against the right meta-schema — the generated $schema value will be wrong for those consumers and some validators will reject it.

No format inference. ISO dates, emails, UUIDs, and URIs all come out as "type": "string". Add format yourself and only if your validator honors it (justinrainbow does not enforce format by default; JSON Schema's format is annotation-only unless you enable assertion).

Beware runtime use with untrusted input. The recursion is depth-proportional to your JSON. A deeply nested or very large payload from an attacker is a cheap way to blow the stack or exhaust memory in whatever endpoint calls this. Keep generation to CLI/tests, or cap input size and depth before calling generate().

Escaping and encoding. The generated object is a plain PHP array; json_encode will happily turn {} into [] if you cast an associative property map to a list. When writing the file, use JSON_FORCE_OBJECT only if you're certain empty properties should serialize as {} — otherwise guard with (object)[] at the property-map boundary. Empty properties serialized as [] is invalid and will confuse validators.

Type-conflict detection is your job. When two samples disagree, the last one wins and you get no warning. Wrap generate() in a small loop over your sample set, md5 the per-sample schema, and fail when schemas diverge rather than silently merging.

Extension points are thin. The class is small; if you need format heuristics or required calculation, the honest path is either (a) post-process the array from getSchema() before writing, or (b) fork/vendor the generator class into your app and add the inference branch yourself. Do not expect hooks, events, or a pluggable type resolver. Post-processing is safer because you stay upgrade-compatible:

$schema = $g->getSchema();
$schema['properties']['email']['format'] = 'email';
$schema['required'] = ['id', 'email'];

Pin the version. At 18 stars the package is effectively unmaintained community code. Use an exact version ("evaisse/php-json-schema-generator": "1.0.*" or whatever the tagged line is) or commit the vendor directory, and add a test that regenerates a known fixture and asserts the output — that test is your real dependency lock.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin