Mobx.dart: How to mobx work with json_serializable

Created on 12 Apr 2019  Â·  19Comments  Â·  Source: mobxjs/mobx.dart

Hi,

I'm using json_serializable and generated code described in official Flutter docs: https://flutter.dev/docs/development/data-and-backend/json#serializing-json-using-code-generation-libraries.

How can I use json_serializable and mobx generated code together?

Most helpful comment

@lynn, how about making a little bit simpler by removing extra class assignment?

@JsonSerializable()
class UserInfo extends UserInfoBase with _$UserInfo {
  UserInfo();
  factory UserInfo.fromJson(Map<String, dynamic> json) => _$UserInfoFromJson(json);
  Map<String, dynamic> toJson() => _$UserInfoToJson(this);
}

abstract class UserInfoBase implements Store {
  @observable String userName;
  // etc.
}

All 19 comments

As far as I can tell, there's no way to combine code generators like that. You'll have to write the fromJSON constructors on your deserializable stores manually. There needs to be some way to namespace the generated files.

Is there any way to use mobx without abstract classes?

This works for us:

@JsonSerializable()
class UserInfo extends _ObservableUserInfo {
  UserInfo();
  factory UserInfo.fromJson(Map<String, dynamic> json) => _$UserInfoFromJson(json);
  Map<String, dynamic> toJson() => _$UserInfoToJson(this);
}

class _ObservableUserInfo = UserInfoBase with _$_ObservableUserInfo;

abstract class UserInfoBase implements Store {
  @observable String userName;
  // etc.
}

The code generated by json_serializable in your_file.g.dart is

UserInfo _$UserInfoFromJson(Map<String, dynamic> json) {
  return UserInfo()
    ..userName = json['userName'] as String;
}

which will conveniently call the setter on the _$_ObservableUserInfo mixin made by mobx-codegen:

  @override
  set userName(String value) {
    _$userNameAtom.context.checkIfStateModificationsAreAllowed(_$userNameAtom);
    super.userName = value;
    _$userNameAtom.reportChanged();
  }

Thanks @lynn. I'll give it a try.

Closing this issue for now

@lynn, how about making a little bit simpler by removing extra class assignment?

@JsonSerializable()
class UserInfo extends UserInfoBase with _$UserInfo {
  UserInfo();
  factory UserInfo.fromJson(Map<String, dynamic> json) => _$UserInfoFromJson(json);
  Map<String, dynamic> toJson() => _$UserInfoToJson(this);
}

abstract class UserInfoBase implements Store {
  @observable String userName;
  // etc.
}

Unfortunately adding JsonSerializable and Generics to the mix it just doesn't work
I'm aware of https://github.com/mobxjs/mobx.dart/pull/119

Here's something i can't make it to work
If I remove Generics it works just fine

`
@JsonSerializable()
class Group Group({int id, String lines, T type}) : super(id: id, lines: lines, type: type);

factory Group.fromJson(Map json) => _$GroupFromJson(json);
Map toJson() => _$GroupToJson(this);
}

abstract class GroupBase @observable
int id;

@JsonKey(fromJson: _typeFromJson, toJson: _typeToJson)
@observable
T type;

@observable
String lines;
GroupBase({this.id, this.lines, this.type});
}`

Has another made this work with ObservableList?

[SEVERE] json_serializable:json_serializable on lib/todo/todo_list.dart:
Error running JsonSerializableGenerator
Could not generate `fromJson` code for `todos`.
None of the provided `TypeHelper` instances support the defined type.
package:mobx_fun/todo/todo_list.dart:19:24
   â•·
19 │   ObservableList<Todo> todos = ObservableList<Todo>();
   │                        ^^^^^
   ╵

For the ObservableList<T>, here is what I've done:

  • Add the @JsonSerializable annotation to the TodoList and Todo classes
  • Added a type-helper for ObservableList<T>. This helps in fromJson/toJson conversions.
@JsonSerializable()
class TodoList = _TodoList with _$TodoList;

abstract class _TodoList with Store {
  @observable
  @_ObservableListConverter()
  ObservableList<Todo> todos = ObservableList<Todo>();

