Search code examples
swiftcodableuserdefaults

Unable to decode data Swift


I saved some values in UserDefault and I am trying to get it back but I am unable to decode it as it keeps returning fatalError

func save(dataPass: FeaturedProperties) {
    var saveArraData: [FeaturedProperties] = getAllData()
    saveArraData.append(dataPass)
    let encoder = JSONEncoder()
    if let encoded = try? encoder.encode(saveArraData) {
        defaults.set(encoded, forKey: key)
        defaults.synchronize()
        print("BOUNCEE CC) saveArraData \(encoded)")
    }

}

func getAllData() -> [FeaturedProperties] {
    if defaults.value(forKey: key) == nil {
        let array = [FeaturedProperties]()
        return array
    }

    if let objects = defaults.data(forKey: key) {
        let decoder = JSONDecoder()
        do {
            let objectsDecoded = try decoder.decode([FeaturedProperties].self, from: objects)
            print("BOUNCEE CC) objects \(objectsDecoded)")
            return objectsDecoded
        } catch let error {
            fatalError(error.localizedDescription)
        }
    } else {
        let array = [FeaturedProperties]()
        return array
    }
}

I am able to print the data but could not decode it to FeaturedProperties any help

error returned is

fatal keyNotFound(FeaturedPropertiesCodingKeys(stringValue: "type_id", intValue: nil), Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key FeaturedPropertiesCodingKeys(stringValue: \"type_id\", intValue: nil) (\"type_id\").", underlyingError: nil)))

here is my featuredProperties

struct FeaturedProperties: Codable {
    var id: Int?
    var title: String?
    var desc: String?
    var typeId: Int?
    var subTypeId: Int?
    var addressLine1: String?
    var addressLine2: String?
    var propertyPurposeId: Int?
    var isFeatured: Int?
    var securityDeposit: Int?
    var locationId: Int?
    var currency: String?
    var price: String?
    var pricePerSqm: String?
    var leasableArea: String?
    var plotSize: String?
    var plotPlate: String?
    var floorSize: String?
    var builtUpArea: String?
    var kitchenSize: String?
    var yearBuilt: String?
    var bathRoomNo: String?
    var bedroomNo: String?
    var toiletNo: String?
    var floorTypeId: String?
    var arePetAllowed: String?
    var hasKitchen: Int?
    var dateAvailable: String?
    var status: Int?
    var availabilityStatus: Int?
    var banner: String?
    var premium: Int?
    var parkingOptions: String?
    var landmarks: String?
    var featuresAndAmenities: String?
    var createdBy: Int?
    var user: User?
    var locationDetails: LocationDetails?
    var noViews: Int?
    var noClicks: Int?
    var longitude: String?
    var latitude: String?
    var isSuspended: Int?
    var createdAt: String?

    var updatedAt: String?
    var deletedAt: String?
    var propertyImage: [SearchPropertyImage]?

    enum FeaturedPropertiesCodingKeys: String, CodingKey {
        case id
        case title
        case desc = "description"
        case typeId = "type_id"
        case subTypeId = "sub_type_id"
        case addressLine1 = "address_line1"
        case addressLine2 = "address_line2"
        case propertyPurposeId = "property_purpose_id"
        case isFeatured = "is_featured"
        case securityDeposit = "security_deposit"
        case locationId = "location_id"
        case currency
        case price
        case pricePerSqm = "price_per_sqm"
        case leasableArea = "leasable_area"
        case plotSize = "plot_size"
        case plotPlate = "plot_plate"
        case floorSize = "floor_size"
        case builtUpArea = "built_up_area"
        case kitchenSize = "kitchen_size"
        case yearBuilt = "year_built"
        case bathRoomNo = "bathroom_no"
        case bedroomNo = "bedroom_no"
        case toiletNo = "toilet_no"
        case floorTypeId = "floor_type_id"
        case arePetAllowed = "are_pet_allowed"
        case hasKitchen = "has_kitchen"
        case dateAvailable = "date_available"
        case status
        case availabilityStatus = "availability_status"
        case banner
        case premium
        case parkingOptions
        case landmarks
        case featuresAndAmenities = "features_and_amenities"
        case createdBy = "created_by"
        case locationDetails = "location_details"
        case noViews = "no_views"
        case noClicks = "no_clicks"
        case longitude
        case latitude
        case isSuspended = "is_suspended"
        case createdAt = "created_at"
        case updatedAt = "updated_at"
        case deletedAt = "deleted_at"
        case propertyImage = "property_image"

    }

    init(from decoder: Decoder) throws {

        let container = try decoder.container(keyedBy: FeaturedPropertiesCodingKeys.self)

        id = try container.decode(Int.self, forKey: .id)
        title = try container.decodeIfPresent(String.self, forKey: .title)
        desc = try container.decodeIfPresent(String.self, forKey: .desc)

        typeId = try container.decode(Int.self, forKey: .typeId)
        subTypeId = try container.decode(Int.self, forKey: .subTypeId)

        addressLine1 = try container.decodeIfPresent(String.self, forKey: .addressLine1)
        addressLine2 = try container.decodeIfPresent(String.self, forKey: .addressLine2)
        propertyPurposeId = try container.decode(Int.self, forKey: .propertyPurposeId)
        isFeatured = try container.decode(Int.self, forKey: .isFeatured)
        securityDeposit = try container.decode(Int.self, forKey: .securityDeposit)
        locationId = try container.decode(Int.self, forKey: .locationId)

        currency = try container.decodeIfPresent(String.self, forKey: .currency)
        price = try container.decodeIfPresent(String.self, forKey: .price)
        pricePerSqm = try container.decodeIfPresent(String.self, forKey: .pricePerSqm)
        leasableArea = try container.decodeIfPresent(String.self, forKey: .leasableArea)
        plotSize = try container.decodeIfPresent(String.self, forKey: .plotSize)
        plotPlate = try container.decodeIfPresent(String.self, forKey: .plotPlate)

        floorSize = try container.decodeIfPresent(String.self, forKey: .floorSize)
        builtUpArea = try container.decodeIfPresent(String.self, forKey: .builtUpArea)
        kitchenSize = try container.decodeIfPresent(String.self, forKey: .kitchenSize)
        yearBuilt = try container.decodeIfPresent(String.self, forKey: .yearBuilt)
        bathRoomNo = try container.decodeIfPresent(String.self, forKey: .bathRoomNo)
        bedroomNo = try container.decodeIfPresent(String.self, forKey: .bedroomNo)

        toiletNo = try container.decodeIfPresent(String.self, forKey: .toiletNo)
        floorTypeId = try container.decodeIfPresent(String.self, forKey: .floorTypeId)
        arePetAllowed = try container.decodeIfPresent(String.self, forKey: .arePetAllowed)
        hasKitchen = try container.decode(Int.self, forKey: .hasKitchen)
        dateAvailable = try container.decodeIfPresent(String.self, forKey: .dateAvailable)
        status = try container.decode(Int.self, forKey: .status)

        availabilityStatus = try container.decode(Int.self, forKey: .availabilityStatus)
        banner = try container.decodeIfPresent(String.self, forKey: .banner)
        premium = try container.decode(Int.self, forKey: .premium)
        parkingOptions = try container.decodeIfPresent(String.self, forKey: .parkingOptions)
        landmarks = try container.decodeIfPresent(String.self, forKey: .landmarks)
        featuresAndAmenities = try container.decodeIfPresent(String.self, forKey: .featuresAndAmenities)
        createdBy = try container.decode(Int.self, forKey: .createdBy)
        noViews = try container.decode(Int.self, forKey: .noViews)
        noClicks = try container.decode(Int.self, forKey: .noClicks)

        longitude = try container.decodeIfPresent(String.self, forKey: .longitude)
        latitude = try container.decodeIfPresent(String.self, forKey: .latitude)
        isSuspended = try container.decode(Int.self, forKey: .isSuspended)
        createdAt = try container.decodeIfPresent(String.self, forKey: .createdAt)
        locationDetails = try container.decodeIfPresent(LocationDetails.self, forKey: .locationDetails)


        updatedAt = try container.decodeIfPresent(String.self, forKey: .updatedAt)
        deletedAt = try container.decodeIfPresent(String.self, forKey: .deletedAt)
        propertyImage = try container.decodeIfPresent([SearchPropertyImage].self, forKey: .propertyImage)          
    }
}

Solution

  • You have obvious mistakes in your decoding process. For example typeId is optional but you are using decode instead of decodeIfPresent. To prevent mistakes, let the compiler generate everything for you:

    struct FeaturedProperties: Codable {
        var id: Int?
        var title: String?
        var desc: String?
        var typeId: Int?
        var subTypeId: Int?
        var addressLine1: String?
        var addressLine2: String?
        var propertyPurposeId: Int?
        var isFeatured: Int?
        var securityDeposit: Int?
        var locationId: Int?
        var currency: String?
        var price: String?
        var pricePerSqm: String?
        var leasableArea: String?
        var plotSize: String?
        var plotPlate: String?
        var floorSize: String?
        var builtUpArea: String?
        var kitchenSize: String?
        var yearBuilt: String?
        var bathRoomNo: String?
        var bedroomNo: String?
        var toiletNo: String?
        var floorTypeId: String?
        var arePetAllowed: String?
        var hasKitchen: Int?
        var dateAvailable: String?
        var status: Int?
        var availabilityStatus: Int?
        var banner: String?
        var premium: Int?
        var parkingOptions: String?
        var landmarks: String?
        var featuresAndAmenities: String?
        var createdBy: Int?
        var user: User?
        var locationDetails: LocationDetails?
        var noViews: Int?
        var noClicks: Int?
        var longitude: String?
        var latitude: String?
        var isSuspended: Int?
        var createdAt: String?
    
        var updatedAt: String?
        var deletedAt: String?
        var propertyImage: [SearchPropertyImage]?
    }
    

    Would be enough.

    Of course, your could add CodingKeys for optional override but if your only aim is to translate camel case identifiers to underscores, you can use key decoding strategy on the JSON decoder/encoder, e.g.

    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    

    If you want to use custom coding keys, the enum must be called CodingKeys, e.g.

    private enum CodingKeys: String, CodingKey {
        case typeId = "type_id"
    }