Подтвердить что ты не робот

Добавить сериализатор JSON для каждого класса модели?

Когда дело доходит до кодирования JSON в Dart, за сообщение Сета Лэдда, наконец, официально утвержденный официальный путь - dart:convert + JSON.Encode.

Скажем, у нас есть куча классов моделей (PODO s), например:

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 для каждого из моих классов моделей или есть лучший способ?

EDIT: это простая сериализация, которую я ищу:

{
    "Id": 17,
    "Name": "John"
}

Сравните с toJson в ServiceStack.Text, например.

Dart serialization библиотека (см. ниже ответ Matt B) выглядит как шаг в правильном направлении. Однако это...

var serialization = new Serialization()
  ..addRuleFor(Customer); 
var json = JSON.encode(serialization.write(customer, format: new SimpleJsonFormat()));

... создает только массив со значениями (без ключей):

[17,"John"]

С помощью SimpleMapFormat по умолчанию создается этот complex.

Я еще не нашел то, что искал...

EDIT 2: добавление некоторого контекста: я создаю веб-службу RESTful в Dart, и я ищу сериализацию JSON, которая может быть легко использована любым клиентом, а не только другим Dart клиент. Например, запрос к API-интерфейсу стека для этого самого вопроса создаст этот ответ JSON. Это формат сериализации, который я ищу. - Или посмотрите типичные ответы JSON, возвращенные Twitter REST API или Facebook Graph API.

EDIT 3: я написал небольшую статью в блоге об этом. См. Также обсуждение в Hacker News.

4b9b3361

Ответ 1

ИМО, это серьезный недостаток в 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.

В приведенном выше примере создается Typed API для проекта .NET Core 2.0 TechStacks с использованием сгенерированных DTO из конечной точки www.techstacks.io/types/dart. Это создает модель следующего Dart JsonCodec шаблона, где вы можете настроить сериализации для моделей Dart, обеспечивая fromJson имени конструктору и toJson() метода экземпляра, вот пример одного из сгенерированного DTOS:

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 built- в json: convert APIs для сериализации и десериализации вашей модели в 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 2s Strong Mode.

Сгенерированные 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"}

Примечание: см. Предостережения с использованием зеркал

Ответ 2

Я написал библиотеку 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.

Ответ 3

Альтернативой является использование пакета Сериализация и добавление правил для ваших классов. Самая основная форма использует отражение, чтобы автоматически получить свойства.

Ответ 4

Redstone mapper - лучшая библиотека сериализации, которую я использовал. JsonObject и Exportable имеют недостаток, что вам нужно расширить некоторые из своих классов. С Redstone Mapper у вас могут быть структуры вроде этого

class News
{
    @Field() String title;
    @Field() String text;
    @Field() List<FileDb> images;
    @Field() String link; 
}

Он работает с геттерами и сеттерами, вы можете скрыть информацию, не аннотируя его с помощью @Field(), вы можете переименовать поле из/в json, иметь вложенные объекты, он работает на сервере и клиенте. Он также интегрируется с инфраструктурой сервера Redstone, где у нее есть помощники для кодирования/декодирования в MongoDB.

Единственная другая структура, которую я видел в правильном направлении, - Dartson, но по-прежнему не хватает некоторых функций по сравнению с Redstone Mapper.

Ответ 5

Я решил с помощью:

class Customer extends JsonObject
{
  int Id;
  String Name;
  Address Addr;
}

class Address extends JsonObject{
  String city;
  String State;
  String Street;
}

Но моя цель - данные привязки от/до json от/до классов модели; Это решение работает, если вы можете модифицировать классы моделей, в отличие от этого вы должны использовать решение "внешний" для преобразования классов модели;

см. также: Разбор JSON-списка с библиотекой JsonObject в Dart

Ответ 6

Другой пакет, решающий эту проблему, - build_value:

https://github.com/google/built_value.dart

С built_value ваши классы моделей выглядят следующим образом:

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 и класс строителя.

Ответ 7

Я использовал dartson, и он чувствует себя очень легко и знакомо (если вы пришли из Java)

Ответ 9

Еще один способ сериализации/десериализации значений для объектов Json - использовать marshalling пакетов.

Прототип (с использованием формата "yaml")

Customer:
  id: int
  name: String

