Flutter deserialisation using generics error - flutter

I am trying to deserialise and serialise a generic class in flutter using build_runner. The code I have written is as below:
import 'package:json_annotation/json_annotation.dart';
import 'api_response_message_model.dart';
part 'api_response_base_model.g.dart';
#JsonSerializable()
class ApiResponseBaseModel<T> {
#JsonKey(fromJson: _dataFromJson, toJson: _dataToJson)
T data;
ApiResponseMessageModel message;
ApiResponseBaseModel({this.data, this.message});
factory ApiResponseBaseModel.fromJson(Map<String, dynamic> json) =>
_$ApiResponseBaseModelFromJson(json);
T _dataFromJson<T>(Map<String, dynamic> input) =>
input['value'] as T;
Map<String, dynamic> _dataToJson<T>(T input) =>
{'value': input};
}
I am getting the error as below when running command flutter packages pub run build_runner build --delete-conflicting-outputs
Could not generate `fromJson` code for `data`.
None of the provided `TypeHelper` instances support the defined type.
package:premier_app/models/api_response_base_model.dart:10:5
╷
10│ T data;
│ ^^^^
╵
Please help me identify the problem and know the correct solution.
I also did tried the method mentioned here but no luck in removing the error - https://github.com/dart-lang/json_serializable/blob/7b40e9b04805bf921e4cebb87ec4ad7b8e1a2d29/json_serializable/test/generic_files/generic_class.dart#L20-L21

Please move the _dataFromJson & _dataToJson methods outside the scope of class ApiResponseBaseModel<T>.
Example:
import 'package:json_annotation/json_annotation.dart';
import 'api_response_message_model.dart';
part 'api_response_base_model.g.dart';
#JsonSerializable()
class ApiResponseBaseModel<T> {
#JsonKey(fromJson: _dataFromJson, toJson: _dataToJson)
T data;
ApiResponseMessageModel message;
ApiResponseBaseModel({this.data, this.message});
factory ApiResponseBaseModel.fromJson(Map<String, dynamic> json) =>
_$ApiResponseBaseModelFromJson(json);
}
T _dataFromJson<T>(Map<String, dynamic> input) =>
input['value'] as T;
Map<String, dynamic> _dataToJson<T>(T input) =>
{'value': input};
I did try out myself, works!
Generated methods look like:
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'api_response_base_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
ApiResponseBaseModel<T> _$ApiResponseBaseModelFromJson<T>(
Map<String, dynamic> json) {
return ApiResponseBaseModel<T>(
data: _dataFromJson(json['data'] as Map<String, dynamic>),
message: json['message'],
);
}
Map<String, dynamic> _$ApiResponseBaseModelToJson<T>(
ApiResponseBaseModel<T> instance) =>
<String, dynamic>{
'data': _dataToJson(instance.data),
'message': instance.message,
};

Related

How to solve this issue , flutter build working but android studio not working?

