Deserialization of abstract types in Dart - dart

Please consider the following code segment, declaration of one base class and two extenders.
abstract class Animal {
final bool flag;
Animal(this.flag);
}
class Cat extends Animal {
final String name;
Cat(this.name, bool flag) : super(flag);
Cat.fromJson(Map<String, dynamic> json)
: name = json['name'],
super(json['flag']);
}
class Fish extends Animal {
final int memory;
Fish(this.memory, bool flag) : super(flag);
Fish.fromJson(Map<String, dynamic> json)
: memory = json['memory'],
super(json['flag']);
}
What is the best practice of serializing and deserializing the following list?
List<Animal> animals = [Cat('Nancy', true), Fish(10, false)];
So, how to serialize the type information for each class in order to construct the appropriate instance upon deserialization?
The serialization code is ommited for the sake of simplicity, however it would return a map of the member fields. The code above is only an example - there is no meaning in the structure or the data.
EDIT: I would prefer not using any library or code generator, but using the internal capabilities of the language.
Any suggestion is appretiated, thank you.

I had to figure that out, so I provide my naive solution for the problem. However, if anyone gives a better approach, I will mark that as an answer.
My naive solution is to decorate every JSON result with a 'type' field that contains the name of the subclass. Then I could create a factory for the base type that constructs the appropriate instance depending on that 'type' field. The full code:
abstract class Animal {
final bool flag;
Animal(this.flag);
factory Animal.fromJson(Map<String, dynamic> json) {
switch (json['type']) {
case 'cat':
return Cat.fromJson(json);
case 'fish':
return Fish.fromJson(json);
default:
throw 'Invalid animal type';
}
}
}
class Cat extends Animal {
final String name;
Cat(this.name, bool flag) : super(flag);
Cat.fromJson(Map<String, dynamic> json)
: name = json['name'],
super(json['flag'].toString() == 'true');
Map<String, dynamic> toJson() {
return {'type': 'cat', 'name': name, 'flag': flag.toString()};
}
}
class Fish extends Animal {
final int memory;
Fish(this.memory, bool flag) : super(flag);
Fish.fromJson(Map<String, dynamic> json)
: memory = json['memory'],
super(json['flag'].toString() == 'true');
Map<String, dynamic> toJson() {
return {'type': 'fish', 'memory': memory, 'flag': flag.toString()};
}
Obviously, during deserialization the list items should be mapped using the factory method as follows:
var data = jsonDecode("[{'type':'cat','name':'Nancy','flag':'true'},{'type':'fish','memory':10,'flag':'false'}]");
List<Animal> list = data.map((e) => Animal.fromJson(e)).cast<Animal>().toList();

Related

How to initialize a static variable and set various value for it?

In java, if you want to initialize a static variable once, you can write code in Static Initialization Block just like this:
abstract class Dummy {
static final Map<String, object> cache = new HashMap();
static {
cache.add('foo', new Foo());
cache.add('bar', new Bar());
}
}
Here I want to ask if there have a similar way in Dart? What is the best coding practice in Dart programming?
abstract class Dummy {
static final Map<String, dynamic> cache = <String, dynamic>{};
}
Well, there is no static initialization block in dart, but there are some other approaches you could take here.
Firstly, if all you want to do is add a few items to a map, you can just use map literal syntax:
abstract class Dummy {
static final Map<String, dynamic> cache = <String, dynamic>{
'foo': Foo(),
'bar': Bar(),
};
}
Also if you just want to initialize a static value by calling a few methods on it, you can use cascade notation .., which for this specific example would look like this:
abstract class Dummy {
static final Map<String, dynamic> cache = <String, dynamic>{}
..['foo'] = Foo()
..['bar'] = Bar();
}
The above is using cascade to call the []= operator on the map instance, but you can call any method on the map instance using cascade. For example, I could also call the remove method:
abstract class Dummy {
static final Map<String, dynamic> cache = <String, dynamic>{}
..['foo'] = Foo()
..['bar'] = Bar()
..remove('foo');
}

Is there an idiomatic way to parse JSON into objects with a shared base class but differing data shapes?

I'm brand new to Dart so I've just been getting my feet wet implementing an SDK for an existing API,
but I'm running into some issues figuring out how to structure things to cut down on boilerplate when
I'm parsing the responses from the server.
Basically, all responses from the API look like the following:
{
"data": {
...
},
"error": {
...
}
}
where either data or error is present, but never both. Fairly standard stuff. The shape of the
error isn't all that interesting since it's the same for every response, but the data object is
different for every response. So far I've been able to get something "working" by declaring a base class
that handles the error and passing that up through super (I should note I'm using the
json_serializer package):
class Response {
Response({this.error});
Error? error;
}
#JsonSerializable(createToJson: false)
class ExampleResponse extends Response {
ExampleResponse({
this.data,
Error? error,
}) : super(error: error);
Map<String, dynamic>? data;
factory ExampleResponse.fromJson(Map<String, dynamic> json) =>
_$ExampleResponseFromJson(json);
}
This works but I lose all of the type information for the data field. I'm not sure how to approach this
from here that doesn't involve yet another class for the data shape:
#JsonSerializable()
class Thing {
Thing(this.name);
String name;
factory Thing.fromJson(Map<String, dynamic> json) => _$ThingFromJson(json);
String toString() => 'Thing{name: $name}';
}
#JsonSerializable()
class ExampleResponseData {
ExampleResponseData(this.thing);
Thing thing;
factory ExampleResponseData.fromJson(Map<String, dynamic> json) =>
_$ExampleResponseDataFromJson(json);
String toString() => 'ExampleResponseData{thing: $thing}';
}
#JsonSerializable(createToJson: false)
class ExampleResponse extends Response {
ExampleResponse({
this.data,
Error? error,
}) : super(error: error);
ExampleResponseData? data;
factory ExampleResponse.fromJson(Map<String, dynamic> json) =>
_$ExampleResponseFromJson(json);
String toString() => 'ExampleResponse{error: $error, data: $data}';
}
In Go I would just define an inner struct for occassions where the type has no value outside of JSON
parsing like:
type ExampleResponse struct {
Error *Error `json:"error"`
Data *struct {
Thing Thing `json:"thing"`
} `json:"thing"`
}
but I'm a lot rustier with OOP in general and on even more unfamiliar ground with Dart. Is there a
better way to handle this that doesn't end up with twice as many types as there are responses?
You can find solution based on built_value. Idea is to use generic for data type. Built value can handle such cases, though you will have a little bit of boilerplate. Below you can find example:
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';
import 'package:built_value/standard_json_plugin.dart';
part 'built_value_example.g.dart';
void main(List<String> arguments) {
final resultAJson = endpointA();
final resultA = serializers.deserialize(resultAJson, specifiedType: FullType(Response, [FullType(DataA)]));
print(resultA);
final resultBJson = endpointB();
final resultB = serializers.deserialize(resultBJson, specifiedType: FullType(Response, [FullType(DataB)]));
print(resultB);
}
Map<String, dynamic> endpointA() {
final data = DataA((b) => b..dataAField = 7);
final response = Response<DataA>((b) => b..data = data);
return serializers.serialize(response, specifiedType: FullType(Response, [FullType(DataA)])) as Map<String, dynamic>;
}
Map<String, dynamic> endpointB() {
final data = DataB((b) => b..dataBField = 'data b value');
final response = Response<DataB>((b) => b..data = data..error = 'data b error');
return serializers.serialize(response, specifiedType: FullType(Response, [FullType(DataB)])) as Map<String, dynamic>;
}
abstract class Response<DATA_TYPE> implements Built<Response<DATA_TYPE>, ResponseBuilder<DATA_TYPE>> {
Response._();
factory Response([Function(ResponseBuilder<DATA_TYPE> b) updates]) = _$Response<DATA_TYPE>;
static Serializer<Response> get serializer => _$responseSerializer;
DATA_TYPE get data;
String? get error;
}
abstract class DataA implements Built<DataA, DataABuilder> {
DataA._();
factory DataA([Function(DataABuilder b) updates]) = _$DataA;
static Serializer<DataA> get serializer => _$dataASerializer;
int get dataAField;
}
abstract class DataB implements Built<DataB, DataBBuilder> {
DataB._();
factory DataB([Function(DataBBuilder b) updates]) = _$DataB;
static Serializer<DataB> get serializer => _$dataBSerializer;
String get dataBField;
}
#SerializersFor([
DataA,
DataB,
Response,
])
final Serializers serializers = (_$serializers.toBuilder()
..addPlugin(StandardJsonPlugin())
..addBuilderFactory(FullType(Response, [FullType(DataA)]), () => ResponseBuilder<DataA>())
..addBuilderFactory(FullType(Response, [FullType(DataB)]), () => ResponseBuilder<DataB>()))
.build();

