Hi,
Consider the following :
import 'package:flutter/material.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter/foundation.dart';
import './converter/widgetList_converter.dart';
part 'testFreezed.freezed.dart';
part 'testFreezed.g.dart';
@freezed
abstract class Test2 with _$Test2 {
const factory Test2({
@ListWidgetModelConv() List<WidgetModel> widgets,
}) = _Test2;
factory Test2.fromJson(Map<String, dynamic> json) => _$Test2FromJson(json);
}
Generate :
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'testFreezed.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$_Test2 _$_$_Test2FromJson(Map<String, dynamic> json) {
return _$_Test2(
widgets: json['widgets'] as List,
);
}
Map<String, dynamic> _$_$_Test2ToJson(_$_Test2 instance) {
final val = <String, dynamic>{};
void writeNotNull(String key, dynamic value) {
if (value != null) {
val[key] = value;
}
}
writeNotNull('widgets', instance.widgets);
return val;
}
Where with the corresponding Json_serialisable :
import 'package:flutter/material.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter/foundation.dart';
import './converter/widgetList_converter.dart';
part 'test.g.dart';
@JsonSerializable(nullable: false)
class Test {
Test({
this.widgets,
});
factory Test.fromJson(Map<String, dynamic> json) => _$TestFromJson(json);
Map<String, dynamic> toJson() => _$TestToJson(this);
@ListWidgetModelConv()
final List<WidgetModel> widgets;
}
Generate :
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'test.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Test _$TestFromJson(Map<String, dynamic> json) {
return Test(
widgets: const ListWidgetModelConv()
.fromJson(json['widgets'] as Map<String, dynamic>),
);
}
Map<String, dynamic> _$TestToJson(Test instance) {
final val = <String, dynamic>{};
void writeNotNull(String key, dynamic value) {
if (value != null) {
val[key] = value;
}
}
writeNotNull('widgets', const ListWidgetModelConv().toJson(instance.widgets));
return val;
}
Could you have a look ?
By the way, replacing
@ListWidgetModelConv() List<WidgetModel> widgets,
by
@JsonKey(fromJson: fromJsons, toJson: toJsons) List<WidgetModel> widgets,
bypass it and produce expected result, but the decorator way should work.
Do we share the same opinion on this ?
Don't hesitate to let me know, if you need any thing else.
What's ListWidgetModelConv?
It's a converter to handle serialisation/deserialisation :
import 'package:json_annotation/json_annotation.dart';
class ListWidgetModelConv
implements JsonConverter<List<WidgetModel>, Map<String, dynamic>> {
const ListWidgetModelConv();
List<WidgetModel> fromJson(Map<String, dynamic> json) {
if (json == null) {
return <WidgetModel>[];
}
List<WidgetModel> result = <WidgetModel>[];
for (String key in json.keys) {
result.add(WidgetModel.fromJson(json[key]));
}
return result;
}
Map<String, dynamic> toJson(List<WidgetModel> object) {
if (object == null) {
return null;
}
Map<String, dynamic> result = <String, dynamic>{};
object.asMap().forEach(
(int key, WidgetModel val) => result[key.toString()] = val.toJson());
return result;
}
}
With type not in List, json_serialisable and Freezed get along just fine.
This converter for exemple, is handle properly:
import 'package:json_annotation/json_annotation.dart';
class DateTimeIntConv implements JsonConverter<DateTime, int> {
const DateTimeIntConv();
@override
DateTime fromJson(int json) =>
json == null ? null : DateTime.fromMillisecondsSinceEpoch(json);
@override
int toJson(DateTime object) =>
object == null ? null : object.millisecondsSinceEpoch;
}
But looking at the freezed part, it seems, the type of the object from the list is smith to dynamic. By doing so, tthe converter don't match anymore. That's my 2 cents analysis.
I just try with
class ListWidgetModelConv
implements JsonConverter<List<dynamic>, Map<String, dynamic>> {
it generate fine with :
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'testFreezed.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$_Test2 _$_$_Test2FromJson(Map<String, dynamic> json) {
return _$_Test2(
widgets: const ListWidgetModelConv()
.fromJson(json['widgets'] as Map<String, dynamic>),
);
}
Map<String, dynamic> _$_$_Test2ToJson(_$_Test2 instance) {
final val = <String, dynamic>{};
void writeNotNull(String key, dynamic value) {
if (value != null) {
val[key] = value;
}
}
writeNotNull('widgets', const ListWidgetModelConv().toJson(instance.widgets));
return val;
}
but my list contain only one type of object, so I presume I should be able to not to downcast it dynamic.
I'm I missing something ?
What's WidgetModel?
It's an object of my project. Don't matter on the issue.
I you prefer, I could replace if with another common object, like Datetime, Color..
Ok, with DateTime it generate : List<DateTime> get datetime;
So I understand why you wanted to know what was WidgetModel. It's another class generated with Freezed.
@freezed
abstract class Container with _$Container {
const factory Container.playlist({
@required
String name,
)
@ListWidgetModelConv()
List<WidgetModel> currentWidgets,
}) = PlayListModel;
const factory EasivioContainer.widget({
@required
String name,
@ListWidgetModelConv()
List<WidgetModel> widgets,
}) = WidgetModel;
factory Container.fromJson(Map<String, dynamic> json) =>
_$ContainerFromJson(json);
}
It sounds like json_serializable is unable to access WidgetsModel for some reason
If you look in the file generated by, is "widgets" a List
inside container_model.freezed.dart found this :
@JsonSerializable()
class _$WidgetModel with DiagnosticableTreeMixin implements WidgetModel {
const _$WidgetModel(
{
@required
this.name,
@ListWidgetModelConv()
this.widgets,
: assert(name != null),
final List<dynamic> widgets;
So still a List but of dynamic instead of WidgetModel.
I wonder, if this is because, the object contain a list on himself, that the generation on the freezed part loose the type. (nope that work, related to Union then?)
Okay, sorry for disturbing you with Json_serialisable...
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'test_model.freezed.dart';
part 'test_model.g.dart';
@freezed
abstract class TestModel with _$TestModel {
const factory TestModel.model({
List<Model> testModel,
}) = Model;
factory TestModel.fromJson(Map<String, dynamic> json) =>
_$TestModelFromJson(json);
}
This simplified exemple is better.
List<Model> is converted to List<dynamic>
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named
part of 'test_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
TestModel _$TestModelFromJson(Map<String, dynamic> json) {
return Model.fromJson(json);
}
class _$TestModelTearOff {
const _$TestModelTearOff();
Model model({List<dynamic> testModel}) {
return Model(
testModel: testModel,
);
}
}
// ignore: unused_element
const $TestModel = _$TestModelTearOff();
mixin _$TestModel {
List<dynamic> get testModel;
Map<String, dynamic> toJson();
$TestModelCopyWith<TestModel> get copyWith;
}
abstract class $TestModelCopyWith<$Res> {
factory $TestModelCopyWith(TestModel value, $Res Function(TestModel) then) =
_$TestModelCopyWithImpl<$Res>;
$Res call({List<dynamic> testModel});
}
class _$TestModelCopyWithImpl<$Res> implements $TestModelCopyWith<$Res> {
_$TestModelCopyWithImpl(this._value, this._then);
final TestModel _value;
// ignore: unused_field
final $Res Function(TestModel) _then;
@override
$Res call({
Object testModel = freezed,
}) {
return _then(_value.copyWith(
testModel:
testModel == freezed ? _value.testModel : testModel as List<dynamic>,
));
}
}
abstract class $ModelCopyWith<$Res> implements $TestModelCopyWith<$Res> {
factory $ModelCopyWith(Model value, $Res Function(Model) then) =
_$ModelCopyWithImpl<$Res>;
@override
$Res call({List<dynamic> testModel});
}
class _$ModelCopyWithImpl<$Res> extends _$TestModelCopyWithImpl<$Res>
implements $ModelCopyWith<$Res> {
_$ModelCopyWithImpl(Model _value, $Res Function(Model) _then)
: super(_value, (v) => _then(v as Model));
@override
Model get _value => super._value as Model;
@override
$Res call({
Object testModel = freezed,
}) {
return _then(Model(
testModel:
testModel == freezed ? _value.testModel : testModel as List<dynamic>,
));
}
}
@JsonSerializable()
class _$Model with DiagnosticableTreeMixin implements Model {
const _$Model({this.testModel});
factory _$Model.fromJson(Map<String, dynamic> json) =>
_$_$ModelFromJson(json);
@override
final List<dynamic> testModel;
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'TestModel.model(testModel: $testModel)';
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty('type', 'TestModel.model'))
..add(DiagnosticsProperty('testModel', testModel));
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other is Model &&
(identical(other.testModel, testModel) ||
const DeepCollectionEquality()
.equals(other.testModel, testModel)));
}
@override
int get hashCode =>
runtimeType.hashCode ^ const DeepCollectionEquality().hash(testModel);
@override
$ModelCopyWith<Model> get copyWith =>
_$ModelCopyWithImpl<Model>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$_$ModelToJson(this);
}
}
abstract class Model implements TestModel {
const factory Model({List<dynamic> testModel}) = _$Model;
factory Model.fromJson(Map<String, dynamic> json) = _$Model.fromJson;
@override
List<dynamic> get testModel;
@override
$ModelCopyWith<Model> get copyWith;
}
Oh, so this is a recursive class
Instead of:
List<Model> testModel,
do
List<TestModel> testModel,
Still not quite, might have oversimplified the exemple :
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'test_model.freezed.dart';
part 'test_model.g.dart';
@freezed
abstract class A with _$A {
const factory A.b({
String name,
List<B> objectB,
}) = B;
const factory A.c({
String name,
List<C> objectC,
}) = C;
}
I need to ensure C list doesn't contains B objects, but need the shared property to access the name.
Does my explanation make sense ?
Why are both B and C under A?
Would it be logical to separate them?
@freezed
abstract class B with _$B {
const factory B({
String name,
List<B> objectB,
}} = _B;
}
@freezed
abstract class C with _$C {
const factory C({
String name,
List<C> objectC,
}} = _C;
}
B and C shared some properties. That's why I wanted to use the Union.
How would you write A then ?
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'test_model.freezed.dart';
part 'test_model.g.dart';
@freezed
abstract class A with _$A {
const factory A.b({
String name,
List<B> objectB,
}) = _B;
const factory A.c({
String name,
List<C> objectC,
}) = _C;
}
@freezed
abstract class B with _$B {
const factory B({
String name,
List<B> objectB,
}} = _B;
}
@freezed
abstract class C with _$C {
const factory C({
String name,
List<C> objectC,
}} = _C;
}
Indeed, work well ! But is there a way not to repeat the entire class B and C in A ? (reason why I put them both in A and remove the private at the beginning to make them public)
You could have:
@freezed
abstract class A with _$A {
const factory A.b(B b) = _Ab;
const factory A.c(C c) = _Ac;
}
@freezed
abstract class B with _$B {
const factory B({
String name,
List<B> objectB,
}} = _B;
}
@freezed
abstract class C with _$C {
const factory C({
String name,
List<C> objectC,
}} = _C;
}
But seems that I loose the shared properties on name field then.
I can't write A.name anymore.
You can add it manually:
@freezed
abstract class A implements _$A {
A._();
factory A.b(B b) = _Ab;
factory A.c(C c) = _Ac;
String get name {
return map(
b: (b) => b.name,
c: (c) => c.name,
);
}
}
Remy, thank you for taking the time.
But it's like redeclaring the model a second time. At least by copy pasting it, not DRY but easier to update.
Maybe that could be an evolution one day.
Until then feels like https://github.com/rrousselGit/freezed/issues/150#issuecomment-617080527 should work cause it takes advantage of the shared properties.
I replaced it manually in the generated file, and it compile, so wonder why it generate with dynamic instead of the real classe type.
Regards.
Because during the step of analyzing the code you wrote to generate Model, then Model doesn't exist yet.
So List<Model> is interpreted as List<dynamic> by Dart's analysis server, and Freezed generated code in consequence
Run into https://github.com/rrousselGit/freezed/issues/142 with https://github.com/rrousselGit/freezed/issues/150#issuecomment-617106811
Will keep an eyes out for it, and give it another try later.
Thanks
@bounty1342 once #204 lands it should help at least with the List
Since #204 is merged, is this still a problem?