Flutter deserialization of inherited classes with json_serializable - flutter

I have problem trying deserialise inherited class with json_serializable package in Dart/Flutter. Here is the code example:
#JsonSerializable(explicitToJson: true)
class Document {
String id = UniqueKey().toString();
String name='';
List<Component> components=[]; //list of Components
Document({required this.components}):super();
Document.empty();
factory Document.fromJson(Map<String, dynamic> json) =>_$DocumentFromJson(json);
Map<String, dynamic> toJson() => _$DocumentToJson(this);
}
#JsonSerializable()
class Component { //Component Base Class
String id = UniqueKey().toString();
String name='';
Component();
factory Component.fromJson(Map<String, dynamic> json) =>_$ComponentFromJson(json);
Map<String, dynamic> toJson() => _$ComponentToJson(this);
}
#JsonSerializable()
class TextComponent extends Component{ //inherited from Component
String text='';
TextComponent():super();
TextComponent.text({required this.text}):super();
factory TextComponent.fromJson(Map<String, dynamic> json) =>_$TextComponentFromJson(json);
#override Map<String, dynamic> toJson() => _$TextComponentToJson(this);
}
And here is the test:
void main() {
//creating json from object
Document d = Document(components:[TextComponent.text(text: 'text')] );
print(d.components[0].runtimeType); //-type is : TextComponent
var json = d.toJson();
//create object from json
var newDoc = Document.fromJson(json);
print(newDoc.components[0].runtimeType); //-type is : Component which is the base class
}
After deserialising the inherited class is downcasted to base class, but i need the inherited class.

Ok, I found this package which does everything:
https://pub.dev/packages/dart_mappable#custom-mappers

Related

How to use private constructor in json_serializable

I'm using a private constructor in my class, but the code generation fails with
The class Foo has no default constructor.
I'm using latest json_serializable: version i.e. 6.1.5:
#JsonSerializable()
class Foo {
final int count;
Foo._(this.count);
factory Foo.fromJson(Map<String, dynamic> json) => _$Foo._FromJson(json);
}
What am I doing wrong?
You can use #JsonSerializable(constructor: '_') which is introduced in the 4.2.0-dev of the JsonSerializable.
This will allow you to field to specify an alternative constructor to invoke when creating a fromJson helper.
For example:
import 'package:json_annotation/json_annotation.dart';
part 'foo.g.dart';
#JsonSerializable(constructor: '_')
class Foo {
final int count;
Foo._(this.count);
factory Foo.fromJson(Map<String, dynamic> json) => _$FooFromJson(json);
}
Now here, instead of using fromJson like this _$Foo._FromJson(json), use it as _$FooFromJson(json)
Since you declared your constructor with _, there is no default constructor for your class. To fix your issue remove the _.
#JsonSerializable()
class Foo {
final int count;
const Foo(this.count);
factory Foo.fromJson(Map<String, dynamic> json) => _$Foo._FromJson(json);
}
if you need to json_serializable: generate is for you, You have to define the default constructor. but you can mack this trick:
first do this and run build_runner:
#JsonSerializable()
class Foo {
final int count;
Foo(this.count);
factory Foo.fromJson(Map<String, dynamic> json) => _$FooFromJson(json);
}
then change it to this:
#JsonSerializable()
class Foo {
final int count;
Foo._(this.count);
factory Foo.fromJson(Map<String, dynamic> json) => _$FooFromJson(json);
}
and go to _.g.dart and mack call the Private constructor:
Foo _$FooFromJson(Map<String, dynamic> json) => Foo._(
json['count'] as int,
);
Map<String, dynamic> _$FooToJson(Foo instance) => <String, dynamic>{
'count': instance.count,
};

How to delegate a call to a method in json_serialization?

I've two classes Foo and Bar:
#JsonSerializable()
class Foo {
final Bar bar;
Foo(this.bar);
factory Foo.fromJson(Map<String, dynamic> json) => _$FooFromJson(json);
Map<String, dynamic> toJson() => _$FooToJson(this);
}
#JsonSerializable()
class Bar {
final String s;
Bar(this.s);
factory Bar.fromJson(Map<String, dynamic> json) => _$BarFromJson(json);
Map<String, dynamic> toJson() => _$BarToJson(this);
}
Once the code is generated, I use:
final bar = Bar('hi');
final foo = Foo(bar);
print(foo.toJson());
The above line prints
{bar: Instance of 'Bar'}
but I want it to print
{bar: "hi"}
The one way is to override toString method in the Bar class but I don't think it's a good way when using json_serializable for code generation. I think there's trivial property I am missing.
For having custom class inside another class you need to use explicitToJson: true in your target class.
#JsonSerializable(explicitToJson: true)
class Foo {
final Bar bar;
Foo(this.bar);
factory Foo.fromJson(Map<String, dynamic> json) => _$FooFromJson(json);
Map<String, dynamic> toJson() => _$FooToJson(this);
}

json = null in fromJSON method in custom JsonConverter " freezed class with multiple constructors "

