Skip to content

Unable to cast nested custom object #237

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
manhhavu opened this issue Aug 2, 2019 · 4 comments
Closed

Unable to cast nested custom object #237

manhhavu opened this issue Aug 2, 2019 · 4 comments

Comments

@manhhavu
Copy link
Contributor

manhhavu commented Aug 2, 2019

Hi,

It seems that when a class' field is custom object, the SDK is currently unable to cast the correct type to the nested object with an error 'type 'ParseObject' is not a subtype of type '''.

import 'package:parse_server_sdk/parse_server_sdk.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:test_api/test_api.dart';

class TestMother extends ParseObject implements ParseCloneable {
  static const String _keyTableName = 'TestMother';
  static const String _keyChild = 'child';

  TestMother() : super(_keyTableName);

  TestMother.clone() : this();

  @override
  clone(Map map) => TestMother.clone()..fromJson(map);

  TestChild get child => get<TestChild>(_keyChild);
  set child(TestChild value) => set<TestChild>(_keyChild, value);
}

class TestChild extends ParseObject implements ParseCloneable {
  static const String _keyTableName = 'TestChild';
  static const String _keyName = 'name';

  TestChild() : super(_keyTableName);

  TestChild.clone() : this();

  String get name => get<String>(_keyName);
  set name(String name) => set<String>(_keyName, name);

  @override
  clone(Map map) => TestMother.clone()..fromJson(map);
}

void main() {
  const endpoint = '<endpoint>';
  const TIMEOUT = const Timeout(const Duration(minutes: 2));

  setUp(() async {
    // Preferences needed by Parse SDK
    SharedPreferences.setMockInitialValues({});

    await Parse().initialize(
      '<client_id>',
      '$endpoint/parse/',
      autoSendSessionId: true,
      debug: true,
    );
  });

  test('Save and delete object with Parse', () async {
    final child = TestChild()..name = "Baby";

    final mother = TestMother()..child = child;

    await mother.save();

    final response = await TestMother().getObject(mother.objectId);
    final saved = response.result as TestMother;

    // This line throw error "type 'ParseObject' is not a subtype of type 'TestChild'"
    expect(saved.child, isNotNull);
  }, timeout: TIMEOUT);
}

Version: 1.0.22 (same problem with older versions : 1.0.21, 1.0.19)

I don't know if it is expected and is there any workaround for that? I have a lot of nested custom objects to update so using the raw method set(key, value) is very cumbersome.

Thanks for your help,

@gorillatapstudio
Copy link

gorillatapstudio commented Aug 7, 2019

I msged before and it seems it is expected. My work around is have my MotherObject mixed the Cache and use a utility for get. (not sure whey github automatically strikes some texts).

MotherClass implements HasCache {
TestChild get child => Utils.getParseObject(this, "child", () => TestChild());
}
abstract class HasCache {
var cache = Map();
}

typedef S Constructor();

static T getParseObject(
ParseObject object, String key, Constructor constructor) {

var cache = (object as HasCache)?.cache;
if (cache != null && cache[key] != null) {
  return cache[key];
}

var value = object.get<dynamic>(key);
if (value == null) return null;
final dynamic json = const JsonDecoder().convert(value.toString());
if (json == null) return null;

T obj = constructor().fromJson(json);

cache[key] = obj;
return obj;

}

@manhhavu
Copy link
Contributor Author

manhhavu commented Aug 8, 2019

Thank @gorillatapstudio for the workaround.

@phillwiggins : What do you think about of making this workaround as default in the lib? Or you have another idea to handle this case more cleanly?

@gorillatapstudio
Copy link

the string conversion in my workaround is not efficient. Hope @phillwiggins can support get(key) directly in the sdk and convert directly.

@phillwiggins
Copy link
Member

Hey @manhhavu & @gorillatapstudio

Unfortunately, I'm doing something similar. In my classes, I do something like this in my Parse class.

@override Day fromJson(Map<String, dynamic> objectData) { super.fromJson(objectData); if (objectData.containsKey(keyOwner)) { owner = User.clone().fromJson(objectData[keyOwner]); } return this; }

The issue is that Flutter doesn't support reflection, and based on best practices, when we actually convert our JSON to a returnable object, we define that the return type is ParseObject. Even using generics we haven't found a better approach to this.

The approach above works well too, but my approach is the reason you will see that ParseObjects implement the cloneable method.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants