Skip to content
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

Deserialize list of different types with same base type from JSON? #441

Closed
BerndWessels opened this issue Jun 12, 2018 · 8 comments
Closed
Assignees
Labels

Comments

@BerndWessels
Copy link

Hi
I have a pretty common use case but can't figure out how to do it with BuiltList.

I have a list of item and section elements in my firestore collection:

item type

{
  "order": 4,
  "title": "item 4",
  "color": "red"
}

section type

{
  "order": 17,
  "name:" "section 2"
}

so in firestore my collection has many items and many sections.

Now if flutter I get this collection like this:

firestore.collection("menus/il86e4qdVawW6ixia1XU/elements").snapshots().listen((QuerySnapshot snapshot) {
}

Now I want a BuiltList from that.

So ideally I would have a
MenuElement abstract class
and
MenuItem class and MenuSection class that both implement MenuElement
and then a BuiltList<MenuElement>

But I just can't figure out how to achieve that because

  1. The values in the BuiltList should be BuiltValue and I can't tell them to implement MenuElement.
  2. I have no idea how to deserialize the snapshot which is JSON with elements of the 2 different types.

Is this at all possible?

It is a pretty common thing for lists to have sections and items and I hope there is a way to achieve this.

@dave26199
Copy link

I'm not quite sure I follow. Which of MenuItem, MenuElement and MenuSection are classes you own, and which belong to firebase?

You can use built_value serialization with partly built_value classes and partly not; you need to write a serializer class for each, like

https://github.com/google/built_value.dart/blob/master/built_value/lib/src/uri_serializer.dart

...but if none of the classes are built_value classes, it's not clear why this is better than just writing your own deserializer entirely :)

@BerndWessels
Copy link
Author

@dave26199 Some more explanation:

This is pretty much the result of querying the firebase elements collection:

[
  {"order": 0, "name": "section header 0"}
  {"order": 1, "title": "item name 1" , "color": "red"}
  {"order": 2, "title": "item name 2" , "color": "green"}
  {"order": 3, "name": "section header 1"}
  {"order": 4, "title": "item name 3" , "color": "blue"}
  {"order": 5, "title": "item name 4" , "color": "yellow"}
]

It is basically a list of section and item elements.

Now I want to deserialize this list received from Firestore into a BuiltList<Element> .

Ideally there is a BuiltValue<Section> and a BuildValue<Item> class which both implement an abstract BuiltValue<Element> class. But I am not sure if this is possible and correct.

Later when iterating through that BuiltList<Element> in a List widget for example, I want to be able to ask each element if it is a section or an item.

I have no idea how to do this kind of deserialization of multiple types into a BuiltList or if this is even possible.

Currently I use this code to deserialize snapshots into BuiltLists:

BuiltList<T> documentsToBuiltList<T>(
    List<DocumentSnapshot> documents, Serializer<T> serializer) {
  return new BuiltList<T>(documents.map<T>((document) {
    T builtDoc = standardSerializers.deserializeWith<T>(serializer, document.data);
    return builtDoc;
  }).toList());
}

But I cannot figure out how this would look like for lists with different types in it?

@davidmorgan
Copy link
Collaborator

One way would be to figure out the type based on the values that are present. You'd need to do this for every item in the list, and call deserializeWith for each one, then return a list of the deserialized results.

var serializer;
if (document.data.containsKey('title')) {
  serializer = Section.serializer;
} else if (document.data.containsKey('color')) {
  serializer = Item.serializer;
} else {
  throw 'No serializer for ${data}.'
}

If you can tell which type is needed based on the fields, this should be a reasonable approach. If you can't, that's a problem :)

@BerndWessels
Copy link
Author

@davidmorgan yes, I can figure out what type each element is.

The only thing I am still confused about is the actual implementation of the BuiltValues for Element, Section and Item.

Would you be so kind and give me a very basic example for those so that I can have a BuitList<Element> that contains BuiltValue<Section> and BuiltValue<Item> elements?

Somehow I am confused about the implements Element and abstract class Element in combination with BuiltValue.

@davidmorgan
Copy link
Collaborator

Are Element, Section and Item classes you own, or are they part of firebase? If they're part of firebase, could you point me to the API docs? Thanks.

@BerndWessels
Copy link
Author

@davidmorgan All these classes would be my own classes. Firestore just received List<DocumentSnapshot> documents and I have to then deserialize those into my own classes.

@davidmorgan
Copy link
Collaborator

Ah, okay. Then, I think you'll want something like this:

abstract class Element {
  int get order;
}

abstract class Section implements Built<Section, SectionBuilder>, Element {
  static Serializer<Section> get serializer => _$sectionSerializer;
  
  int get order;
  String get name;
  
  Section._();
  factory Section([updates(SectionBuilder b)]) = _$Section;
}

abstract class Item implements Built<Item, ItemBuilder>, Element {
  static Serializer<Item> get serializer => _$itemSerializer;
  
  int get order;
  String get title;
  String get color;

  Item._();
  factory Item([updates(ItemBuilder b)]) = _$Item;
}

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

No branches or pull requests

4 participants
@BerndWessels @davidmorgan @dave26199 and others