Search code examples
iosjsonswiftrealm

Error mapping Optional JSON to RealmSwift @Persisted


Issue: There are two different objects within the array I am attempting to map and store into a RealmSwift database. However this parameter (@Persisted var folderItems: List) which is optional and only appears for the 'folder' type causes an error. All other data is mapped correctly.

Error: The data couldn’t be read because it is missing.

Would anyone be able to resolve this so that the 'items' gets mapped when present?

Developer Note: @Persisted var folderItems: List? Is not valid, results in error: Generic struct 'Persisted' requires that 'List' conform to '_PersistableInsideOptional'

JSON:

{
  "data": [
    {
      "id": "8e467944-00b8-11ef-ba84-0edd649d5685",
      "thumbnail": "https://sample.com",
      "position": "z",
      "updated_at": 1713797931000,
      "translations": [
        {
          "title": "This Carousel has a folder with a bunch of media",
          "id": "en_US"
        }
      ],
      "type": "carousel",
      "items": [
        {
          "id": "9fadb738-00b8-11ef-9c82-0edd649d5685",
          "thumbnail": "https://sample.com",
          "position": "aa",
          "updated_at": 1713797842000,
          "translations": [
            {
              "title": "Folder that has a bunch of media items",
              "id": "en_US"
            }
          ],
          "type": "folder",
          "parent_id": "8e467944-00b8-11ef-ba84-0edd649d5685",
          "items": [
            {
              "id": "2782c91c-fc24-11ee-ac56-0edd649d5685",
              "type": "video",
              "position": "gggggggggh",
              "updated_at": 1713797886000,
              "translations": [
                {
                  "title": "Deadpool & Wolverine",
                  "description": null,
                  "thumbnail": "https://sample.com",
                  "external_resource_id": "Jjsy0jnrTvWo7N9NQlFQ_Deadpool  Wolverine  Official Teaser  In Theaters July 26.mp4",
                  "id": "en_US",
                  "original": "https://sample.com"
                }
              ]
            }
          ]
        },
        {
          "id": "2100f7d8-fb5e-11ee-a915-0edd649d5685",
          "type": "photo",
          "position": "p",
          "updated_at": 1713797939000,
          "translations": [
            {
              "title": "Chevy Convertible",
              "description": null,
              "thumbnail": "https://sample.com",
              "external_resource_id": "mPrEUiKRXOHdcVJXVBwX_chevy.jpeg",
              "id": "en_US",
              "original": "https://sample.com",
              "large": "https://sample.com"
            }
          ]
        }
      ]
    }
  ]
}

Classes


public class Carousel: SynchronizedBase, Codable {

    @Persisted(primaryKey: true) var id = UUID().uuidString

    @Persisted var carouselId: String
    @Persisted var thumbnail: String?
    @Persisted var titleTranslations: List<Translation>
    @Persisted var position: String?
    @Persisted var lastUpdatedAt: Int?
    @Persisted var type: String?
    @Persisted var items: List<CarouselMedia>
    
    enum CodingKeys: String, CodingKey {
        case carouselId = "id"
        case thumbnail
        case titleTranslations = "translations"
        case position
        case lastUpdatedAt = "updated_at"
        case type
        case items

    }

}

public class CarouselMedia: SynchronizedBase, Codable {

    @Persisted(primaryKey: true) var id = UUID().uuidString

    @Persisted var mediaId: String
    @Persisted var type: String?
    @Persisted var lastUpdatedAt: Int?
    @Persisted var position: String?
    @Persisted var translations: List<Translation>
     
    //Folder Specific
    @Persisted var parentCarouselId: String?
    @Persisted var thumbnail: String?
    @Persisted var folderItems: List<CarouselMedia>

    enum CodingKeys: String, CodingKey {
       
        case mediaId = "id"
        case type
        case thumbnail
        case translations
        case lastUpdatedAt = "updated_at"
        case parentCarouselId = "parent_id"
        case position
        case folderItems = "items"

    }
}

public class Translation: Object, Codable {

    @Persisted(primaryKey: true) var id = UUID().uuidString

    @Persisted var localizationId: String?//en_US
    @Persisted var title: String?
    @Persisted var desc: String?
    @Persisted var thumbnail: String?
    @Persisted var externalResourceId: String?

    @Persisted var original: String?
    @Persisted var large: String? //Still needed?

    @Persisted var needsDownload: Bool = true
    
    enum CodingKeys: String, CodingKey {
        case localizationId = "id"
        case title
        case desc = "description"
        case thumbnail
        case externalResourceId = "external_resource_id"

        case original
        case large

    }
}

Solution

  • Added to the CarouselMedia class:

        public required init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
    
            mediaId = try container.decode(String.self, forKey: .mediaId)
            type = try? container.decode(String.self, forKey: .type)
            lastUpdatedAt = try? container.decode(Int.self, forKey: .lastUpdatedAt)
            translations = try container.decode(List<Translation>.self, forKey: .translations)
            parentCarouselId = try? container.decode(String.self, forKey: .parentCarouselId)
            thumbnail = try? container.decode(String.self, forKey: .thumbnail)
            position = try? container.decode(String.self, forKey: .position)
    
            let items = try? container.decode(List<CarouselMedia>.self, forKey: .folderItems)
            folderItems = items ?? List<CarouselMedia>()
        }
    
        override required init() {
            super.init()
        }
    

    Credit: Durgesh Trivedi