sunshinejr / SwiftyUserDefaults

Modern Swift API for NSUserDefaults
http://radex.io/swift/nsuserdefaults/static
MIT License
4.84k stars 364 forks source link

Failure encoding encodable of type RecentSearches: The operation couldn’t be completed. #272

Closed WestFlow127 closed 1 year ago

WestFlow127 commented 3 years ago

I am trying to store a struct that contains two arrays of objects (UserEntity.Fragment & GenreEntity.Fragment).

public struct RecentSearches: MagmaEntity {
   var id: String
   var genres: [MagmaGenreEntity.Fragment]
   var users: [MagmaUserEntity.Fragment]

   public init(
        id: String = Cuid.generateId(),
        users: [MagmaUserEntity.Fragment] = [],
        genres: [MagmaGenreEntity.Fragment] = []
    ) {
        self.id = id
        self.users = users
        self.genres = genres
       }
    }

Both the items in the array who conform to 'Fragment' protocol conforms to Codable & DefaultsSerializable through inheriting 'MagmaEntity'. As well as the Struct 'RecentSearches' itself, conforms to MagmaEntity.

  protocol MagmaEntity: Codable, DefaultsSerializable, Equatable, IdentifiableType {
      var id: String { get }
  }

  extension MagmaEntity {
      public var identity: String {
          id
      }
  }

The struct initializes the the DefaultKey for "RecentSearches", but when it comes time to add a user to the 'users' array, I get an encoding error:

Screen Shot 2021-02-01 at 4 14 45 PM
"Fatal error: Failure encoding encodable of type RecentSearches: The operation couldn’t be completed. 

(CodableFirebase.DocumentReferenceError error 0.): file SwiftyUserDefaults/Defaults.swift, line 79"

I've tried writing my own init(encoder:) function and defining my own CodingKeys but that didn't work. Does anyone know what might be my problem? Let me know if any more info is needed for clarification.

sunshinejr commented 3 years ago

hey @WestFlow127, it seems like a problem with encoding the type itself - did you try encoding the type using a simple JSONEncoder?

WestFlow127 commented 3 years ago

@sunshinejr I didn't try that. I thought it should work for free with the frameworks. Here is where it fails exactly:

Screen Shot 2021-02-02 at 11 27 15 AM

As you can see in the ss, the JSONEncoder is being used within the swiftyDefaults framework. Did you have a different method in mind?

*Update:

I tried implementing my own encode and decode functions as so which did not work:

public struct RecentSearches: MagmaEntity {
    var id: String
    var genres: [MagmaGenreEntity.Fragment]
    var users: [MagmaUserEntity.Fragment]

    enum CodingKeys: String, CodingKey {
        case genres = "genres"
        case users = "users"
        case id = "id"
    }

    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(String.self, forKey: .id)

        var genresContainer = try container.nestedUnkeyedContainer(forKey: .genres)
        var _genres : [MagmaGenreEntity.Fragment] = []
        while !genresContainer.isAtEnd {
            _genres.append(try genresContainer.decode(MagmaGenreEntity.Fragment.self))
        }
        genres = _genres

        var usersContainer = try container.nestedUnkeyedContainer(forKey: .users)
        var _users : [MagmaUserEntity.Fragment] = []
        while !usersContainer.isAtEnd {
            _users.append(try usersContainer.decode(MagmaUserEntity.Fragment.self))
        }
        users = _users
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)

        var userContainer = container.nestedUnkeyedContainer(forKey: .users)
        for user in users {
            try userContainer.encode(user) // failed here
        }

        var genreContainer = container.nestedUnkeyedContainer(forKey: .genres)
        for genre in genres {
            try genreContainer.encode(genre)
        }
    }
}
sunshinejr commented 3 years ago

@WestFlow127 So the problem seems to be that the JSONEncoder is not able to correctly encode your users. Not sure why is that the case, maybe a bug in Swift? I would advise investigating this more and post a bug at Swift's JIRA (we have reported few regarding generics in SwiftUserDefaults as well...).

Though if you think you are able to encode/decode the type via a different method (e.g. archiving using NSKeyedArchiver), we have few other built-in bridges that could help you with that, or you can even write your own to decode/encode your object from/to Data (since this is how you need to store full objects in plists, unfortunately).