wvteijlingen / Spine

A Swift library for working with JSON:API APIs. It supports mapping to custom model classes, fetching, advanced querying, linking and persisting.
MIT License
266 stars 109 forks source link

Serializing Resources for storage in UserDefaults #184

Open Croge32 opened 7 years ago

Croge32 commented 7 years ago

I'm trying to use the Serializer to store objects in UserDefaults so we can load resources up without always doing API requests.

I'm trying to serialize an array of objects that each contain toOne/toMany relationships.

So right now I'm doing:

func persistShoppingList() {
    let serializer = getShoppingListSerializer()
    let shoppingListData = try! serializer.serializeResources(contents, options: [.IncludeID, .IncludeToOne, .IncludeToMany])
    UserDefaults.standard.set(shoppingListData, forKey: "shoppingListArray")
}

However when I deserialize this, I have none of the attributes of the relationship objects inside of contents, only their ids. Here is how I'm deserializing:

  func checkShoppingListPersistence() {
    let serializer = getShoppingListSerializer()

    if let dataFromDefaults = UserDefaults.standard.data(forKey: "shoppingListArray") {
      let contentsJson = try! serializer.deserializeData(dataFromDefaults, mappingTargets: contents).data
      contents = (contentsJson as? [DFPContents])!

      tableView.reloadData()
      tableViewHeight.constant = tableView.contentSize.height
    } else {
      getShoppingList()
    }
  }

I'm not exactly sure what mappingTargets is for. Maybe I'm using that wrong?

How would I go about including the attributes of these relationships when serializing/deserializing?

markst commented 7 years ago

@Croge32 have you an example of the JSON which has been written to UserDefaults?

Croge32 commented 7 years ago

@markst The structure of the object's model is as follows:

class DFPContents: Resource {
  var quantity: NSNumber?
  var sequence: NSNumber?
  var wasConsumed: NSNumber?
  var calories: NSNumber?
  var foodId: NSNumber?
  var servingId: NSNumber?

  var dfpMeal: DFPMeal?
  var food: Food?
  var portion: Portion?

  override class var resourceType: ResourceType {
    return "dfp_contents"
  }

  override class var fields: [Field] {
    return fieldsFromDictionary([
      "quantity": Attribute(),
      "sequence": Attribute(),
      "calories": Attribute(),
      "wasConsumed": BooleanAttribute().serializeAs("was_consumed"),
      "foodId": Attribute().serializeAs("food_id"),
      "servingId": Attribute().serializeAs("serving_id"),
      "dfpMeal": ToOneRelationship(DFPMeal.self).serializeAs("dfp_meal"),
      "food": ToOneRelationship(Food.self),
      "portion": ToOneRelationship(Portion.self),
    ])
  }

...

I'm trying to serialize an array of these DFPContents objects. They are not coming from JSON, which I suspect is part of the problem. Basically when I just serialize the array itself, the children do populate and their ID and types are assigned, but each of their attributes are nil. I see that in the library itself, it looks like responses are able to be parsed into a JSONAPIDocument object where included resources can be specified, which I'm unable to do on my level.

I did however find a workaround. I noticed that if I serialize the object AND it's children all in a Resource array, the parents children are also populated with the children's attributes. So I'm able to pass an items array of type [Resource] into the serializer and then deserialize and pick out just the objects I want. However this is ugly and feels hacky.

Here is how I'm serializing in order to get the child attributes to be non-nil:

func persistShoppingList() {
    let serializer = self.getShoppingListSerializer()

    var items = [Resource]()
    let contents = self.contents as [Resource]
    let foods = self.contents.flatMap {
      return $0.food!
    } as [Resource]
    let portions = self.contents.flatMap {
      return $0.portion!
    } as [Resource]

    items.append(contentsOf: contents)
    items.append(contentsOf: foods)
    items.append(contentsOf: portions)

    let data = try! serializer.serializeResources(items, options: [.IncludeToMany, .IncludeToOne, .IncludeID])
    cache["shoppingListArray"] = data as NSData
    tableView.reloadData()
    tableViewHeight.constant = tableView.contentSize.height
  }

I have also abandoned UserDefaults and am using a caching library now.

Any further insights? I know this probably sounds a bit confusing.

Croge32 commented 7 years ago

@markst Any ideas at all? Any suggestions on how I can achieve data persistence using Spine?

Kamajabu commented 7 years ago

It's most probably not a reason, but have you remembered about adding:

 serializer.registerResource(Food.self)
 serializer.registerResource(Portion.self)

Etc.?

EDIT: Nope, it doesn't help :(