flutter build run time android mobile it's working but open android studio then generate or run time below function not working
List<Disp>.from(
json["loads"].map((x) => Disp.fromJson(x)));
model class Disp
class Disp {
String dispatch_id;
String refer_id;
String trailer_id;
String trailer_type;
Disp(this.dispatch_id,
this.refer_id,
this.trailer_id,
this.trailer_type);
Disp.fromJson(Map<String, dynamic> json)
: dispatch_id= json['dispatch_id'],
refer_id= json['refer_id'],
trailer_id= json['trailer_id'],
trailer_type= json['trailer_type'];
Map<String, dynamic> toJson() =>
{
'dispatch_id' : dispatch_id,
'refer_id': refer_id,
'trailer_id': trailer_id,
'trailer_type':trailer_type,
};
}
how to solve this issue? anyone help me. Note : flutter sdk using run build time this function working. but android studio using run or generate apk time this method not working
From what i see the code is
Disp.fromJson(Map<String, dynamic> json)
: dispatch_id= json['dispatch_id'],
refer_id= json['refer_id'],
trailer_id= json['trailer_id'],
trailer_type= json['trailer_type'];
this one should be a factory as below
factory Disp.fromJson(Map<String, dynamic> json)
: dispatch_id= json['dispatch_id'],
refer_id= json['refer_id'],
trailer_id= json['trailer_id'],
trailer_type= json['trailer_type'];
I will just add the complete parsing below :
// To parse this JSON data, do
//
// final disp = dispFromJson(jsonString);
import 'dart:convert';
Disp dispFromJson(String str) => Disp.fromJson(json.decode(str));
String dispToJson(Disp data) => json.encode(data.toJson());
class Disp {
Disp({
this.loads,
});
List<Load> loads;
factory Disp.fromJson(Map<String, dynamic> json) => Disp(
loads: List<Load>.from(json["loads"].map((x) => Load.fromJson(x))),
);
Map<String, dynamic> toJson() => {
"loads": List<dynamic>.from(loads.map((x) => x.toJson())),
};
}
class Load {
Load({
this.dispatchId,
this.referId,
this.trailerId,
this.trailerType,
});
String dispatchId;
String referId;
String trailerId;
String trailerType;
factory Load.fromJson(Map<String, dynamic> json) => Load(
dispatchId: json["dispatch_id"],
referId: json["refer_id"],
trailerId: json["trailer_id"],
trailerType: json["trailer_type"],
);
Map<String, dynamic> toJson() => {
"dispatch_id": dispatchId,
"refer_id": referId,
"trailer_id": trailerId,
"trailer_type": trailerType,
};
}
Use the above json class parser and let me know if it works
final disp = dispFromJson(/*your json data string*/);

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,
};

Generated class from freezed creates duplicate FromJson method

I have a class which I am trying to use with Freezed, Json Serializable, and Hive. After running dart run build_runner build and generating the necessary classes, my compiler gives me the following error:
: Error: Can't use '_$FooBarFromJson' because it is declared more than once.
and
: Error: '_$FooBarFromJson' is already declared in this scope.
part 'foobar.freezed.dart';
part 'foobar.g.dart';
#freezed
#JsonSerializable(explicitToJson: true)
#HiveType(typeId: 0)
class FooBar extends HiveObject with _$FooBar {
factory FooBar({
#HiveField(0) required int baz
}) = _FooBar;
factory FooBar.fromJson(Map<String, dynamic> json) =>
_$FooBarFromJson(json);
}
}
After looking through the generated classes, my foobar.g.dart file contains the following methods:
FooBar _$FooBarFromJson(Map<String, dynamic> json) => FooBar(
baz: json['baz'] as int,
);
Map<String, dynamic> _$FooBarToJson(FooBar instance) =>
<String, dynamic>{
'baz': instance.baz,
};
_$_FooBar _$$_FooBarFromJson(Map<String, dynamic> json) =>
_$_FooBar(
baz: json['baz'] as int,
);
Map<String, dynamic> _$$_FooBarToJson(_$_FooBar instance) =>
<String, dynamic>{
'baz': instance.baz,
};
And my foobar.freezed.dart contains this method:
FooBar _$FooBarFromJson(Map<String, dynamic> json) {
return _FooBar.fromJson(json);
}
I've noticed that other files that get converted only have the methods with the _$$_ prefix in foobar.g.dart, whereas _$FooBarFromJson is being created in both foobar.freezed.dart and foobar.g.dart, which is the cause of the errors. What am I missing here?
According to the following this comment in a freezed issue and as shown in the package readme example, you need to place the #JsonSerializable(explicitToJson: true) inside the class in the following manner:
part 'foobar.freezed.dart';
part 'foobar.g.dart';
#freezed
#HiveType(typeId: 0)
class FooBar extends HiveObject with _$FooBar {
#JsonSerializable(explicitToJson: true) // <~~~ here
factory FooBar({
#HiveField(0) required int baz
}) = _FooBar;
factory FooBar.fromJson(Map<String, dynamic> json) =>
_$FooBarFromJson(json);
}
}
You will notice this will give you the following warning:
The annotation 'JsonSerializable' can only be used on classes
This is a known issue/limitation and the maintainer recommends disabling that specific warning as discussed here.
I believe an alternative approach is to create build.yaml and specify explicitToJson in there but I don't have much knowledge there.

Flutter: json_serializable type '_InternalLinkedHashMap<Object?, Object?>' is not a subtype of type 'Map<String, dynamic>' error