Can I use generics in Dart like this?

I want to parse http response to Dart object,so I defined a abstract class
BaseBean:
abstract class BaseBean{
BaseBean.fromJson(Map<String, dynamic> json);
Map<String, dynamic> toJson();
}
And I used it in a function:
Future<ResultData<T>> netFetch<T extends BaseBean>(){
......
return new ResultData(T.fromJson(), result, code);
}
but T.fromJson() has an error:
The method 'fromJson' isn't defined for the class 'Type'
So,can I use generics in Dart like this?Is there a better way to solve this problem?
Yes, of course, it is possible, but only with a workaround:
T unmarshal<T>(Map map, {Type type}) {
if (type == null) {
type = T;
}
switch (type) {
case Order:
return Order.fromJson(map) as T;
case OrderItem:
return OrderItem.fromJson(map) as T;
case Product:
return Product.fromJson(map) as T;
default:
throw StateError('Unable to unmarshal value of type \'$type\'');
}
}
var order = unmarshal<Order>(data);
//
var product = unmarshal(data, type: Product) as Product;
//
var type = <String, Type>{};
types['OrderItem'] = OrderItem;
// ...
var type = types['OrderItem'];
var orderItem = unmarshal(data, type: type);
I used the same approach and I came to this solution.
I created a Map<Type, BaseBean Function(dynamic)> decoders.
It looks like
final decoders = {
MyClass: (data) => MyClass.fromJson(data),
};
and I get the function in this way
final MyClass myData = decoders[T]!(data);
You can also create a typedef for the BaseBean Function(dynamic) like this
typedef Decoder = BaseBean Function(dynamic);
So the Map<Type, BaseBean Function(dynamic)> will became Map<Type, Decoder>.

