I'm working for a generic CRUD Repository but I have some problems with the serialization of my classes. For solving this issue I have create a factory on my base Entity:
const factories = {
Product: Product.fromJson,
};
abstract class Entity {
[...]
factory Entity.fromJson({
required Type type,
required Map json,
}) {
final factory = factories[type];
if (factory == null) {
throw Exception('Type Error: ($type) not found on factories');
}
return factory(json: json);
}
Map get json => {'id': id};
}
And in my repository abraction I just use this factory:
abstract class IDioCrudRepository<E extends Entity> extends DioRepository {
IDioCrudRepository({required super.baseUrl});
FutureOr<E> get(String id) async {
final res = await dio.get('$baseUrl/$id');
return Entity.fromJson(
type: E,
json: res.data as Map,
) as E;
}
[...]
}
Dou you know guys if this is a bad practice?
I would suggest you , at least, to make a Builder class and to move all that code far from Entity. In this manner entity class would not have any responsibility on how to build itself from a key and a json map.
const factories = {
Product: Product.fromJson,
};
class EntityBuilder {
static Entity fromJson({
required Type type,
required Map json,
}) {
final factory = factories[type];
if (factory == null) {
throw Exception('Type Error: ($type) not found on factories');
}
return factory(json: json);
}
}
abstract class Entity {
[...]
Map get json => {'id': id};
}
abstract class IDioCrudRepository<E extends Entity> extends DioRepository {
IDioCrudRepository({required super.baseUrl});
FutureOr<E> get(String id) async {
final res = await dio.get('$baseUrl/$id');
return EntityBuilder.fromJson(
type: E,
json: res.data as Map,
) as E;
}
[...]
}
Related
Is it possible to somehow create an abstract factory method? Maybe what I'm trying to do is possible to implement differently?
abstract class ApiModel {
// Error: A function body must be provided.
factory ApiModel.fromJson(Map<String, dynamic> json);
}
class User extends ApiModel {
final int id;
final String name;
User({required this.id, required this.name});
#override
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'] as int,
name: json['name'] as String,
);
}
}
class ApiResponse<Model extends ApiModel> {
final List<Model> results;
ApiResponse({required this.results});
factory ApiResponse.fromJson(Map<String, dynamic> json) {
return ApiResponse(results: (json['results'] as List).map((item) => Model.fromJson(item)).toList());
}
}
I solved it like this:
factory ApiResponse.fromJson(Map<String, dynamic> json, Model Function(dynamic) mapper) {
return ApiResponse(
info: Info.fromJson(json['info']),
results: (json['results'] as List).map(mapper).toList(),
);
}
I'm new to Dart 2. I want a class to have a property. It's a reference of other class. it's not an instance but class itself. In TypeScript, it's possible to write as below. Is there a same way in Dart 2?
class Item { }
class ItemList {
itemClass: typeof Item;
}
const itemList = new ItemList();
itemList.itemClass = Item;
UPDATED:
I added some more context. The following is minimal sample code. I want to delegate a role of instantiation to super class.
class RecordBase {
id = Math.random();
toJson() {
return { "id": this.id };
};
}
class DbBase {
recordClass: typeof RecordBase;
create() {
const record = new this.recordClass();
const json = record.toJson();
console.log(json);
}
}
class CategoryRecord extends RecordBase {
toJson() {
return { "category": "xxxx", ...super.toJson() };
};
}
class TagRecord extends RecordBase {
toJson() {
return { "tag": "yyyy", ...super.toJson() };
};
}
class CategoryDb extends DbBase {
recordClass = CategoryRecord;
}
class TagDb extends DbBase {
recordClass = TagRecord;
}
const categoryDb = new CategoryDb();
categoryDb.create();
const tagDb = new TagDb();
tagDb.create();
I have tried to make you sample code into Dart. As I told before, you cannot get a reference to a class and call the constructor on runtime based on this reference.
But you can make a reference to a method which constructs the object of you class.
import 'dart:math';
class RecordBase {
static final Random _rnd = Random();
final int id = _rnd.nextInt(100000);
Map<String, dynamic> toJson() => <String, dynamic>{'id': id};
}
abstract class DbBase {
final RecordBase Function() getRecordClass;
RecordBase record;
Map<String, dynamic> json;
DbBase(this.getRecordClass);
void create() {
record = getRecordClass();
json = record.toJson();
print(json);
}
}
class CategoryRecord extends RecordBase {
#override
Map<String, dynamic> toJson() {
return <String, dynamic>{'category': 'xxxx', ...super.toJson()};
}
}
class TagRecord extends RecordBase {
#override
Map<String, dynamic> toJson() {
return <String, dynamic>{'tag': 'yyyy', ...super.toJson()};
}
}
class CategoryDb extends DbBase {
CategoryDb() : super(() => CategoryRecord());
}
class TagDb extends DbBase {
TagDb() : super(() => TagRecord());
}
void main() {
final categoryDb = CategoryDb();
categoryDb.create(); // {category: xxxx, id: 42369}
final tagDb = TagDb();
tagDb.create(); // {tag: yyyy, id: 97809}
}
I am not really sure if the create() method should be seen as a method or a constructor. So I choose to make it a method to be closer to your code.
So I have a class like Question like bellow:
#JsonSerializable()
class Question {
String id;
String content;
Question({this.id, this.content});
factory Question.fromJson(Map<String, dynamic> json) =>
_$QuestionFromJson(json);
Map<String, dynamic> toJson() => _$QuestionToJson(this);
}
Please keep in mind that those _$QuestionFromJson and _$QuestionToJson comes from this library https://pub.dev/packages/json_serializable
Say I have many class like that which have a fromJson factory and a toJson method. I want to create a base class that contains those 2 method. A base model is easy for toJson as bellow:
abstract class BaseModel {
Map<String, dynamic> toJson();
}
But what about the factory method, I have no idea how to declare them then override it simply like:
#override
factory Question.fromJson(Map<String, dynamic> json) =>
_$QuestionFromJson(json);
EDIT:
My idea of using this is because I want to create a converter utility that I only need to pass in the class of the result like Converter.listFromJson<MyClass>(jsonString). For now, the helper is:
static List<T> listFromJson<T>(jsonString, Function mappingFunction) {
return myJsonMap.map(mappingFunction).cast<T>().toList();
}
so I have to map each item by passing the map function every time I use this helper method:
Converter.listFromJson<Question>(
jsonMap, (item) => Question.fromJson(item));
There'are a few more class that needs to be convert to the list like this. I want to reuse the method without the (item) => Question.fromJson(item) method part. That's why I want to create a base class that have the factory fromJson method so that I can use it in the converter
return myJsonMap.map((item) => BaseModel.fromJson(item)).cast<T>().toList();
then I just simply call
Converter.listFromJson<Question>(jsonMap);
Thank you for your time.
i don't know if i got you correctly, that's what i understood from your question
abstract class BaseModel{
BaseModel();
BaseModel.fromJson(Map<String,dynamic> json);
}
class Question extends BaseModel{
final String id;
final String name;
Question({this.id,this.name}): super();
#override
factory Question.fromJson(Map<String, dynamic> json) {
return Question(
id: json['id'],
name: json['name']
);
}
}
void main(){
Map<String,dynamic> json = {'id': "dsajdas",'name': 'test'};
Question question = Question.fromJson(json);
print('question: ${question.id}');
}
That was my approach but you can't do such a thing. There is a workaround by declaring .fromJson(json) in a variable. Look at my sample codes, hope you can get an idea.
class Categories {
final String id;
String name;
String image;
Categories({this.id, this.name, this.image});
Categories.fromJson(dynamic json)
: id = json['id'],
name = json['name'],
image = json['image'];
}
class CategoriesModel extends AppModel<Categories> {
List<Categories> list = [];
Function fromJson = (dynamic json) => Categories.fromJson(json);
}
class AppModel<T> {
List<T> list = [];
Function fromJson;
List<T> getList() {
if (this.list.isNotEmpty) return this.list;
List<dynamic> list = GetStorage().read('tableName');
list.forEach((data) {
this.list.add(fromJson(data));
});
return this.list;
}
}
I'm wondering how can I parse a JSON data to a base class, because I'm trying to write, but get Unhandled Exception: type 'Future' is not a subtype of type 'WeatherModel' in type cast.
Here is my code:
abstract class BaseModel {
fromJson(Map<String, dynamic> json);
Map<String, dynamic> toJson();
}
import 'BaseModel.dart';
class WeatherModel extends BaseModel {
String success;
Result result;
Records records;
WeatherModel({this.success, this.result, this.records});
#override
fromJson(Map<String, dynamic> json) {
//...
}
#override
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
//... parse to Json
return data;
}
}
}
import 'dart:async';
import 'package:dio/dio.dart';
import 'package:flightinfo/model/BaseModel.dart';
class HttpUtils {
static Future<BaseModel> get(String url, Map<String, dynamic> params, BaseModel baseModel) async {
try {
print("url:$url,params:$params");
Response response = await Dio().get(url, queryParameters: params);
if (response != null && response.statusCode == 200) {
baseModel.fromJson(response.data);
print(baseModel);
return baseModel;
}
print(response);
} catch (exception) {
print(exception);
}
return null;
}
}
class WeatherRequest {
Future<WeatherModel> get() async {
return HttpUtils.get(_url, _params,new WeatherModel());
}
}
I think HttpUtils.get direct to BaseModel for extensibility. This is very common in Java, but in Dart I get a exception in WeatherRequest in below line:
return HttpUtils.get(_url, _params,new WeatherModel());
Unhandled Exception: type 'Future' is not a subtype of type 'WeatherModel' in type cast
In Dart, how to take care of this cast?
The error might be occurring because get() method of WeatherRequest returns a Future to WeatherModel but you have returned a Future<BaseModel> instead.
Try adding await to resolve Future converting it to BaseModel before returning.
return (await HttpUtils.get(_url, _params,new WeatherModel()) );
Also, as per this SO answer:
You can cast an object to its parent class, but you can't cast an object to any of its child class.
So implicit casting of BaseModel to WeatherModel may not work as expected. Instead, you can add a helper method to BaseModel like BaseModel.toWeatherModel and return a WeatherModel
abstract class BaseModel {
toWeatherModel(){
//...
}
}
Or modify get method of WeatherRequest to return Future<BaseModel>.
class WeatherRequest {
Future<BaseModel> get() async {
return (await HttpUtils.get(_url, _params,new WeatherModel()));
}
}
I have a request endpoint using in web api:
public HttpResponseMessage Create(IList<SlideContent> l)
{
...
}
i send the parameter as a json and web api serializes it to IList
SlideContent is:
public abstract class SlideItem
{
...
}
and i have specialised classes
public class TitleSlideItem : SlideItem
{
...
}
public class ParagraphSlideItem : SlideItem
{
...
}
just like that i can't call the Create function, because i get a
missingmethodexception: cannot create abstract class
so i can't deserialize the json parameter. if i remove the abstract modifier, then i don't have specialized objects, every object's type will be SlideContent.
I even put annotations in the json, but it doesn't help either.
If i'm not wrong, the i would have to write a custom binder for the abstract class, but how can i do that?
Sincerely,
Zoli
One possibility is to substitute the built-in JSON serializer with a custom formatter using JSON.NET as shown in the following blog post.
public class JsonNetFormatter : MediaTypeFormatter
{
private JsonSerializerSettings _jsonSerializerSettings;
public JsonNetFormatter(JsonSerializerSettings jsonSerializerSettings)
{
_jsonSerializerSettings = jsonSerializerSettings ?? new JsonSerializerSettings();
// Fill out the mediatype and encoding we support
SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
Encoding = new UTF8Encoding(false, true);
}
protected override bool CanReadType(Type type)
{
if (type == typeof(IKeyValueModel))
{
return false;
}
return true;
}
protected override bool CanWriteType(Type type)
{
return true;
}
protected override Task<object> OnReadFromStreamAsync(Type type, Stream stream, HttpContentHeaders contentHeaders, FormatterContext formatterContext)
{
// Create a serializer
JsonSerializer serializer = JsonSerializer.Create(_jsonSerializerSettings);
// Create task reading the content
return Task.Factory.StartNew(() =>
{
using (StreamReader streamReader = new StreamReader(stream, Encoding))
{
using (JsonTextReader jsonTextReader = new JsonTextReader(streamReader))
{
return serializer.Deserialize(jsonTextReader, type);
}
}
});
}
protected override Task OnWriteToStreamAsync(Type type, object value, Stream stream, HttpContentHeaders contentHeaders, FormatterContext formatterContext, TransportContext transportContext)
{
// Create a serializer
JsonSerializer serializer = JsonSerializer.Create(_jsonSerializerSettings);
// Create task writing the serialized content
return Task.Factory.StartNew(() =>
{
using (JsonTextWriter jsonTextWriter = new JsonTextWriter(new StreamWriter(stream, Encoding)) { CloseOutput = false })
{
serializer.Serialize(jsonTextWriter, value);
jsonTextWriter.Flush();
}
});
}
}
then in Application_Start when registering the formatter you could configure the serializer to use type information in the JSON:
var formatters = GlobalConfiguration.Configuration.Formatters;
formatters.Remove(formatters.XmlFormatter);
formatters.Remove(formatters.JsonFormatter);
var serializerSettings = new JsonSerializerSettings();
serializerSettings.TypeNameHandling = TypeNameHandling.Objects;
serializerSettings.Converters.Add(new IsoDateTimeConverter());
formatters.Add(new JsonNetFormatter(serializerSettings));
and then you could POST the following JSON:
[
{
"$type":"AppName.Models.TitleSlideItem, AppName",
"Id":1,
"Title":"some title" // this is a specific property of the TitleSlideItemclass
},
{
"$type":"AppName.Models.ParagraphSlideItem, AppName",
"Id":2,
"Paragraph":"some paragraph" // this is a specific property of the ParagraphSlideItem class
}
]
which will be successfully deserialized inside this action:
public HttpResponseMessage Post(IList<SlideItem> l)
{
...
}
JsonFx creates custom binders. For example take a look at:
https://code.google.com/p/jsonfx/source/browse/trunk/JsonFx/JsonFx.MvcTemplate/Global.asax.cs
Look at the RegisterBinders() function in that file and the source to the binders or just install JsonFx and create a project using their MVC template and see how they do it or just use their library if that would work for you. That's what I would do.