Добавить 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, это решение ближе всего к автоматизированному решению.

Другие вопросы по тегам