Flutter _TypeError (type 'List<dynamic>' is not a subtype of type 'Map<String, dynamic>')

I am new to flutter and getting type error. I am trying to use json automated serializations.
AFTER DOING SOME TWEAKS HERE IS HOW IT LOOKS LIKE
Here is how I am trying to get the data from api
Future getMyProduct() async {
final res = await http.get('url');
final data = json.decode(res.body);
BaseResponse req = new BaseResponse.fromJson(data);
return req;
}
My BaseResponse class looks like this
import 'package:dynamicapp/model/model.dart';
import 'package:json_annotation/json_annotation.dart';
part 'response.g.dart';
#JsonSerializable()
class BaseResponse extends Object {
final int id;
final int sellingPrice;
final int totalStock;
final String productName;
final String productDesc;
final List<Image> images;
BaseResponse(this.id, this.sellingPrice, this.totalStock, this.productName,
this.productDesc, this.images);
factory BaseResponse.fromJson(Map<String, dynamic> json) => _$BaseResponseFromJson(json);
Map<String, dynamic> toJson() => _$BaseResponseToJson(this);
}
#JsonSerializable()
class Image extends Object {
final int id;
final String image;
// final int product_id;
Image(this.id, this.image);
factory Image.fromJson(Map<String, dynamic> json) => _$ImageFromJson(json);
Map<String, dynamic> toJson() => _$ImageToJson(this);
}
Could anyone please help me with this. I am stuck here. Have been trying different methods but none working. Thank you.
It looks like data is a List<dynamic>, then data.map(someFunc).toList() will take each element of data pass it to someFunc and form it back into a list of the return type of someFunc (which you will presumably want to be BaseResponse). Which tells you that someFunc needs to be a function that takes dynamic and returns BaseResponse.
You'd want to write something like this:
final data = json.decode(res.body);
List<BaseResponse> responses =
data.map((j) => BaseResponse.fromJson(j)).toList();

How to use json_annotation with a Dart Extended ListBase class

So I have a #JsonSerializable class that's doing the job without an issue.
#JsonSerializable()
class BaseValue {
String id;
var value;
DateTime valueDate;
BaseValue({
this.id,
this.value,
this.valueDate
});
factory BaseValue.fromJson(Map<String, dynamic> json) => _$BaseValueFromJson(json);
Map<String, dynamic> toJson() => _$BaseValueToJson(this);
}
No I want to have the same fromJson toJson enabled for an extended ListBase class but can't find how to implement this.
class BaseValues<BaseValue> extends ListBase<BaseValue> {
final List<BaseValue> l = [];
BaseValues();
void set length(newLength) => l.length = newLength;
int get length => l.length;
BaseValue operator [](int index) => l[index];
void operator []=(int index, BaseValue value) => l[index] = value;
}
Maybe I need to use something else instead of ListBase.
Thanks in advance for any help provided.
It's possible, but it's not super easy. You need to create a custom TypeHelper to support your class.
Then, if you want to use pub run build_runner... you'll need to create a custom builder that instantiates an instance of JsonSerializableGenerator and include an instance of your TypeHelper.
There is a pending commit that will give you some more freedom here - https://github.com/dart-lang/json_serializable/commit/4f19d468bf05eed3e4a8ebc27244fc3b8d411dc9 – but you'll still have to handle the possible generic type args of BaseValues.

Resources