I have this class
#freezed
abstract class CartEntity with _$CartEntity {
const factory CartEntity.empty(String status, String message) = _Empty;
const factory CartEntity.notEmpty(int x) = _NotEmpty;
factory CartEntity.fromJson(Map<String, dynamic> json) =>
_$CartEntityFromJson(json);
}
And this converter
class CartEntityConverter
implements JsonConverter<CartEntity, Map<String, dynamic>> {
const CartEntityConverter();
#override
CartEntity fromJson(Map<String, dynamic> json) {
//the problem here
print(json);// null
return _Empty.fromJson(json);
}
#override
Map<String, dynamic> toJson(CartEntity object) {
return object.toJson();
}
}
And this wrapper class
#freezed
abstract class CartEntityWrapper with _$CartEntityWrapper {
const factory CartEntityWrapper(#CartEntityConverter() CartEntity cartEntity) =
CartEntityWrapperData;
factory CartEntityWrapper.fromJson(Map<String, dynamic> json) =>
_$CartEntityWrapperFromJson(json);
}
And iam called
final cartEntity = CartEntityWrapperData.fromJson({'x':'y'});
print(cartEntity);
fromJson method which in CartEntityConverter is always receive null json so what's i made wrong ?
Instead of making yet another converter class that you use directly, you could just add .fromJsonA method in the main class.
It will looks like this one:
#freezed
abstract class CartEntity with _$CartEntity {
const factory CartEntity.empty(String status, String message) = _Empty;
const factory CartEntity.notEmpty(int x) = _NotEmpty;
factory CartEntity.fromJson(Map<String, dynamic> json) =>
_$CartEntityFromJson(json);
factory CartEntity.fromJsonA(Map<String, dynamic> json) {
if (/*condition for .empty constructor*/) {
return _Empty.fromJson(json);
} else if (/*condition for .notEmpty constructor*/) {
return _NotEmpty.fromJson(json);
} else {
throw Exception('Could not determine the constructor for mapping from JSON');
}
}
}
solved by using
final cartEntity = CartEntityConverter().fromJson({'x':'y'});
print(cartEntity);
instead of
final cartEntity = CartEntityWrapperData.fromJson({'x':'y'});
print(cartEntity);
documentation have a lack at this point i tried random stuffs to make it work

How to serialize with Map<String, dynamic> using the package built_value

Does anyone know how can I serialize/deserialize a Map<String, dynamic> instead of String that is the default in the toJson e fromJson methods of the built_value package?
I need to use Firestore and the setData method only accepts a Map for the data.
My current Serializer class has the code below. There's some other plugin or config that I can add to work with the map?
final Serializers serializers =
(_$serializers.toBuilder()..addPlugin(StandardJsonPlugin())).build();
Here are the methods:
String toJson() {
return json.encode(serializers.serializeWith(Comment.serializer, this));
}
static Comment fromJson(String jsonString) {
return serializers.deserializeWith(
Comment.serializer, json.decode(jsonString));
}
Your toJson method should look like:
Map<String, dynamic> toJson() {
return serializers.serializeWith(PlayerModel.serializer, this);
}
That is get rid of the json.encode.
Similarly for fromJson:
static PlayerModel fromJson(Map<String, dynamic> json) {
return serializers.deserializeWith(PlayerModel.serializer, json);
}
Here is a sample PlayerModel I use:
abstract class PlayerModel implements Built<PlayerModel, PlayerModelBuilder> {
#nullable
String get uid;
String get displayName;
PlayerModel._();
factory PlayerModel([void Function(PlayerModelBuilder) updates]) =
_$PlayerModel;
Map<String, dynamic> toJson() {
return serializers.serializeWith(PlayerModel.serializer, this);
}
static PlayerModel fromJson(Map<String, dynamic> json) {
return serializers.deserializeWith(PlayerModel.serializer, json);
}
static Serializer<PlayerModel> get serializer => _$playerModelSerializer;
}

flutter dart JsonSerializable with inherited class

I have the following two classes where one is extending from the other like this :
#JsonSerializable(nullable: true)
class Response {
final String responseCode;
final String responseMessage;
final String errorLog;
Response({this.errorLog, this.responseCode, this.responseMessage});
factory Response.fromJson(Map<String, dynamic> json) =>
_$ResponseFromJson(json);
}
.........................................................
#JsonSerializable(nullable: false)
class Verify extends Response {
Data data;
Verify({
this.data,
});
factory Verify.fromJson(Map<String, dynamic> json) => _$VerifyFromJson(json);
Map<String, dynamic> toJson() => _$VerifyToJson(this);
}
and whenever I'm trying to read response class properties from Verify class, it's always null.
so please how to achieve this?
this one I have solved by passing the parameters to super in verify class constructor like this
#JsonSerializable()
class VerifyResponse extends Response {
Data data;
VerifyResponse({
this.data,
String responseCode,
String responseMessage,
}) : super(responseCode: responseCode, responseMessage: responseMessage);
factory VerifyResponse.fromJson(Map<String, dynamic> json) =>
_$VerifyResponseFromJson(json);
Map<String, dynamic> toJson() => _$VerifyResponseToJson(this);
}
and for the response class it remains the same
#JsonSerializable()
class Response {
final String responseCode;
final String responseMessage;
Response({this.responseCode, this.responseMessage});
factory Response.fromJson(Map<String, dynamic> json) =>
_$ResponseFromJson(json);
}
it's a bit annoying but it's what it's.
You should remove 'final' keyword from Response Class
#JsonSerializable(nullable: true)
class Response {
String responseCode;
String responseMessage;
String errorLog;
Response({this.errorLog, this.responseCode, this.responseMessage});
factory Response.fromJson(Map<String, dynamic> json) =>
_$ResponseFromJson(json);
}
It worked by adding super(); explicitly to the child class's constructor.
#JsonSerializable()
class VerifyResponse extends Response {
Data data;
VerifyResponse({
this.data,
String responseCode,
String responseMessage,
//No need to list all parent class properties
}) : super();
factory VerifyResponse.fromJson(Map<String, dynamic> json) =>
_$VerifyResponseFromJson(json);
Map<String, dynamic> toJson() => _$VerifyResponseToJson(this);
}