Несколько форм в Dart
Одно поле может хранить разные варианты данных:
успех с числом или ошибка с текстом. Ключевое слово
sealed на базовом классе перечисляет
допустимые формы через final подклассы,
а запись и чтение JSON задают вручную в методах
toJson и фабрике по полю type.
Для JSON удобно добавить служебный ключ
type - дискриминатор, по которому при
чтении выбирают конкретный подкласс. Короткая
метка вроде "paid" задается в коде
записи и разбора:
sealed class PayReply {}
final class Paid extends PayReply {
Paid(this.amount);
final int amount;
}
final class Declined extends PayReply {
Declined(this.reason);
final String reason;
}
Map<String, Object?> payReplyToJson(PayReply reply) {
return switch (reply) {
Paid(:final amount) => {
'type': 'paid',
'amount': amount,
},
Declined(:final reason) => {
'type': 'declined',
'reason': reason,
},
};
}
PayReply payReplyFromJson(Map<String, Object?> json) {
return switch (json['type']) {
'paid' => Paid(json['amount'] as int),
'declined' => Declined(json['reason'] as String),
_ => Declined(json['type'] as String),
};
}
Запись значения типа PayReply включает
"type":"paid" или "type":"declined"
рядом с полями варианта:
import 'dart:convert';
sealed class PayReply {}
final class Paid extends PayReply {
Paid(this.amount);
final int amount;
}
final class Declined extends PayReply {
Declined(this.reason);
final String reason;
}
Map<String, Object?> payReplyToJson(PayReply reply) {
return switch (reply) {
Paid(:final amount) => {
'type': 'paid',
'amount': amount,
},
Declined(:final reason) => {
'type': 'declined',
'reason': reason,
},
};
}
PayReply payReplyFromJson(Map<String, Object?> json) {
return switch (json['type']) {
'paid' => Paid(json['amount'] as int),
'declined' => Declined(json['reason'] as String),
_ => Declined(json['type'] as String),
};
}
PayReply ok = Paid(900);
var text = jsonEncode(payReplyToJson(ok));
print(text);
var raw = jsonDecode(text);
if (raw is Map<String, Object?>) {
var back = payReplyFromJson(raw);
print(back);
}
После разбора статический тип снова PayReply,
а фактическая форма определяется при проверке
ветки switch. Так повторяется идея
закрытой иерархии из раздела об ООП, но здесь
ветку выбирают по строке type в JSON.
Опишите sealed класс InboxEvent
с вариантами Opened (поле msgId,
имя "opened") и Archived
(поле folder, имя "archived").
Запишите событие Opened с идентификатором
7 в JSON и выведите строку.
Разберите текст
{"type":"archived","folder":"2024"}
в InboxEvent и выведите имя папки
из восстановленного варианта.