I am linking cloud function to my Flutter model. Result is coming from Cloud-Function
print(result.data);
print(result.data.runtimeType);
GameModel _incomingGame = GameModel.fromJson(result.data);
print(result.data);
{
game: 'GTA',
played: ['ISODATE', ...]
}
print(result.data.runtimeType);
flutter: _InternalLinkedHashMap<String, dynamic>
GameModel
#JsonSerializable()
class GameModel {
String? game;
List? played;
GameModel(
this.game,
this.played,);
factory GameModel.fromJson(Map<String, dynamic> json) =>
_$GameModelFromJson(json);
Map<String, dynamic> toJson() => _$GameModelToJson(this);
}
game_model.g.dart
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'game_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
GameModel _$GameModelFromJson(Map<String, dynamic> json) => GameModel(
json['game'] as String?,
json['played'] as List?,
);
Map<String, dynamic> _$GameModelToJson(GameModel instance) => <String, dynamic>{
'game': instance.game,
'played': instance.played,
};
The incoming data result's runtimeType shows that it is Map<String, dynamic> in the console. However, when I execute GameModel.fromJson(), it causes type '_InternalLinkedHashMap<Object?, Object?>' is not a subtype of type 'Map<String, dynamic>' in type cast.
I really don't get that why this is happening somehow? Even if I do something like below also causes the same type cast error.
GameModel _gameData = result.data;
var game = GameModel.fromJson(_gameData);
Is there any way that I can fix this?
Try this solution:
GameModel.fromJson((result.data as Map<dynamic, dynamic>).cast<String, dynamic>())
Try these solutions
GameModel _incomingGame = GameModel.fromJson(result.data as Map<String, dynamic>);
Or
GameModel _incomingGame = GameModel.fromJson(Map<String, dynamic>.from(result.data));
It also would be better if you check the types before executing type casts
If above solutions doesn't work, add the type when calling cloud functions
final func = FirebaseFunctions.instance.httpsCallable('gameFunction');
final result = await func<Map<String, dynamic>?>();

JsonConverter for unions generated by freezed package in dart

I'm trying to implement toJson/fromJson for a union generated by the freezed package. Let's say we have a class as following:
#freezed
abstract class Result with _$Result {
const factory Result.error(String message) = Error;
const factory Result.success() = Success;
factory Result.fromJson(Map<String, dynamic> json) => _$ResultFromJson(json);
}
Where I want to/fromJson to behave as following:
Result error = Result.error('some error');
expect(error.toJson(), {'type': 'error', 'message': 'some error'});
expect(Result.fromJson({'type': 'error', 'message': 'some error'}), error);
It's stated in the docs that you can use a JsonConverter (fromJSON with multiple classes) but I don't know how to use it properly.
class ResultConverter implements JsonConverter<Result, Map<String, dynamic>> {
const ResultConverter();
#override
Result fromJson(Map<String, dynamic> json) {
if (json == null) {
return null;
}
switch (json['type'] as String) {
case 'success':
return Success.fromJson(json);
case 'error':
return Error.fromJson(json);
default:
throw FallThroughError();
}
}
#override
Map<String, dynamic> toJson(Result object) => object.map(
error: (e) => {'type': 'error', ...e.toJson()},
success: (s) => {'type': 'success', ...s.toJson()},
);
}
fromJson works fine if the factory method calls ResultConverter().fromJson(this) instead of the generated one, but that feels like a workaround and will not work on toJson.
Is it possible to annotate the Result class somehow so that the codegeneration will use the converter?
Yes, this resource has helped me - link to achieve it.
Plus, it works for dedicated named constructor in case of freezed package.
Like this (please note no abstract keyword and private constructor added):
#freezed
class Result with _$Result {
const Result._();
#ResultConverter()
const factory Result.error(String message) = Error;
#ResultConverter()
const factory Result.success() = Success;
factory Result.fromJson(Map<String, dynamic> json) => _$ResultFromJson(json);
}
change toJson(Result object) method to be like that
#override
Map<String, dynamic> toJson(Result object) => object.toJson();
You can just use your converter.
Try this:
final result = ResultConverter().fromJson(json);