[Dart, Flutter] Сериализация в JSON и иммутабельный объект. О пакете built_value для Flutter
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Иногда JSON от API необходимо конвертировать в объект и желательно в иммутабельное значение. На Dart это возможно, но для этого необходимо много кодить для каждого из объектов. К счастью, существует пакет, который поможет Вам все это выполнить, и в этой статье я Вам расскажу об этом способе.
Наша цель:
1. Сериализация
final user = User.formJson({"name": "Maks"});
final json = user.toJson();
2. Использование как значения
final user1 = User.formJson({"name": "Maks"});
final user2 = User((b) => b..name='Maks');
if (user1 == user2) print('Один и тот же пользователь');
3. Иммутабельность
user.name = 'Alex'; // Неверно
final newUser = user.rebuild((b) => b..name='Alex'); // Верно
Устанавливаем пакеты
Открываем файл pubspec.yaml на нашем Flutter проекте и добавляем пакет built_value на dependencies:
...
built_value: ^7.1.0
А также добавляем пакеты built_value_generator и build_runner на dev_dependencies. Эти пакеты помогут генерировать необходимые коды.
dev_dependencies:
...
build_runner: ^1.10.2
built_value_generator: ^7.1.0
Сохраняем файл pubspec.yaml и запускаем “flutter pub get” чтобы получить все необходимые пакеты.
Создаем built_value
Давайте создадим простой класс чтобы увидеть как все работает.
Создаем новый файл user.dart:
import 'package:built_value/built_value.dart';
part 'user.g.dart';
abstract class User implements Built<User, UserBuilder> {
String get name;
User._();
factory User([void Function(UserBuilder) updates]) = _$User;
}
Итак, мы создали простой абстрактный класс User с одним полем name, указали, что наш класс является частью user.g.dart и основная имплементация находится там, в том числе и UserBuilder. Чтобы автоматически создать этот файл, необходимо запустить это в командной строке:
flutter packages pub run build_runner watch
или
flutter packages pub run build_runner build
Запускаем с watch чтобы не перезапускать каждый раз, когда что-то меняется в классе.
После этого мы видим что появился новый файл user.g.dart. Можно посмотреть что внутри и увидеть сколько времени мы сэкономим автоматизируя этот процесс. Когда добавим еще поля и сериализацию этот файл станет еще больше.
Давайте проверим что у нас получилось:
final user = User((b) => b..name = "Max");
print(user);
print(user == User((b) => b..name = "Max")); // true
print(user == User((b) => b..name = "Alex")); // false
nullable
Добавляем новое поле surname на User класс:
abstract class User implements Built<User, UserBuilder> {
String get name;
String get surname;
...
}
Если попробовать вот так:
final user = User((b) => b..name = 'Max');
То мы получим ошибку:
Tried to construct class "User" with null field "surname".
Чтобы surname сделать опциональным нужно использовать nullable:
@nullable
String get surname;
или же нужно каждый раз давать surname:
final user = User((b) => b
..name = 'Max'
..surname = 'Madov');
print(user);
Built Collection
Давайте используем массивы. Для этого нам поможет BuiltList:
import 'package:built_collection/built_collection.dart';
...
abstract class User implements Built<User, UserBuilder> {
...
@nullable
BuiltList<String> get rights;
...
final user = User((b) => b
..name = 'Max'
..rights.addAll(['read', 'write']));
print(user);
Enum
Необходимо ограничить rights, так чтобы кроме ‘read’, ‘write’ и ‘delete’ не принимал другие значения. Для этого в новом файле под именем right.dart создаем новый EnumClass:
import 'package:built_collection/built_collection.dart';
import 'package:built_value/built_value.dart';
part 'right.g.dart';
class Right extends EnumClass {
static const Right read = _$read;
static const Right write = _$write;
static const Right delete = _$delete;
const Right._(String name) : super(name);
static BuiltSet<Right> get values => _$rightValues;
static Right valueOf(String name) => _$rightValueOf(name);
}
User:
@nullable
BuiltList<Right> get rights;
Теперь rights принимает только тип Right:
final user = User((b) => b
..name = 'Max'
..rights.addAll([Right.read, Right.write]));
print(user);
Сериализация
Чтобы эти объекты можно было легко конвертировать в JSON и обратно нам нужно добавить к нашим классам еще пару методов:
...
import 'package:built_value/serializer.dart';
import 'serializers.dart';
...
abstract class User implements Built<User, UserBuilder> {
...
Map<String, dynamic> toJson() => serializers.serializeWith(User.serializer, this);
static User fromJson(Map<String, dynamic> json) =>
serializers.deserializeWith(User.serializer, json);
static Serializer<User> get serializer => _$userSerializer;
}
В принципе для сериализации хватит и этого:
static Serializer<User> get serializer => _$userSerializer;
Но для удобства добавим методы toJson и fromJson.
Также добавляем одну строку в класс Right:
import 'package:built_value/serializer.dart';
,,,
class Right extends EnumClass {
...
static Serializer<Right> get serializer => _$rightSerializer;
}
И нужно создать еще один файл с именем serializers.dart:
import 'package:built_collection/built_collection.dart';
import 'package:built_value/serializer.dart';
import 'package:built_value/standard_json_plugin.dart';
import 'package:built_value_demo/right.dart';
import 'package:built_value_demo/user.dart';
part 'serializers.g.dart';
@SerializersFor([Right, User])
final Serializers serializers =
(_$serializers.toBuilder()..addPlugin(StandardJsonPlugin())).build();
Каждый новый Built класс необходимо добавить в @SerializersFor([...]) чтобы сериализация работала как надо.
Теперь можно проверять что у нас получилось:
final user = User.fromJson({
"name": "Max",
"rights": ["read", "write"]
});
print(user);
print(user.toJson());
final user2 = User((b) => b
..name = 'Max'
..rights.addAll([Right.read, Right.write]));
print(user == user2); // true
Давайте поменяем значения:
final user3 = user.rebuild((b) => b
..surname = "Madov"
..rights.replace([Right.read]));
print(user3);
Дополнительно
По итогу найдутся и те кто скажут, что все равно необходимо писать довольно много. Но если Вы пользуетесь Visual Studio Code рекомендую установить снипет под названием Built Value Snippets и тогда можно все это генерировать автоматически. Для этого поищите в Marketplace или пройдите по этой ссылке.
После установки напишите в Dart файле “bv” и вы сможете увидеть какие опции существуют.
Если Вы не хотите чтобы Visual Studio Code показывал сгенерированные “*.g.dart” файлы, нужно открыть Settings и поискать Files: Exclude, после чего нажмите на Add Pattern и добавьте “**/*.g.dart”.
Что дальше?
На первый взгляд, может показаться, что такое количество приложенных усилий не стоит того, но если у вас много таких классов это в разы облегчит и ускорит весь процесс.
P.S. Буду очень рад и благодарен Вам, если поделитесь своими методами, которые посчитаете практичнее и эффективнее предложенного мной.
GitHub проект
Пакеты:
pub.dev/packages/built_value
pub.dev/packages/built_value_generator
pub.dev/packages/build_runner
===========
Источник:
habr.com
===========
Похожие новости:
- Проект dahliaOS развивает дистрибутив на базе технологий Linux и Fuchsia
- [ReactJS, Flutter] Flutter для React/React Native разработчика
- [Python] aio api crawler
- [Разработка мобильных приложений, Flutter] Flutter: результаты опроса разработчиков за Q3 2020 (перевод)
- [Разработка веб-сайтов, Программирование, Dart, Flutter] Обновление роадмапа AngularDart (перевод)
- [C++, Алгоритмы, Программирование, Процессоры, Разработка веб-сайтов] Исключительно быстрая валидация UTF-8 (перевод)
- [Apache, Big Data, Hadoop] Spark schemaEvolution на практике
- [Flutter, Конференции, Разработка мобильных приложений] Анонс вебинара «Flutter vs технология, на которой пишете вы: за чем будущее?»
- [Разработка мобильных приложений, Проектирование и рефакторинг, Dart, Flutter] Flutter + чистая архитектура: разбираем на примере
- [Программирование, Конференции, Карьера в IT-индустрии, Учебный процесс в IT] Бесплатные онлайн-мероприятия по разработке (14 октября — 20 октября)
Теги для поиска: #_dart, #_flutter, #_flutter, #_dart, #_json, #_serialization, #_immutable, #_dart, #_flutter
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 00:29
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Иногда JSON от API необходимо конвертировать в объект и желательно в иммутабельное значение. На Dart это возможно, но для этого необходимо много кодить для каждого из объектов. К счастью, существует пакет, который поможет Вам все это выполнить, и в этой статье я Вам расскажу об этом способе. Наша цель: 1. Сериализация final user = User.formJson({"name": "Maks"});
final json = user.toJson(); 2. Использование как значения final user1 = User.formJson({"name": "Maks"});
final user2 = User((b) => b..name='Maks'); if (user1 == user2) print('Один и тот же пользователь'); 3. Иммутабельность user.name = 'Alex'; // Неверно
final newUser = user.rebuild((b) => b..name='Alex'); // Верно Устанавливаем пакеты Открываем файл pubspec.yaml на нашем Flutter проекте и добавляем пакет built_value на dependencies: ...
built_value: ^7.1.0 А также добавляем пакеты built_value_generator и build_runner на dev_dependencies. Эти пакеты помогут генерировать необходимые коды. dev_dependencies: ...
build_runner: ^1.10.2 built_value_generator: ^7.1.0 Сохраняем файл pubspec.yaml и запускаем “flutter pub get” чтобы получить все необходимые пакеты. Создаем built_value Давайте создадим простой класс чтобы увидеть как все работает. Создаем новый файл user.dart: import 'package:built_value/built_value.dart';
part 'user.g.dart'; abstract class User implements Built<User, UserBuilder> { String get name; User._(); factory User([void Function(UserBuilder) updates]) = _$User; } Итак, мы создали простой абстрактный класс User с одним полем name, указали, что наш класс является частью user.g.dart и основная имплементация находится там, в том числе и UserBuilder. Чтобы автоматически создать этот файл, необходимо запустить это в командной строке: flutter packages pub run build_runner watch
или flutter packages pub run build_runner build
Запускаем с watch чтобы не перезапускать каждый раз, когда что-то меняется в классе. После этого мы видим что появился новый файл user.g.dart. Можно посмотреть что внутри и увидеть сколько времени мы сэкономим автоматизируя этот процесс. Когда добавим еще поля и сериализацию этот файл станет еще больше. Давайте проверим что у нас получилось: final user = User((b) => b..name = "Max");
print(user); print(user == User((b) => b..name = "Max")); // true print(user == User((b) => b..name = "Alex")); // false nullable Добавляем новое поле surname на User класс: abstract class User implements Built<User, UserBuilder> {
String get name; String get surname; ... } Если попробовать вот так: final user = User((b) => b..name = 'Max');
То мы получим ошибку: Tried to construct class "User" with null field "surname".
Чтобы surname сделать опциональным нужно использовать nullable: @nullable
String get surname; или же нужно каждый раз давать surname: final user = User((b) => b
..name = 'Max' ..surname = 'Madov'); print(user); Built Collection Давайте используем массивы. Для этого нам поможет BuiltList: import 'package:built_collection/built_collection.dart';
... abstract class User implements Built<User, UserBuilder> { ... @nullable BuiltList<String> get rights; ... final user = User((b) => b
..name = 'Max' ..rights.addAll(['read', 'write'])); print(user); Enum Необходимо ограничить rights, так чтобы кроме ‘read’, ‘write’ и ‘delete’ не принимал другие значения. Для этого в новом файле под именем right.dart создаем новый EnumClass: import 'package:built_collection/built_collection.dart';
import 'package:built_value/built_value.dart'; part 'right.g.dart'; class Right extends EnumClass { static const Right read = _$read; static const Right write = _$write; static const Right delete = _$delete; const Right._(String name) : super(name); static BuiltSet<Right> get values => _$rightValues; static Right valueOf(String name) => _$rightValueOf(name); } User: @nullable
BuiltList<Right> get rights; Теперь rights принимает только тип Right: final user = User((b) => b
..name = 'Max' ..rights.addAll([Right.read, Right.write])); print(user); Сериализация Чтобы эти объекты можно было легко конвертировать в JSON и обратно нам нужно добавить к нашим классам еще пару методов: ...
import 'package:built_value/serializer.dart'; import 'serializers.dart'; ... abstract class User implements Built<User, UserBuilder> { ... Map<String, dynamic> toJson() => serializers.serializeWith(User.serializer, this); static User fromJson(Map<String, dynamic> json) => serializers.deserializeWith(User.serializer, json); static Serializer<User> get serializer => _$userSerializer; } В принципе для сериализации хватит и этого: static Serializer<User> get serializer => _$userSerializer;
Но для удобства добавим методы toJson и fromJson. Также добавляем одну строку в класс Right: import 'package:built_value/serializer.dart';
,,, class Right extends EnumClass { ... static Serializer<Right> get serializer => _$rightSerializer; } И нужно создать еще один файл с именем serializers.dart: import 'package:built_collection/built_collection.dart';
import 'package:built_value/serializer.dart'; import 'package:built_value/standard_json_plugin.dart'; import 'package:built_value_demo/right.dart'; import 'package:built_value_demo/user.dart'; part 'serializers.g.dart'; @SerializersFor([Right, User]) final Serializers serializers = (_$serializers.toBuilder()..addPlugin(StandardJsonPlugin())).build(); Каждый новый Built класс необходимо добавить в @SerializersFor([...]) чтобы сериализация работала как надо. Теперь можно проверять что у нас получилось: final user = User.fromJson({
"name": "Max", "rights": ["read", "write"] }); print(user); print(user.toJson()); final user2 = User((b) => b
..name = 'Max' ..rights.addAll([Right.read, Right.write])); print(user == user2); // true Давайте поменяем значения: final user3 = user.rebuild((b) => b
..surname = "Madov" ..rights.replace([Right.read])); print(user3); Дополнительно По итогу найдутся и те кто скажут, что все равно необходимо писать довольно много. Но если Вы пользуетесь Visual Studio Code рекомендую установить снипет под названием Built Value Snippets и тогда можно все это генерировать автоматически. Для этого поищите в Marketplace или пройдите по этой ссылке. После установки напишите в Dart файле “bv” и вы сможете увидеть какие опции существуют. Если Вы не хотите чтобы Visual Studio Code показывал сгенерированные “*.g.dart” файлы, нужно открыть Settings и поискать Files: Exclude, после чего нажмите на Add Pattern и добавьте “**/*.g.dart”. Что дальше? На первый взгляд, может показаться, что такое количество приложенных усилий не стоит того, но если у вас много таких классов это в разы облегчит и ускорит весь процесс. P.S. Буду очень рад и благодарен Вам, если поделитесь своими методами, которые посчитаете практичнее и эффективнее предложенного мной. GitHub проект Пакеты: pub.dev/packages/built_value pub.dev/packages/built_value_generator pub.dev/packages/build_runner =========== Источник: habr.com =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 00:29
Часовой пояс: UTC + 5