Русский
Русский
English
Статистика
Реклама

Сериализация в JSON и иммутабельный объект. О пакете built_value для Flutter



Иногда 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")); // trueprint(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:

@nullableString 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:

@nullableBuiltList<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
К списку статей
Опубликовано: 07.11.2020 14:06:38
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Dart

Flutter

Json

Serialization

Immutable

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru