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

Kiota Serialization Json Laravel Package

microsoft/kiota-serialization-json

JSON serialization library for PHP from Microsoft's Kiota toolchain. Lets Kiota-generated API clients parse and serialize JSON payloads. Install via Composer with microsoft/kiota-serialization-json.

View on GitHub
Deep Wiki
Context7

Getting Started

This package is not a general-purpose JSON serializer — it is the JSON backend for Kiota-generated SDKs. It implements SerializationWriterFactory / ParseNodeFactory from microsoft/kiota-abstractions so that generated models (or hand-written Parsable classes) can be hydrated from and rendered to JSON. You will almost never call it directly if you are using a generated client (Microsoft Graph, SharePoint, any Kiota-generated SDK) — it gets pulled in transitively. You will call it directly if you want to reuse Kiota models without the generated HTTP layer.

Install and check the PHP floor first. The 2.x line requires PHP >= 8.2 (Laravel 11/12 territory). On Laravel 9/10 with PHP 8.1, stay on the 1.5.x line.

composer require microsoft/kiota-serialization-json:^2.2
# PHP 8.1 / Laravel 9-10:
composer require microsoft/kiota-serialization-json:^1.5

Read these four files first — everything else is plumbing:

  • src/JsonSerializationWriterFactory.php — getSerializationWriter(string $contentType)
  • src/JsonSerializationWriter.php — writeStringValue(), writeObjectValue(), getSerializedContent()
  • src/JsonParseNodeFactory.php — getParseNode(string $contentType, StreamInterface|string $payload)
  • src/JsonParseNode.php — getStringValue(), getObjectValue(), getCollectionOfObjectValues()

First use case: typed DTOs from a JSON API without a generated SDK. A model must implement Parsable (and AdditionalDataHolder if you want unknown fields preserved):

use Microsoft\Kiota\Abstractions\Parsable;
use Microsoft\Kiota\Abstractions\Serialization\AdditionalDataHolder;
use Microsoft\Kiota\Abstractions\Serialization\ParseNode;
use Microsoft\Kiota\Abstractions\Serialization\SerializationWriter;

class User implements Parsable, AdditionalDataHolder
{
    public ?string $id = null;
    public ?string $name = null;
    public ?\DateTime $createdAt = null;
    public array $additionalData = [];

    public static function createFromDiscriminatorValue(ParseNode $n): self
    {
        return new self();
    }

    public function getFieldDeserializers(): array
    {
        return [
            'id'        => fn (ParseNode $n) => $this->id = $n->getStringValue(),
            'name'      => fn (ParseNode $n) => $this->name = $n->getStringValue(),
            'createdAt' => fn (ParseNode $n) => $this->createdAt = $n->getDateTimeValue(),
        ];
    }

    public function serialize(SerializationWriter $writer): void
    {
        $writer->writeStringValue('id', $this->id);
        $writer->writeStringValue('name', $this->name);
        $writer->writeDateTimeValue('createdAt', $this->createdAt);
        $writer->writeAdditionalData($this->additionalData);
    }

    public function getAdditionalData(): array { return $this->additionalData; }
    public function setAdditionalData(array $data): void { $this->additionalData = $data; }
}

Deserialize:

$node = (new JsonParseNodeFactory())->getParseNode('application/json', $response->body());
$user = $node->getObjectValue([User::class, 'createFromDiscriminatorValue']);

Serialize (note the stream rewind):

$writer = (new JsonSerializationWriterFactory())->getSerializationWriter('application/json');
$writer->writeObjectValue(null, $user);
$stream = $writer->getSerializedContent();
$stream->rewind();
$json = $stream->getContents(); // '{"id":...,"name":...}'

Implementation Patterns

Bind the factories as singletons. Neither factory holds per-request state, and 2.0.1 specifically reduced parse-node allocation overhead — reusing one instance keeps that win. Register in a small service provider:

$this->app->singleton(JsonParseNodeFactory::class);
$this->app->singleton(JsonSerializationWriterFactory::class);

Wrap it in one serializer service. Keep Kiota types out of controllers and form requests:

class KiotaJson
{
    public function __construct(
        private JsonParseNodeFactory $parser,
        private JsonSerializationWriterFactory $writer,
    ) {}

    public function parse(string $body): ParseNode
    {
        return $this->parser->getParseNode('application/json', $body);
    }

    public function toJson(Parsable $model): string
    {
        $w = $this->writer->getSerializationWriter('application/json');
        $w->writeObjectValue(null, $model);
        $stream = $w->getSerializedContent();
        $stream->rewind();
        return $stream->getContents();
    }
}

Bridge with Laravel's HTTP client instead of Kiota's request adapter. Kiota's auth/retry/redirect stack lives in microsoft/kiota-http-guzzle; if you only want the serialization half (common in a Laravel app that already uses Http::withToken()), skip the adapter entirely and feed the raw body straight into JsonParseNodeFactory:

