realm-dart icon indicating copy to clipboard operation
realm-dart copied to clipboard

JsonSerializable compatibility

Open alevittoria opened this issue 3 years ago • 10 comments

I really appreciate this library but how can I use it in combination with the json_serializable library? I need to serialize/deserialize my objects in a fast way but realm generator seems to be not compatible with json_serializable library.

alevittoria avatar Jun 23 '22 16:06 alevittoria

We also hit this same scenario where our 'web' Flutter app needed json_serializable package and our native macOS/Windows/iOS/Andriod applications use realm-dart package... You cant use both simultaneously... Whilst there are workarounds for our specific use case, it would be good if both packages could work alongside each other.

dotjon0 avatar Jun 23 '22 18:06 dotjon0

Yes it is really an issue and we will think of providing an easy way to serialize/deserialize RealmObjects. There are some workarounds that could currently help you. One of them could be for example if you define a model.dart like:

import 'package:realm_dart/realm.dart';
import 'package:json_annotation/json_annotation.dart';
part 'model.g.dart';

@RealmModel()
@JsonSerializable()
class _Person {
  late String name;
  late int age;
}

extension PersonJ on Person {
  static Person toRealmObject(_Person person) {
    return Person(
      person.name,
      person.age,
    );
  }

  static Person fromJson(Map<String, dynamic> json) => toRealmObject(_$PersonFromJson(json));
  Map<String, dynamic> toJson() => _$PersonToJson(this);
}

Then run the generator using dart run build_runner build or flutter pub run build_runner build And then it could be used as follow:

final json = person.toJson();
Person copyPerson = PersonJ.fromJson(json);

Of course you can find even better workarounds. We will take an attention on the issue. Thank you for the feedback!

desistefanova avatar Jun 24 '22 12:06 desistefanova

Thank you @desistefanova for your suggestion - prefer not to duplicate code, but at the same time this is a workaround that will work. Thank you again and hope you have a good weekend.

dotjon0 avatar Jun 24 '22 19:06 dotjon0

@desistefanova Thank you for sharing the workaround. Were you able to find a solution for nested class like below.

@RealmModel()
@JsonSerializable(
    explicitToJson: true)
class _Patient {

  @PrimaryKey()
  late String email;

  late List<$Telecom> telecom = [];

}

@RealmModel()
@JsonSerializable()
class $Telecom {

  late String value;

}

_$PatientFromJson will have a ref to $Telecom.fromJson(e as Map<String, dynamic>). However $Telecom can't have a factory constructor for realm.

tushkaty avatar Sep 26 '22 10:09 tushkaty

Hi @tushkaty! We still don't have an elegant solution for Json serialization but the good thing is that having methods inside your RealmModel class is not a problem. You are free add methods like fromJson and toJson inside the class $Telecom. Here is the sample:

import 'package:realm_dart/realm.dart';
import 'package:json_annotation/json_annotation.dart';
part 'jsonapp.g.dart';

@RealmModel()
@JsonSerializable(explicitToJson: true)
class _Patient {
  @PrimaryKey()
  late String email;

  late List<$Telecom> telecom = [];
}

@RealmModel()
@JsonSerializable()
class $Telecom {
  late String value;
  static $Telecom fromJson(Map<String, dynamic> json) => _$$TelecomFromJson(json);
  Map<String, dynamic> toJson() => _$$TelecomToJson(this);
}

extension PatientJ on Patient {
  static Patient toRealmObject(_Patient patient) {
    return Patient(patient.email)..telecom.addAll(patient.telecom.map((e) => Telecom(e.value)));
  }

  static Patient fromJson(Map<String, dynamic> json) => toRealmObject(_$PatientFromJson(json));
  Map<String, dynamic> toJson() => _$PatientToJson(this);
}

extension TelecomJ on Telecom {
  static Telecom toRealmObject($Telecom telecom) {
    return Telecom(telecom.value);
  }

  static Telecom fromJson(Map<String, dynamic> json) => toRealmObject(_$$TelecomFromJson(json));
  Map<String, dynamic> toJson() => _$$TelecomToJson(this);
}

void main(List<String> arguments) {
  final patient = Patient("[email protected]")..telecom.add(Telecom("tlc"));
  final json = patient.toJson();
  Patient copyPatientn = PatientJ.fromJson(json);
}

desistefanova avatar Sep 29 '22 08:09 desistefanova

Hi All, I have used the above code, thanks @desistefanova, and managed to combine the findings into a really elegant solution which I will try my best to show here: (I will use the example above for easier comprehension)

In a solution with just 1 RealmModel:

import 'package:realm_dart/realm.dart';
import 'package:json_annotation/json_annotation.dart';

part 'model.g.dart';

@RealmModel()
@JsonSerializable()
class _Person {
  late String name;
  late int age;
  
  Person toRealmObject() {
    return Person(
      name,
      age,
    );
  }
}

extension PersonJsonHandler on Person {
  static Person fromJson(Map<String, dynamic> json) =>
      _$PersonFromJson(json).toRealmObject();
      
        Map<String, dynamic> toJson() => _$PersonToJson(this);
}

Now, where it gets really nice is with embedded models. By setting up our classes as above, only the top level model would need to implement the extension method and the following embedded objects can be written using the following approach: (For simplicity I have omitted embedding the model as a list and just referencing the model as an individual model)

import 'package:realm_dart/realm.dart';
import 'package:json_annotation/json_annotation.dart';

