Skip to content

Commit 1e42529

Browse files
committed
feat(a2ui_message): make toJson compatible with fromJson
1 parent 5890b91 commit 1e42529

4 files changed

Lines changed: 96 additions & 15 deletions

File tree

packages/genui/lib/src/model/a2ui_message.dart

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@ sealed class A2uiMessage {
121121
],
122122
);
123123
}
124+
125+
/// Converts this message to a JSON map.
126+
Map<String, dynamic> toJson();
124127
}
125128

126129
/// An A2UI message that signals the client to create and show a new surface.
@@ -155,13 +158,15 @@ final class CreateSurface extends A2uiMessage {
155158
/// If true, the client sends the full data model in A2A metadata.
156159
final bool sendDataModel;
157160

158-
/// Converts this message to a JSON map.
161+
@override
159162
Map<String, dynamic> toJson() => {
160163
'version': 'v0.9',
161-
surfaceIdKey: surfaceId,
162-
'catalogId': catalogId,
163-
'theme': ?theme,
164-
'sendDataModel': sendDataModel,
164+
'createSurface': {
165+
surfaceIdKey: surfaceId,
166+
'catalogId': catalogId,
167+
'theme': theme,
168+
'sendDataModel': sendDataModel,
169+
},
165170
};
166171
}
167172

@@ -187,10 +192,16 @@ final class UpdateComponents extends A2uiMessage {
187192
final List<Component> components;
188193

189194
/// Converts this message to a JSON map.
195+
///
196+
/// The result is compatible with [A2uiMessage.fromJson] for round-trip
197+
/// serialization (e.g. when saving messages locally).
198+
@override
190199
Map<String, dynamic> toJson() => {
191200
'version': 'v0.9',
192-
surfaceIdKey: surfaceId,
193-
'components': components.map((c) => c.toJson()).toList(),
201+
'updateComponents': {
202+
surfaceIdKey: surfaceId,
203+
'components': components.map((c) => c.toJson()).toList(),
204+
},
194205
};
195206
}
196207

@@ -224,12 +235,14 @@ final class UpdateDataModel extends A2uiMessage {
224235
/// key at the path.
225236
final Object? value;
226237

227-
/// Converts this message to a JSON map.
238+
@override
228239
Map<String, dynamic> toJson() => {
229240
'version': 'v0.9',
230-
surfaceIdKey: surfaceId,
231-
'path': path.toString(),
232-
'value': ?value,
241+
'updateDataModel': {
242+
surfaceIdKey: surfaceId,
243+
'path': path.toString(),
244+
'value': value,
245+
},
233246
};
234247
}
235248

@@ -246,6 +259,9 @@ final class DeleteSurface extends A2uiMessage {
246259
/// The ID of the surface that this message applies to.
247260
final String surfaceId;
248261

249-
/// Converts this message to a JSON map.
250-
Map<String, dynamic> toJson() => {'version': 'v0.9', surfaceIdKey: surfaceId};
262+
@override
263+
Map<String, dynamic> toJson() => {
264+
'version': 'v0.9',
265+
'deleteSurface': {surfaceIdKey: surfaceId},
266+
};
251267
}

packages/genui/lib/test/validation.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,10 @@ Future<List<ExampleValidationError>> validateCatalogItemExamples(
8181
components: components,
8282
);
8383

84+
final payload =
85+
surfaceUpdate.toJson()['updateComponents'] as Map<String, dynamic>;
8486
final List<ValidationError> validationErrors = await schema.validate(
85-
surfaceUpdate.toJson(),
87+
payload,
8688
);
8789
if (validationErrors.isNotEmpty) {
8890
errors.add(

packages/genui/test/model/a2ui_message_test.dart

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,66 @@ void main() {
105105
expect(message.toJson(), containsPair('version', 'v0.9'));
106106
});
107107

108+
test('CreateSurface round-trip: toJson then fromJson', () {
109+
const message = CreateSurface(
110+
surfaceId: 's1',
111+
catalogId: 'c1',
112+
theme: {'color': 'blue'},
113+
sendDataModel: true,
114+
);
115+
final decoded = A2uiMessage.fromJson(
116+
Map<String, dynamic>.from(message.toJson()),
117+
);
118+
expect(decoded, isA<CreateSurface>());
119+
final create = decoded as CreateSurface;
120+
expect(create.surfaceId, message.surfaceId);
121+
expect(create.catalogId, message.catalogId);
122+
expect(create.theme, message.theme);
123+
expect(create.sendDataModel, message.sendDataModel);
124+
});
125+
126+
test('UpdateComponents round-trip: toJson then fromJson', () {
127+
const message = UpdateComponents(
128+
surfaceId: 's1',
129+
components: [
130+
Component(id: 'c1', type: 'Text', properties: {'text': 'Hi'}),
131+
],
132+
);
133+
final decoded = A2uiMessage.fromJson(
134+
Map<String, dynamic>.from(message.toJson()),
135+
);
136+
expect(decoded, isA<UpdateComponents>());
137+
final update = decoded as UpdateComponents;
138+
expect(update.surfaceId, message.surfaceId);
139+
expect(update.components.length, message.components.length);
140+
expect(update.components.first.id, message.components.first.id);
141+
});
142+
143+
test('UpdateDataModel round-trip: toJson then fromJson', () {
144+
final message = UpdateDataModel(
145+
surfaceId: 's1',
146+
path: DataPath('/user/name'),
147+
value: 'Alice',
148+
);
149+
final decoded = A2uiMessage.fromJson(
150+
Map<String, dynamic>.from(message.toJson()),
151+
);
152+
expect(decoded, isA<UpdateDataModel>());
153+
final update = decoded as UpdateDataModel;
154+
expect(update.surfaceId, message.surfaceId);
155+
expect(update.path, message.path);
156+
expect(update.value, message.value);
157+
});
158+
159+
test('DeleteSurface round-trip: toJson then fromJson', () {
160+
const message = DeleteSurface(surfaceId: 's1');
161+
final decoded = A2uiMessage.fromJson(
162+
Map<String, dynamic>.from(message.toJson()),
163+
);
164+
expect(decoded, isA<DeleteSurface>());
165+
expect((decoded as DeleteSurface).surfaceId, message.surfaceId);
166+
});
167+
108168
test('fromJson throws on unknown message type', () {
109169
final json = <String, Object>{'version': 'v0.9', 'unknown': {}};
110170
expect(

packages/genui/test/test_infra/validation_test_utils.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,11 @@ void validateCatalogExamples(
5353
components: components,
5454
);
5555

56+
final payload =
57+
surfaceUpdate.toJson()['updateComponents']
58+
as Map<String, dynamic>;
5659
final List<ValidationError> validationErrors = await schema.validate(
57-
surfaceUpdate.toJson(),
60+
payload,
5861
);
5962
expect(validationErrors, isEmpty);
6063
});

0 commit comments

Comments
 (0)