Добавить JSON сериализатор для каждого класса модели?
Когда дело доходит до кодирования JSON в Dart, согласно утверждению Сета Лэдда, окончательно утвержденный, теперь официальный путь - dart:convert
+ JSON.Encode
,
Допустим, у нас есть несколько классов моделей ( PODO), таких как:
class Customer
{
int Id;
String Name;
}
Теперь я хотел бы иметь возможность просто JSON-кодировать мои доменные объекты следующим образом:
var customer = new Customer()
..Id = 17
..Name = "John";
var json = JSON.encode(customer);
К сожалению, это не сработает...
Uncaught Error: Converting object to an encodable object failed.
Stack Trace:
#0 _JsonStringifier.stringifyValue (dart:convert/json.dart:416)
#1 _JsonStringifier.stringify (dart:convert/json.dart:336)
#2 JsonEncoder.convert (dart:convert/json.dart:177)
....
... если мы явно не скажем dart:convert
как кодировать:
class Customer
{
int Id;
String Name;
Map toJson() {
Map map = new Map();
map["Id"] = Id;
map["Name"] = Name;
return map;
}
}
Я действительно должен добавить toJson
метод для каждого из моих классов моделей, или есть лучший способ?
РЕДАКТИРОВАТЬ: это простая сериализация, которую я ищу:
{
"Id": 17,
"Name": "John"
}
Сравнить с ToJson
в ServiceStack.Text, например.
Библиотека сериализации Dart (см. Ответ Мэтта B ниже) выглядит как шаг в правильном направлении. Тем не менее, это...
var serialization = new Serialization()
..addRuleFor(Customer);
var json = JSON.encode(serialization.write(customer, format: new SimpleJsonFormat()));
... создает просто массив со значениями (без ключей):
[17,"John"]
С другой стороны, использование SimpleMapFormat по умолчанию создает это сложное представление.
Все еще не нашел то, что я ищу...
РЕДАКТИРОВАТЬ 2: Добавление некоторого контекста. Я создаю веб-сервис RESTful в Dart и ищу сериализацию JSON, которая может быть легко использована любым клиентом, а не просто другим клиентом Dart. Например, запрос API-интерфейса Stack Exchange для этого самого вопроса создаст этот ответ JSON. Это формат сериализации, который я ищу. - Или посмотрите типичные ответы JSON, возвращаемые API REST Twitter или API Graph Facebook.
РЕДАКТИРОВАТЬ 3: я написал небольшой пост в блоге об этом. Смотрите также обсуждение на Hacker News.
7 ответов
ИМО, это серьезный недостаток в Dart, что удивительно, учитывая его ориентацию на веб-приложения. Я бы подумал, что наличие поддержки JSON в стандартных библиотеках означало бы, что сериализация классов в и из JSON будет работать как вода, к сожалению, поддержка JSON кажется неполной, когда кажется, что выбор состоит в том, чтобы работать со слабо типизированными картами или страдать через ненужный шаблон для настройки ваших стандартных (PODO) классов для сериализации, как и ожидалось.
Без поддержки отражений и зеркал
Поскольку популярные платформы Dart, такие как Flutter, не поддерживают Reflection/Mirrors, единственным вариантом является использование решения Code-Gen. Подход, который мы использовали в собственной поддержке ServiceStack для Dart и Flutter, позволяет генерировать типизированные модели Dart для всех ваших ServiceStack Services из удаленного URL, например:
$ npm install -g @servicestack/cli
$ dart-ref https://www.techstacks.io
Поддерживается в.NET Core и любых популярных опциях хостинга.NET.
В приведенном выше примере генерируется API Typed для проекта .NET Core 2.0 TechStacks с использованием сгенерированных DTO из конечной точки https://www.techstacks.io/types/dart. Это создает модели в соответствии с шаблоном JsonCodec от Dart, где вы можете настроить сериализацию для своих моделей Dart, предоставив fromJson
именованный конструктор и toJson()
метод экземпляра, вот пример одного из сгенерированных DTO:
class UserInfo implements IConvertible
{
String userName;
String avatarUrl;
int stacksCount;
UserInfo({this.userName,this.avatarUrl,this.stacksCount});
UserInfo.fromJson(Map<String, dynamic> json) { fromMap(json); }
fromMap(Map<String, dynamic> json) {
userName = json['userName'];
avatarUrl = json['avatarUrl'];
stacksCount = json['stacksCount'];
return this;
}
Map<String, dynamic> toJson() => {
'userName': userName,
'avatarUrl': avatarUrl,
'stacksCount': stacksCount
};
TypeContext context = _ctx;
}
С этой моделью вы можете использовать встроенные в Dart API json: convert для сериализации и десериализации вашей модели в JSON, например:
//Serialization
var dto = new UserInfo(userName:"foo",avatarUrl:profileUrl,stacksCount:10);
String jsonString = json.encode(dto);
//Deserialization
Map<String,dynamic> jsonObj = json.decode(jsonString);
var fromJson = new UserInfo.fromJson(jsonObj);
Преимущество этого подхода заключается в том, что он работает на всех платформах Dart, включая Flutter и AngularDart или Dart Web Apps с сильным режимом Dart 2 и без него.
Сгенерированные DTO могут также использоваться с пакетом Dart сервис- стека, чтобы обеспечить сквозное типизированное решение, которое заботится о сериализации JSON в и из ваших типизированных DTO, например:
var client = new JsonServiceClient("https://www.techstacks.io");
var response = await client.get(new GetUserInfo(userName:"mythz"));
Для получения дополнительной информации см. Документацию по встроенной поддержке Dart в ServiceStack.
Дротик с зеркалами
Если вы используете Dart на платформе, где доступна поддержка Mirrors, я обнаружил, что использование Mixin требует наименьших усилий, например:
import 'dart:convert';
import 'dart:mirrors';
abstract class Serializable {
Map toJson() {
Map map = new Map();
InstanceMirror im = reflect(this);
ClassMirror cm = im.type;
var decls = cm.declarations.values.where((dm) => dm is VariableMirror);
decls.forEach((dm) {
var key = MirrorSystem.getName(dm.simpleName);
var val = im.getField(dm.simpleName).reflectee;
map[key] = val;
});
return map;
}
}
Который вы можете смешать с вашими классами PODO с:
class Customer extends Object with Serializable
{
int Id;
String Name;
}
Что вы можете теперь использовать с JSON.encode
:
var c = new Customer()..Id = 1..Name = "Foo";
print(JSON.encode(c));
Результат:
{"Id":1,"Name":"Foo"}
Примечание: см. Предостережения с использованием зеркал
Я написал библиотеку Exportable для решения таких задач, как преобразование в Map или JSON. С его помощью объявление модели выглядит так:
import 'package:exportable/exportable.dart';
class Customer extends Object with Exportable {
@export int id;
@export String name;
}
И если вы хотите конвертировать в JSON, вы можете:
String jsonString = customer.toJson();
Также легко инициализировать новый объект из строки JSON:
Customer customer = new Customer()..initFromJson(jsonString);
Или в качестве альтернативы:
Customer customer = new Exportable(Customer, jsonString);
Пожалуйста, смотрите README для получения дополнительной информации.
Redstone mapper - лучшая библиотека сериализации, которую я использовал. У JsonObject и Exportable есть недостаток, заключающийся в том, что вам нужно расширить некоторые их классы. С Redstone Mapper вы можете иметь такие структуры
class News
{
@Field() String title;
@Field() String text;
@Field() List<FileDb> images;
@Field() String link;
}
Он работает с геттерами и сеттерами, вы можете скрыть информацию, не отмечая ее @Field()
, вы можете переименовать поле из / в json, иметь вложенные объекты, это работает на сервере и клиенте. Он также интегрируется с платформой Redstone Server, где у него есть помощники для кодирования / декодирования в MongoDB.
Единственный другой фреймворк, который я видел в правильном направлении - это Дартсон, но в нем по-прежнему отсутствуют некоторые функции по сравнению с Redstone Mapper.
Альтернативой является использование пакета Serialization и добавление правил для ваших классов. Самая основная форма использует отражение, чтобы получить свойства автоматически.
Другой пакет, решающий эту проблему, - build_value:
https://github.com/google/built_value.dart
Со встроенным значением ваши классы моделей выглядят так:
abstract class Account implements Built<Account, AccountBuilder> {
static Serializer<Account> get serializer => _$accountSerializer;
int get id;
String get name;
BuiltMap<String, JsonObject> get keyValues;
factory Account([updates(AccountBuilder b)]) = _$Account;
Account._();
}
Обратите внимание, что built_value - это не только сериализация, но и операторы ==, hashCode, toString и класс компоновщика.
Я решил с:
class Customer extends JsonObject
{
int Id;
String Name;
Address Addr;
}
class Address extends JsonObject{
String city;
String State;
String Street;
}
Но моя цель - связать данные из / в json из / в классы моделей; Это решение работает, если вы можете модифицировать классы модели, в отличие от этого вы должны использовать решение "external" для преобразования классов модели;
см. также: Анализ списка JSON с помощью библиотеки JsonObject в Dart.
Я добился этого:
Чтобы это сработало, передайте explicitToJson: true в аннотации @JsonSerializable() над объявлением класса. Теперь класс User выглядит следующим образом:
import 'address.dart';
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart';
@JsonSerializable(explicitToJson: true)
class User {
String firstName;
Address address;
User(this.firstName, this.address);
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}
Вы можете проверить здесь: https://flutter.dev/docs/development/data-and-backend/json#generating-code-for-nested-classes
Я использовал дартсон, и это кажется очень простым и знакомым (если вы пришли с Java)
Я предпочитаю использовать https://ashamp.github.io/jsonToDartModel/ онлайн-инструмент и писать самостоятельно.
Он имеет следующие функции:
- онлайн-использование, без плагина
- поддержка многомерного списка
- поддержка сложного json
- поддержка преобразования всех реквизитов в тип String
- предупреждение о пустом реквизите
- отдельный файл
- dart ключевое слово защищено
- мгновенное преобразование
Я думаю, что это лучше, чем другие инструменты. Добро пожаловать, если у вас есть предложения, проблемы или сообщения об ошибках.
Некоторые ответы больше не применимы к Flutter 2; вот процесс автоматического создания методов toJson и fromJson:
https://flutter.dev/docs/development/data-and-backend/json
PS: Я бы хотел, чтобы это было так же просто, как использование библиотеки Newtonsoft в Asp.Net, это решение ближе всего к автоматизированному решению.