0

I am trying to make an enum ParentalSalary conform to Codable so that it can be saved in UserDefaults. I have tried to follow this guide: https://blog.natanrolnik.me/codable-enums-associated-values and tweaked it according to my program.

Furthermore, I've gotten stuck in the trouble shooting as I can't find any solutions / explanations when searching for the error message.

Below is the code for the enum I'm trying to make Codable:

enum ParentalSalary {
    case noSalary
    case upTo(percentage: Int)
}

extension ParentalSalary : Codable {

    private enum CodingKeys: String, CodingKey {
        case typeOfSalary
        case percentage
    }

    private enum TypeOfSalaryCodingKeys : String, Codable {
        case noSalary
        case upTo
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        switch self {
            case .noSalary:
                try container.encode(TypeOfSalaryCodingKeys.noSalary, forKey: .typeOfSalary)
            case .upTo(let percentage):
                try container.encode(TypeOfSalaryCodingKeys.upTo, forKey: .typeOfSalary)
                try container.encode(percentage, forKey: .percentage)
        }
    }

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

        switch typeOfSalary {
            case .noSalary:
                self = .noSalary
            case .upTo:
                let percentage = try container.decode(Int.self, forKey: .percentage)
                self = .upTo(percentage: percentage)
        }
    }
}

Furthermore, I use the ParentalSalary data type in a separate struct (Profile: Identifiable, Codable) which I in turn make an ObservableObject and save to UserDefaults using a JSONEncoder() and JSONDecoder(). See below:

class TheProfile: ObservableObject {
    @Published var profile = Profile.default{
    didSet {
        let encoder = JSONEncoder()
        if let encoded = try? encoder.encode(profile) {
            UserDefaults.standard.set(encoded, forKey: "Profile")
            }
        }
    }
    init() {
        if let profile = UserDefaults.standard.data(forKey: "Profile") {
            let decoder = JSONDecoder()
            if let decoded = try? decoder.decode(Profile.self, from: profile) {
                self.profile = decoded
                return
            }
        }

        self.profile = Profile.default
    }
}

I get the error message below (in a separate SwiftUI view for unrelated code written earlier which did not give an error previously):

Generic parameter 'S' could not be inferred

Am I making a misstake in the ParentalSalary enum in the sense that it does not conform to Codable or is something else causing this error?

One suspicion I had (with respect to me just learning this) is that there could be an issue with encoding the Profile: Identifiable, Codable with a JSONEncoder() combined with encoding the ParentalSalary enum with a different (?) encoder.

Any input or support is greatly appreciated.

6
  • 2
    What generic parameter is that or in other words, which line generates the error? A side note, an enum is a type so it should start with a capital letter, that is ParentalSalary Commented Feb 6, 2020 at 20:06
  • The error is generated in a Picker in a View looping through a separate enum in the profile struct as per below: Picker(selection: $profile.numberOfParents, label: Text("XXX")) { ForEach(Profile.numberOfParents.allCases, id: \.self) {p in Text(p.rawValue).tag(p) } } Commented Feb 6, 2020 at 20:11
  • And thanks for the input on enum being a type - got it! In the progress of learning currently. Commented Feb 6, 2020 at 20:15
  • First, breakup the code line that has the error (if you add a return after every opening curly brace {it should be good). Then, see which line Xcode puts the error on. If the issue doesn't suddenly jump out at you add the troublesome code to your question with some context. Add a comment to the end of the line that has the error indicating that that is the specific error location. This will allow people to see the error in context which will get you better answers. Commented Feb 6, 2020 at 22:54
  • Typically you see this kind of error when you are working with generics (arrays use generics, that is how you can have an array of Strings, ints, etc.) and Xcode can't figure out what type you are working with. Xcode will try to figure out the concrete type but sometimes it needs a little help. If you tried to use Array() Xcode would be lost but if you used Array<String>() it knows exactly what you mean (note that Array<String>() is the same as [String]()). Commented Feb 6, 2020 at 23:00

1 Answer 1

1

The problem is in your decoding method. You need to first decode your typeOfSalary key which is a String, switch the decoded String and decode the percentage in case it is equal to TypeOfSalaryCodingKeys.upTo.rawValue:

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    let typeOfSalary = try container.decode(String.self, forKey: .typeOfSalary)
    switch typeOfSalary {
    case TypeOfSalaryCodingKeys.noSalary.rawValue:
        self = .noSalary
    case TypeOfSalaryCodingKeys.upTo.rawValue:
        self = .upTo(percentage: try container.decode(Int.self, forKey: .percentage))
    default:
        throw DecodingError.dataCorruptedError(forKey: CodingKeys.typeOfSalary, in: container, debugDescription: "Invalid Type of Salary: \(typeOfSalary)")
    }
}

Playground testing:

let ps1: ParentalSalary = .noSalary
let ps2: ParentalSalary = .upTo(percentage: 27)
let parentalSalaries: [ParentalSalary] = [ps1,ps2]

do {
    let psData = try JSONEncoder().encode(parentalSalaries)
    print(String(data: psData, encoding: .utf8)!)
    let loadedJSON = try JSONDecoder().decode([ParentalSalary].self, from: psData)
    print(loadedJSON)
} catch {
    print(error)
}

This will print:

[{"typeOfSalary":"noSalary"},{"typeOfSalary":"upTo","percentage":27}] [__lldb_expr_1.ParentalSalary.noSalary, __lldb_expr_1.ParentalSalary.upTo(percentage: 27)]

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.