  // ...
}

I've added this to the todos example. You can see the full code here.

I love that you added this code! It makes it far easier to do JSON serialization for mobx-based stores. And the codes on the main site drive it home. Thank you!

Some feedback:

  1. Why the use of the leading underscore? Isn't _ObservableListConverter a type you want people to use and not to make it private (even if only in name)?

  2. The name _ObservableListConverter is fairly vague -- convert into what? Could you make it more clear that it supports @JsonSerializable as part of the name, e.g. @ObservableListJsonConverter?

  3. Do I have to make a separate annotation at all? Could it be an argument to @observerable instead that serves as a signal to the codegen to generate the support for JSON? Or even better, if I've got @JsonSerializable as an attribute on the containing class, can the code generator just generate the JSON support for the @observerable collection?

  4. Is there similar support for Maps and Sets?

Thanks Chris!

  1. I couldn't find a way to make this converter generic for general purpose use. Hence the need for a private local converter with a specific type argument for T. Perhaps Kevin Moore can help?
  2. Yup, agree with the more explicit name. Have made the change and pushed to master.
  3. I'll have to keep the json-serialization separate, else I'll need a dependency on json_annotations package. Here too, I'll need to dig in to see how I can add the support directly inside the the Observable collections. Might have to dig in more to understand the interface contract for json_annotations package.
  4. Similar to point 1. Don't know how to make this generally useful with Type arguments

re: #1 -- are you generating the annotation type? If so, what if I have multiple lists? Won't the name collide?

re: #4 -- can you use the same technique for Maps and Sets that you use for Lists?

Ya, in the example there is only one of those so it works fine. If we have multiple, we could genericize the converter itself.

class _ObservableListJsonConverter<T>
    implements JsonConverter<ObservableList<T>, List<Map<String, dynamic>>> { ... }

Do note that the type argument T will have to implement an interface that has fromJson and toJson converters.

Ya the same technique works for maps and sets. A set is serialized to an array (List) and a map is just an object (Map<String, dynamic>).

Would you show how to call fromJson using todo_list? I keep getting the error like "type 'List' is not a subtype of type 'List>' in type cast".
It would be helpful if you make some more examples with json_serializable.

I like the good old json_serializable and not this json_mapper, this works as expected with json_serializable

@JsonSerializable()
class Company extends _Company with _$Company {
  Company({List<Employee> employees}) {
    if (employees != null) this.employees = ObservableList.of(employees);
  }
  factory Company.fromJson(Map<String, dynamic> json) => _$CompanyFromJson(json);
  Map<String, dynamic> toJson() => _$CompanyToJson(this);
}

abstract class _Company with Store {
  ObservableList<Employee> employees;
}

@JsonSerializable()
class Employee extends _Employee with _$Employee {
  Employee();
  factory Employee.fromJson(Map<String, dynamic> json) => _$EmployeeFromJson(json);
  Map<String, dynamic> toJson() => _$EmployeeToJson(this);
}

abstract class _Employee with Store {
  @observable
  String name;
}

I've also added a guide on JSON Serialization on the website.

Hi, seems like your site is down. Where can I read this guide?
I'd like to use json_serializable too, instead of this weird json_mapper. Guys put a lot of code pieces here. What is a correct way, after all?
You added the @_ObservableListConverter - have you added same attributes for ObservableMap/Set?

I managed to serialize MobX Store too, according to the last @ali80 answer.
There's a little error: the code above this.employees.addAll(employees); will not work, because a new ObservableList() is not created yet. The correct way is this.employees = ObservableList()..addAll(employees), or simpler, this.employees = ObservableList.of(employees)

image

I managed to serialize MobX Store too, according to the last @ali80 answer.
There's a little error: the code above this.employees.addAll(employees); will not work, because new ObservableMap() is not created yet. The correct way is this.employees = ObservableList()..addAll(employees), or simpler, this.employees = ObservableList.of(employees)

image

yes, there was a bug, now fixed, thanks for mentioning it.

Was this page helpful?
0 / 5 - 0 ratings