part 'model.g.dart';

@RealmModel()
@JsonSerializable()
class _Person {
  late String name;
  late $Telecom? telecom; //Remember, embedded models need to be nullable
  
  Person toRealmObject() {
    return Person(
      name,
      telecom: telecom?.toRealmObject(),
    );
  }
}

extension PersonJsonHandler on Person {
  static Person fromJson(Map<String, dynamic> json) =>
      _$PersonFromJson(json).toRealmObject(); //This is where the magic happens, so no need to create the extension on lower level classes
      
        Map<String, dynamic> toJson() => _$PersonToJson(this);
}

And then $Telecom class looks as follows:

import 'package:realm/realm.dart';
import 'package:json_annotation/json_annotation.dart';

part 'telecom.g.dart';

@RealmModel()
@JsonSerializable()
class $Telecom {
  late String value;

  Telecom toRealmObject() {
    return Telecom(
     value,
    );
  }

//This is mandatory as it will be referenced in the auto-generated class for the upper level model (in this case, person.g.dart)
  static Telecom fromJson(Map<String, dynamic> json) =>
      _$$TelecomFromJson(json).toRealmObject();
      
   //If you require the toJson function you can add it as follows
   static Map<String, dynamic> toJson($Telecom telecom) =>
     _$$TelecomToJson(telecom);
}

then in main:

void main(List<String> arguments) {
  Patient copyPatientn = PatientJ.fromJson(json);
}

I have not included the full solution for toJson as I did not need it in my solution but it should be pretty easy to infer the solution from my example.

Hope this helps anybody looking for a more simplistic solution for their projects with models embedded on many layers.

RashadAkoodieRabbit avatar Aug 01 '23 07:08 RashadAkoodieRabbit

Hi @RashadAkoodieRabbit! Thank you very much for providing this example. I hope it will help people to solve the issue with the serialisation. Meanwhile, @nielsenko is working on a solution for providing serialization/deserialization to EJSON for complex types like ObjectId, Decimal128 that we support in the RealmModel. Once it is ready we will avoid writing additional code for toJson/fromJson.

desistefanova avatar Aug 01 '23 08:08 desistefanova

Hi @RashadAkoodieRabbit! Thank you very much for providing this example. I hope it will help people to solve the issue with the serialisation. Meanwhile, @nielsenko is working on a solution for providing serialization/deserialization to EJSON for complex types like ObjectId, Decimal128 that we support in the RealmModel. Once it is ready we will avoid writing additional code for toJson/fromJson.

Glad to share! Please could you update this issue when the fix goes live so we migrate our project to use the latest :) thank you

RashadAkoodieRabbit avatar Aug 02 '23 16:08 RashadAkoodieRabbit

Any update yet on this? Also on using UInt8List with json?

RashadAkoodieRabbit avatar Sep 20 '23 09:09 RashadAkoodieRabbit

@RashadAkoodieRabbit As you may know, there is a PR (sitting on top of another) to support EJSON, but currently it is in a bit of a flux.

I'm no longer working for MongoDB, so I won't be able help bring it over the finish line.

nielsenko avatar Sep 20 '23 11:09 nielsenko

any updates on this issue?

kingwill101 avatar Mar 15 '24 00:03 kingwill101

Yes, the linked PRs are now merged and will be released with our next release. We're putting the finishing touches there and hope to have it published shortly.

nirinchev avatar Mar 15 '24 00:03 nirinchev

@nirinchev will installation work as normal? I tried using the current alpha and i ran into the following error


Unhandled exception:
Exception: Error parsing package pubspec at Directory: '/home/.../.pub-cache/hosted/pub.dev'. Error FileSystemException: Cannot open file, path = '/home/kingwill101/.pub-cache/hosted/pub.dev/realm-2.0.0-alpha.5/' (OS Error: Is a directory, errno = 21)
#0      InstallCommand.parsePubspec (package:realm_dart/src/cli/install/install_command.dart:119:7)
<asynchronous suspension>
#1      InstallCommand.run (package:realm_dart/src/cli/install/install_command.dart:143:26)
<asynchronous suspension>
#2      CommandRunner.runCommand (package:args/command_runner.dart:212:13)
<asynchronous suspension>
CMake Error at cmake_install.cmake:148 (file):
  file INSTALL cannot find
  "/home/....../linux/flutter/ephemeral/.plugin_symlinks/realm/linux/binary/linux/librealm_dart.so":
  No such file or directory.

downgrading back to the current stable works

dart run realm install -t linux                                                                                                
Building package executable... 
Built realm:realm.
Downloading Realm binaries for [email protected] to /tmp/realm-binary-RZIKXQ/linux.tar.gz
Extracting Realm binaries to /home/...../binary/linux
extracting librealm_dart.so

Archive /tmp/realm-binary-RZIKXQ/linux.tar.gz extracted to /home/.../../..../binary/linux
Realm install command finished.

kingwill101 avatar Mar 15 '24 21:03 kingwill101

The alpha is still quite raw - we wanted to publish it to run some tests, but wouldn't say it's ready for consumption yet.

That being said, we do appreciate the enthusiasm and the early report ❤️ I'll take a look next week and try to get it resolved before the next drop.

nirinchev avatar Mar 15 '24 22:03 nirinchev

Create a PR with a fix here: https://github.com/realm/realm-dart/pull/1576 - I expect it to be released with the next alpha release.

nirinchev avatar Mar 18 '24 00:03 nirinchev