Автоматически генерировать код (используя утилиту "yaml2podo.dart")

// Generated by tool.

import 'package:marshalling/json_serializer.dart';

final json = JsonSerializer()
  ..addType(() => Customer())
  ..addAccessor('id', (o) => o.id, (o, v) => o.id = v)
  ..addAccessor('name', (o) => o.name, (o, v) => o.name = v)
  ..addProperty<Customer, String>('name')
  ..addProperty<Customer, int>('id');

class Customer {
  String name;
  int id;

  Customer();

  factory Customer.fromJson(Map map) {
    return json.unmarshal<Customer>(map);
  }

  Map<String, dynamic> toJson() {
    return json.marshal(this) as Map<String, dynamic>;
  }
}

Использовать сгенерированный код (автоматическая сериализация/дериализация объектов)

import 'json_objects.dart';

void main() {
  var customer = new Customer()
    ..id = 17
    ..name = "John";
  var jsonCustomer = customer.toJson();
  print(jsonCustomer);
  customer = Customer.fromJson(jsonCustomer);
  print('Id: ${customer.id}');
  print('Name: ${customer.name}');
}

Результат:

{name: John, id: 17} Id: 17 Name: John

Ответ 10

Мне действительно нужно добавлять метод toJson для каждого из моих классов моделей, или есть лучший способ?

На данный момент лучшего способа нет (или они не очень распространены).
Но чтобы не делать это вручную, вы можете использовать генератор кода.

Пример запроса API стека Exchange с использованием вспомогательного сгенерированного кода:

import 'dart:convert';

import 'package:http/http.dart' as _http;

import 'json_objects.dart';

Future<void> main() async {
  var url =
      'https://api.stackexchange.com/2.1/questions/20024298?order=desc&sort=activity&site=stackoverflow';
  var response = await _http.get(url);
  if (response.statusCode == 200) {
    var map = jsonDecode(response.body) as Map;
    var response1 = Response1.fromJson(map);
    for (var item in response1.items) {
      print('Title: ' + item.title);
      print('Tags: ' + item.tags.join(', '));
      print('View count: ' + item.viewCount.toString());
      print('Score: ' + item.score.toString());
      print('Owner: ' + item.owner.displayName);
    }
  }
}

Результат:

Title: Add JSON serializer to every model class?
Tags: json, dart
View count: 13412
Score: 42
Owner: Max

Исходный код используемых объектов данных:

// Generated by 'yaml2podo'
// Version: 0.1.16
// https://pub.dev/packages/yaml2podo

class Response1 {
  final int quotaRemaining;
  final bool hasMore;
  final int quotaMax;
  final List<Response1Items> items;

  Response1({this.quotaRemaining, this.hasMore, this.quotaMax, this.items});

  factory Response1.fromJson(Map map) {
    return Response1(
        quotaRemaining: map['quota_remaining'] as int,
        hasMore: map['has_more'] as bool,
        quotaMax: map['quota_max'] as int,
        items: _toList(map['items'], (e) => Response1Items.fromJson(e as Map)));
  }

  Map<String, dynamic> toJson() {
    var result = <String, dynamic>{};
    result['quota_remaining'] = quotaRemaining;
    result['has_more'] = hasMore;
    result['quota_max'] = quotaMax;
    result['items'] = _fromList(items, (e) => e.toJson());
    return result;
  }
}

class Response1Items {
  final int lastEditDate;
  final int viewCount;
  final int questionId;
  final Response1ItemsOwner owner;
  final String link;
  final int lastActivityDate;
  final bool isAnswered;
  final int score;
  final List<String> tags;
  final int acceptedAnswerId;
  final int creationDate;
  final String title;
  final int answerCount;

  Response1Items(
      {this.lastEditDate,
      this.viewCount,
      this.questionId,
      this.owner,
      this.link,
      this.lastActivityDate,
      this.isAnswered,
      this.score,
      this.tags,
      this.acceptedAnswerId,
      this.creationDate,
      this.title,
      this.answerCount});

  factory Response1Items.fromJson(Map map) {
    return Response1Items(
        lastEditDate: map['last_edit_date'] as int,
        viewCount: map['view_count'] as int,
        questionId: map['question_id'] as int,
        owner: _toObject(
            map['owner'], (e) => Response1ItemsOwner.fromJson(e as Map)),
        link: map['link'] as String,
        lastActivityDate: map['last_activity_date'] as int,
        isAnswered: map['is_answered'] as bool,
        score: map['score'] as int,
        tags: _toList(map['tags'], (e) => e as String),
        acceptedAnswerId: map['accepted_answer_id'] as int,
        creationDate: map['creation_date'] as int,
        title: map['title'] as String,
        answerCount: map['answer_count'] as int);
  }

  Map<String, dynamic> toJson() {
    var result = <String, dynamic>{};
    result['last_edit_date'] = lastEditDate;
    result['view_count'] = viewCount;
    result['question_id'] = questionId;
    result['owner'] = owner?.toJson();
    result['link'] = link;
    result['last_activity_date'] = lastActivityDate;
    result['is_answered'] = isAnswered;
    result['score'] = score;
    result['tags'] = _fromList(tags, (e) => e);
    result['accepted_answer_id'] = acceptedAnswerId;
    result['creation_date'] = creationDate;
    result['title'] = title;
    result['answer_count'] = answerCount;
    return result;
  }
}

class Response1ItemsOwner {
  final String link;
  final String userType;
  final int reputation;
  final int acceptRate;
  final String displayName;
  final int userId;
  final String profileImage;

  Response1ItemsOwner(
      {this.link,
      this.userType,
      this.reputation,
      this.acceptRate,
      this.displayName,
      this.userId,
      this.profileImage});

  factory Response1ItemsOwner.fromJson(Map map) {
    return Response1ItemsOwner(
        link: map['link'] as String,
        userType: map['user_type'] as String,
        reputation: map['reputation'] as int,
        acceptRate: map['accept_rate'] as int,
        displayName: map['display_name'] as String,
        userId: map['user_id'] as int,
        profileImage: map['profile_image'] as String);
  }

  Map<String, dynamic> toJson() {
    var result = <String, dynamic>{};
    result['link'] = link;
    result['user_type'] = userType;
    result['reputation'] = reputation;
    result['accept_rate'] = acceptRate;
    result['display_name'] = displayName;
    result['user_id'] = userId;
    result['profile_image'] = profileImage;
    return result;
  }
}

List _fromList(dynamic data, dynamic Function(dynamic) toJson) {
  if (data == null) {
    return null;
  }
  var result = [];
  for (var element in data) {
    var value;
    if (element != null) {
      value = toJson(element);
    }
    result.add(value);
  }
  return result;
}

List<T> _toList<T>(dynamic data, T Function(dynamic) fromJson) {
  if (data == null) {
    return null;
  }
  var result = <T>[];
  for (var element in data) {
    T value;
    if (element != null) {
      value = fromJson(element);
    }
    result.add(value);
  }
  return result;
}

T _toObject<T>(dynamic data, T Function(dynamic) fromJson) {
  if (data == null) {
    return null;
  }
  return fromJson(data);
}

/*
# Generated by 'resp2yaml'
# Version: 0.1.16
# https://pub.dev/packages/yaml2podo

# bin/response1.json
Response1:
  "items": List<Response1Items>
  "has_more": bool
  "quota_max": int
  "quota_remaining": int

Response1Items:
  "tags": List<String>
  "owner": Response1ItemsOwner
  "is_answered": bool
  "view_count": int
  "accepted_answer_id": int
  "answer_count": int
  "score": int
  "last_activity_date": int
  "creation_date": int
  "last_edit_date": int
  "question_id": int
  "link": String
  "title": String

Response1ItemsOwner:
  "reputation": int
  "user_id": int
  "user_type": String
  "accept_rate": int
  "profile_image": String
  "display_name": String
  "link": String
*/

Ответ 11

Я предпочитаю использовать https://ashamp.github.io/jsonToDartModel/ онлайн-инструмент для самостоятельной записи.

У этого есть особенности ниже:

  • онлайн использование, без плагина
  • поддержка многомерного списка
  • Комплекс поддержки JSON
  • поддержка конвертировать все реквизиты в тип строки
  • предупреждение о пустых реквизитах
  • один файл
  • ключевое слово dart защищено
  • мгновенное преобразование

Я думаю, что это лучше, чем другие инструменты. Добро пожаловать, если у вас есть какие-либо предложения, проблемы или сообщения об ошибках.