$body = Http::withToken($token)->get("{$base}/users/{$id}")->throw()->body();
$user = app(KiotaJson::class)->parse($body)
    ->getObjectValue([User::class, 'createFromDiscriminatorValue']);

Use generated SDKs when the API surface is large. For Graph-style APIs, install the generated SDK (microsoft/msgraph-sdk-php) and pass a Guzzle-backed GraphRequestAdapter. The JSON package then becomes invisible — you interact with models, not parse nodes. Note 2.2.0 added Guzzle 8 support, so verify your guzzlehttp/guzzle major matches.

Handle collections with a factory callable, not a class-string:

$users = $node->getChildNode('value')
    ->getCollectionOfObjectValues([User::class, 'createFromDiscriminatorValue']);

Model polymorphism with discriminator mappings. If an endpoint returns a union of shapes, register the mapping on the parent's getFieldDeserializers() via ParseNode::getChildNode() plus your own switch on a type field, or override createFromDiscriminatorValue() to inspect the node and pick the concrete class. Composed/intersection types are supported (since 0.4.0, fixed in 1.3.0), but you must implement Parsable on every variant.

Enums: use the Microsoft\Kiota\Abstractions\Enum trait on a PHP enum-like class and read with getEnumValue([Status::class, 'forValue']). Avoid getStringValue() for enums if you want validation.

Binary payloads: writeBinaryContent() / getBinaryContent() return StreamInterface. Since 0.4.3 you can deserialize an object field directly into a stream — useful for file-download endpoints.

Testing: build fixtures as raw JSON strings and assert on hydrated models. Keep a tests/Fixtures/*.json folder and a helper:

protected function parseFixture(string $file, array $factory): mixed
{
    $json = file_get_contents(base_path("tests/Fixtures/{$file}"));
    return (new JsonParseNodeFactory())
        ->getParseNode('application/json', $json)
        ->getObjectValue($factory);
}

Gotchas and Tips

Version sync is mandatory. All microsoft/kiota-* PHP packages are released together from the microsoft/kiota-php monorepo, and several releases are literally "Synchronize microsoft-kiota-php versions." Mixing kiota-abstractions ^1.5 with kiota-serialization-json ^2.2 will resolve but break on interface changes. Bump them as a set; let Composer resolve the whole family, not one package.

PHP 8.2 is a hard floor on 2.x. 2.0.0 explicitly dropped 7.2–8.2-era support "to address security issues." If your composer.json allows PHP 8.1, Composer will silently pick 1.5.x — check composer show rather than assuming you got the current release.

The stream returned by getSerializedContent() is already written but not rewound. Forgetting ->rewind() yields an empty string, not an error. This is the single most common footgun.

No type coercion since 1.3.0. The library deliberately stopped casting; a getIntValue() against a JSON string, or getStringValue() against a number, can return null instead of a best-effort value. One documented exception: float serialization accepts integers (1.3.1). Validate at the boundary with Validator or a FormRequest before feeding payloads in, and don't rely on ?? to distinguish "missing" from "wrong type."

Nulls vanish from collections. Since 1.0.0, deserialized collections return only non-null entries and are re-indexed. A round trip [1, null, 3] becomes [1, 3]. Don't use this for wire-format fidelity; use it for typed hydration.

additionalData is opt-in at three separate points. The model must implement AdditionalDataHolder, getFieldDeserializers() must not list the key, and serialize() must call writeAdditionalData(). Miss any one and unknown fields are silently dropped — a frequent source of "why did my PATCH lose fields" bugs.

Dates carry timezone semantics. writeDateTimeValue() expects a DateTimeInterface; getDateTimeValue() returns \DateTime|null. Kiota's abstractions also expose date-only and time-only types in recent versions — pick the getter that matches the schema, not the one that happens to compile.

Malformed JSON throws from JsonParseNode. Wrap parse calls in a try/catch and convert to a ValidationException or a 422/502 in your app; leaking a Kiota exception to the user is noise.

php://temp spools to disk. The writer buffers into a temp stream that spills past 2 MB. For large bulk POSTs, serialize per item and stream out rather than building one giant writer.

This repo is release-only. Issues and PRs belong in microsoft/kiota-php, not here. With ~8 stars and zero direct dependents this looks abandoned — it isn't, but the signal is misleading; judge it by the monorepo.

Extension points worth knowing: implement SerializationWriterFactory / ParseNodeFactory for a non-JSON format (e.g. XML, or a Laravel-friendly wrapper) and register them on the Kiota factory registries; implement Parsable directly for hand-written DTOs; override createFromDiscriminatorValue() for polymorphism. There is no Laravel service provider, facade, config file, or Blade/validation integration — you supply all of it.

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.
andydefer/laravel-notification
adachsoft/cli-process-tester
jhaskell13/laraflow
sebastienheyd/boilerplate-media-manager
ccast/tagixo-primix
pretendtrue/laravel-builder
sawmainek/laravel-scout-tntsearch-driver
tomshaw/laravel-calendar-table
dibakar/laravel-ownership
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo
stevenay/myanfont
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc