Skip to content

Commit 0b52ffd

Browse files
committed
feat: adds json serialization to DTOs
1 parent 13aaba0 commit 0b52ffd

18 files changed

+559
-12
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace WordPress\AiClient\Common\Contracts;
6+
7+
use JsonSerializable;
8+
9+
/**
10+
* Interface for objects that support JSON serialization and deserialization.
11+
*
12+
* @since 1.0.0
13+
*/
14+
interface WithJsonSerialization extends JsonSerializable
15+
{
16+
/**
17+
* Creates an instance from JSON data.
18+
*
19+
* @since 1.0.0
20+
*
21+
* @param array<string, mixed> $json The JSON data.
22+
* @return self The created instance.
23+
*/
24+
public static function fromJson(array $json);
25+
}

src/Files/DTO/File.php

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace WordPress\AiClient\Files\DTO;
66

77
use WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface;
8+
use WordPress\AiClient\Common\Contracts\WithJsonSerialization;
89
use WordPress\AiClient\Files\Enums\FileTypeEnum;
910
use WordPress\AiClient\Files\ValueObjects\MimeType;
1011

@@ -16,7 +17,7 @@
1617
*
1718
* @since n.e.x.t
1819
*/
19-
class File implements WithJsonSchemaInterface
20+
class File implements WithJsonSchemaInterface, WithJsonSerialization
2021
{
2122
/**
2223
* @var MimeType The MIME type of the file.
@@ -377,4 +378,45 @@ public static function getJsonSchema(): array
377378
],
378379
];
379380
}
381+
382+
/**
383+
* {@inheritDoc}
384+
*
385+
* @since n.e.x.t
386+
*
387+
* @return array<string, mixed>
388+
*/
389+
public function jsonSerialize(): array
390+
{
391+
$data = [
392+
'fileType' => $this->fileType->value,
393+
'mimeType' => $this->getMimeType(),
394+
];
395+
396+
if ($this->fileType->isRemote()) {
397+
$data['url'] = $this->url;
398+
} else {
399+
$data['base64Data'] = $this->base64Data;
400+
}
401+
402+
return $data;
403+
}
404+
405+
/**
406+
* {@inheritDoc}
407+
*
408+
* @since n.e.x.t
409+
*/
410+
public static function fromJson(array $json): File
411+
{
412+
$fileType = FileTypeEnum::from((string) $json['fileType']);
413+
414+
if ($fileType->isRemote()) {
415+
return new self((string) $json['url'], (string) $json['mimeType']);
416+
} else {
417+
// Create data URI from base64 data and mime type
418+
$dataUri = sprintf('data:%s;base64,%s', (string) $json['mimeType'], (string) $json['base64Data']);
419+
return new self($dataUri);
420+
}
421+
}
380422
}

src/Messages/DTO/Message.php

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace WordPress\AiClient\Messages\DTO;
66

77
use WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface;
8+
use WordPress\AiClient\Common\Contracts\WithJsonSerialization;
89
use WordPress\AiClient\Messages\Enums\MessageRoleEnum;
910

1011
/**
@@ -15,7 +16,7 @@
1516
*
1617
* @since n.e.x.t
1718
*/
18-
class Message implements WithJsonSchemaInterface
19+
class Message implements WithJsonSchemaInterface, WithJsonSerialization
1920
{
2021
/**
2122
* @var MessageRoleEnum The role of the message sender.
@@ -90,4 +91,38 @@ public static function getJsonSchema(): array
9091
'required' => ['role', 'parts'],
9192
];
9293
}
94+
95+
/**
96+
* {@inheritDoc}
97+
*
98+
* @since n.e.x.t
99+
*
100+
* @return array<string, mixed>
101+
*/
102+
public function jsonSerialize(): array
103+
{
104+
return [
105+
'role' => $this->role->value,
106+
'parts' => array_map(function (MessagePart $part) {
107+
return $part->jsonSerialize();
108+
}, $this->parts),
109+
];
110+
}
111+
112+
/**
113+
* {@inheritDoc}
114+
*
115+
* @since n.e.x.t
116+
*/
117+
public static function fromJson(array $json): Message
118+
{
119+
$role = MessageRoleEnum::from((string) $json['role']);
120+
/** @var array<array<string, mixed>> $partsData */
121+
$partsData = $json['parts'];
122+
$parts = array_map(function (array $partData) {
123+
return MessagePart::fromJson($partData);
124+
}, $partsData);
125+
126+
return new self($role, $parts);
127+
}
93128
}

src/Messages/DTO/MessagePart.php

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace WordPress\AiClient\Messages\DTO;
66

77
use WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface;
8+
use WordPress\AiClient\Common\Contracts\WithJsonSerialization;
89
use WordPress\AiClient\Files\DTO\File;
910
use WordPress\AiClient\Messages\Enums\MessagePartTypeEnum;
1011
use WordPress\AiClient\Tools\DTO\FunctionCall;
@@ -18,7 +19,7 @@
1819
*
1920
* @since n.e.x.t
2021
*/
21-
class MessagePart implements WithJsonSchemaInterface
22+
class MessagePart implements WithJsonSchemaInterface, WithJsonSerialization
2223
{
2324
/**
2425
* @var MessagePartTypeEnum The type of this message part.
@@ -202,4 +203,56 @@ public static function getJsonSchema(): array
202203
],
203204
];
204205
}
206+
207+
/**
208+
* {@inheritDoc}
209+
*
210+
* @since n.e.x.t
211+
*
212+
* @return array<string, mixed>
213+
*/
214+
public function jsonSerialize(): array
215+
{
216+
$data = ['type' => $this->type->value];
217+
218+
if ($this->type->isText() && $this->text !== null) {
219+
$data['text'] = $this->text;
220+
} elseif ($this->type->isFile() && $this->file !== null) {
221+
$data['file'] = $this->file->jsonSerialize();
222+
} elseif ($this->type->isFunctionCall() && $this->functionCall !== null) {
223+
$data['functionCall'] = $this->functionCall->jsonSerialize();
224+
} elseif ($this->type->isFunctionResponse() && $this->functionResponse !== null) {
225+
$data['functionResponse'] = $this->functionResponse->jsonSerialize();
226+
}
227+
228+
return $data;
229+
}
230+
231+
/**
232+
* {@inheritDoc}
233+
*
234+
* @since n.e.x.t
235+
*/
236+
public static function fromJson(array $json): MessagePart
237+
{
238+
$type = MessagePartTypeEnum::from((string) $json['type']);
239+
240+
if ($type->isText()) {
241+
return new self((string) $json['text']);
242+
} elseif ($type->isFile()) {
243+
/** @var array<string, mixed> $fileData */
244+
$fileData = $json['file'];
245+
return new self(File::fromJson($fileData));
246+
} elseif ($type->isFunctionCall()) {
247+
/** @var array<string, mixed> $functionCallData */
248+
$functionCallData = $json['functionCall'];
249+
return new self(FunctionCall::fromJson($functionCallData));
250+
} elseif ($type->isFunctionResponse()) {
251+
/** @var array<string, mixed> $functionResponseData */
252+
$functionResponseData = $json['functionResponse'];
253+
return new self(FunctionResponse::fromJson($functionResponseData));
254+
}
255+
256+
throw new \InvalidArgumentException(sprintf('Unknown message part type: %s', $json['type']));
257+
}
205258
}

src/Messages/DTO/ModelMessage.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,20 @@ public function __construct(array $parts)
2727
{
2828
parent::__construct(MessageRoleEnum::model(), $parts);
2929
}
30+
31+
/**
32+
* {@inheritDoc}
33+
*
34+
* @since n.e.x.t
35+
*/
36+
public static function fromJson(array $json): ModelMessage
37+
{
38+
/** @var array<array<string, mixed>> $partsData */
39+
$partsData = $json['parts'];
40+
$parts = array_map(function (array $partData) {
41+
return MessagePart::fromJson($partData);
42+
}, $partsData);
43+
44+
return new self($parts);
45+
}
3046
}

src/Messages/DTO/SystemMessage.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,20 @@ public function __construct(array $parts)
2727
{
2828
parent::__construct(MessageRoleEnum::system(), $parts);
2929
}
30+
31+
/**
32+
* {@inheritDoc}
33+
*
34+
* @since n.e.x.t
35+
*/
36+
public static function fromJson(array $json): SystemMessage
37+
{
38+
/** @var array<array<string, mixed>> $partsData */
39+
$partsData = $json['parts'];
40+
$parts = array_map(function (array $partData) {
41+
return MessagePart::fromJson($partData);
42+
}, $partsData);
43+
44+
return new self($parts);
45+
}
3046
}

src/Messages/DTO/UserMessage.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,20 @@ public function __construct(array $parts)
2626
{
2727
parent::__construct(MessageRoleEnum::user(), $parts);
2828
}
29+
30+
/**
31+
* {@inheritDoc}
32+
*
33+
* @since n.e.x.t
34+
*/
35+
public static function fromJson(array $json): UserMessage
36+
{
37+
/** @var array<array<string, mixed>> $partsData */
38+
$partsData = $json['parts'];
39+
$parts = array_map(function (array $partData) {
40+
return MessagePart::fromJson($partData);
41+
}, $partsData);
42+
43+
return new self($parts);
44+
}
2945
}

src/Operations/Contracts/OperationInterface.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace WordPress\AiClient\Operations\Contracts;
66

77
use WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface;
8+
use WordPress\AiClient\Common\Contracts\WithJsonSerialization;
89
use WordPress\AiClient\Operations\Enums\OperationStateEnum;
910

1011
/**
@@ -15,7 +16,7 @@
1516
*
1617
* @since n.e.x.t
1718
*/
18-
interface OperationInterface extends WithJsonSchemaInterface
19+
interface OperationInterface extends WithJsonSchemaInterface, WithJsonSerialization
1920
{
2021
/**
2122
* Gets the operation ID.

src/Operations/DTO/GenerativeAiOperation.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,4 +132,43 @@ public static function getJsonSchema(): array
132132
],
133133
];
134134
}
135+
136+
/**
137+
* {@inheritDoc}
138+
*
139+
* @since n.e.x.t
140+
*
141+
* @return array<string, mixed>
142+
*/
143+
public function jsonSerialize(): array
144+
{
145+
$data = [
146+
'id' => $this->id,
147+
'state' => $this->state->value,
148+
];
149+
150+
if ($this->result !== null) {
151+
$data['result'] = $this->result->jsonSerialize();
152+
}
153+
154+
return $data;
155+
}
156+
157+
/**
158+
* {@inheritDoc}
159+
*
160+
* @since n.e.x.t
161+
*/
162+
public static function fromJson(array $json): GenerativeAiOperation
163+
{
164+
$state = OperationStateEnum::from((string) $json['state']);
165+
$result = null;
166+
if (isset($json['result'])) {
167+
/** @var array<string, mixed> $resultData */
168+
$resultData = $json['result'];
169+
$result = GenerativeAiResult::fromJson($resultData);
170+
}
171+
172+
return new self((string) $json['id'], $state, $result);
173+
}
135174
}

src/Results/Contracts/ResultInterface.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace WordPress\AiClient\Results\Contracts;
66

77
use WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface;
8+
use WordPress\AiClient\Common\Contracts\WithJsonSerialization;
89
use WordPress\AiClient\Results\DTO\TokenUsage;
910

1011
/**
@@ -15,7 +16,7 @@
1516
*
1617
* @since n.e.x.t
1718
*/
18-
interface ResultInterface extends WithJsonSchemaInterface
19+
interface ResultInterface extends WithJsonSchemaInterface, WithJsonSerialization
1920
{
2021
/**
2122
* Gets the result ID.

0 commit comments

Comments
 (0)