1

Compiler complains: "Raw value for enum case must be a literal"

In the struct/class we can use generic value but how to use generic in enum.

The below is the server response that I want to make a general structure

{
  "status": true,
  "message": "message",
  "error" : "error if any"
  "any key" : "Any kind of data"
}

In above example the "any key" part is tricky one. "any key" will be different for different service call.

for user city list:

{
  "status": true,
  "message": "message",
  "error" : ""
  "cities" : "city list"
}

for user state list:

{
  "status": true,
  "message": "message",
  "error" : ""
  "states" : "state list"
}

for user posts:

{
  "status": true,
  "message": "message",
  "error" : ""
  "posts" : "list of posts"
}

An you can see every service call has same key for "status", "message" and "error" and the data has different keys "cities", "states", "posts", etc.

So I want to create a general struct to include all these in to one.

I did the following way but stuck at different keys.

struct Response<T>: Codable  {


let message : String? // common in every service call
let status : Bool? // common in every service call
let errors: String? // common in every service call
let data : T? // it will be different for every call

enum CodingKeys: String, CodingKey {
    
    case message = "message"
    case status = "status"
    case data = <key>    //Here what to use?
    case errors = "errors"
}

init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)
    
    message = try values.decodeIfPresent(String.self, forKey: .message)
    status = try values.decodeIfPresent(Bool.self, forKey: .status)
    errors = try values.decodeIfPresent(String.self, forKey: .errors)
    data = try T(from: decoder)
}

}

Is it possible what I'm trying to do?

If yes, how to achieve this?

Any help will be appreciated!!

I got following errors when I was trying..

  1. Raw value for enum case must be a literal

  2. Non-nominal type 'T' does not support explicit initialization

Community
  • 1
  • 1
Mahendra
  • 8,448
  • 3
  • 33
  • 56
  • 1
    It would be much easier if the JSON structure was `{ ... "data" : { "cities" : "city list" } }` and `{ ... "data" : { "posts" : "list of posts" } }` – vadian Sep 28 '18 at 11:16
  • I know @vadian but api is already implemented :( – Mahendra Sep 28 '18 at 11:20
  • Then I'm afraid you can't use `Decodable` because the CodingKeys must be static unless you can include **all** possible CodingKeys and use a `decodeIfPresent` chain. – vadian Sep 28 '18 at 11:25
  • @vadian Or implement one for yourself, it just has to allow four keys and return nil as itself for invalid keys. – Fabian Sep 28 '18 at 11:26
  • Easy to parse this JSON with Decodable, but the generic is a complete red herring. This is an xy question. – matt Sep 28 '18 at 12:47

2 Answers2

2

One option would be (Doesn't actually work, see comment below):

struct Response<T>: Codable  {
    let message : String? // if these exist in every call do they have to be optional?
    let status : Bool? 
    let errors: String?
    let data: T
}

struct Users: Codable {
    let users: [User]
}

struct Cities: Codable {
    let cities: [City]
}

let response = JSONDecoder().decode(Response<Users>.self, from: data)

Pretty much the same thing but without generics:

class Response: Codable  {
    let message : String? // if these exist in every call do they have to be optional?
    let status : Bool? 
    let errors: String?
}

class UserResponse: Response, Codable {
    let users: [User]
}

class CitiesResponse: Response, Codable {
    let cities: [City]
}

Your main problem is that Codable needs to know what type to decode and map to, so you either need to specify the type using Generics (Response<Users>) or use inheritance to make specific types.

UPDATE:

Generic option didn't work because Codable still needs to know the key, so you end up doing alot of checking for keys and knowing what key matches with what type, making it pointless.

I did manage to Decode an example users one which should be reusable for cities and other types but it also seems a little long winded.

import PlaygroundSupport

let userJsonData = """
{
"status": true,
"message": "message",
"error" : "error if any",
"users" : [{
    "id": 1,
    "name": "John"
}, {
    "id": 2,
    "name": "Steve"
}]
}
""".data(using: .utf8)!

class Response: Codable {
    let error: String
    let status: Bool
    let message: String
}

struct User: Codable {
    let id: Int
    let name: String
}

class Users: Response {
    var users: [User]

    enum CodingKeys: String, CodingKey {
        case users
    }

    required init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        users = try values.decode([User].self, forKey: .users)
        try super.init(from: decoder)
    }
}

do {
     let response = try JSONDecoder().decode(Users.self, from: userJsonData)
    print(response.users)
} catch {
    print(error)
}

Output:

[__lldb_expr_22.User(id: 1, name: "John"), __lldb_expr_22.User(id: 2, name: "Steve")]

Scriptable
  • 19,402
  • 5
  • 56
  • 72
  • Note: I'm testing the generic method now to make sure it actually works as I explained – Scriptable Sep 28 '18 at 11:20
  • 1
    You can't inherit `struct`s in the second part. Use `class` instead. – user28434'mstep Sep 28 '18 at 11:20
  • @user28434 good spot, updated. Thanks. Copy/paste issue :( – Scriptable Sep 28 '18 at 11:21
  • When inheriting from a `Codable`, is it executing the parent coder implementation too? – Fabian Sep 28 '18 at 11:22
  • 1
    I haven't actually tested it, you probably need to call super.init – Scriptable Sep 28 '18 at 11:23
  • @Fabian, i check it right now, and autocreated methods do not call `super`'s, so you will have to do coding/decoding code manually for all children types. – user28434'mstep Sep 28 '18 at 11:26
  • @user28434 an interesting thing, I wonder what state objects would be in incase there is no super initializer called. – Fabian Sep 28 '18 at 11:28
  • 1
    Looks like the generic method wont work as it also needs to know the key (CodingKey) as well as the type – Scriptable Sep 28 '18 at 11:28
  • 1
    I am playing around with an example in a playground now, if I find a working solution I'll post it – Scriptable Sep 28 '18 at 11:30
  • @Fabian, well class requires all it's field to be initialized either with default values, or in `init`. If you will declare own init, you will be prompted to declare own `init(from: Decodable)` so here it's up to you what state it will end up. And if you initialize with default value, well, your fields will have this default value after decoding. – user28434'mstep Sep 28 '18 at 11:33
  • @user28434 let me clarify what I mean. You said the autocreated methods do not call super. But that‘s the usual way super‘s variables are initialized. What if they have no default value? Sounds weird to me. – Fabian Sep 28 '18 at 11:35
  • 1
    @Fabian, then you will be forced to write `init(from: Decodable)` manually, like there will be huge compile time error, and you won't get anywhere until you do decoding code in child class yourself. – user28434'mstep Sep 28 '18 at 11:38
  • @user28434 ah that makes sense! Thank you. – Fabian Sep 28 '18 at 11:39
  • I've updated the answer with a working solution using inheritance, it does actually work but takes just as much work as creating a struct for each response I think. also uses `super.init` – Scriptable Sep 28 '18 at 11:42
  • A big thank you for your efforts. Now I got a way to get this fix. – Mahendra Sep 28 '18 at 12:29
  • 1
    @Scriptable added a generic example. – Fabian Sep 28 '18 at 12:39
0

Update

Since enum require literals:

protocol ResponseData: Decodable {
    static var attributeName: String { get }
}

struct Response<T>: Decodable  where T: ResponseData {
    let message : String
    let status : Bool
    let errors: String
    let data: T

Coding keys are just a collection of strings or ints which the CodingKey-class deems acceptable.

    struct MyCodingKeys: CodingKey {
        var stringValue: String
        var intValue: Int?

        init?(intValue: Int) {
            return nil
        }

        init?(stringValue: String) {
            if let _ = DefaultCodingKeys.init(rawValue: stringValue){
                // OK
            } else if stringValue == T.attributeName {
                // OK
            } else {
                // NOT OK
                return nil
            }

            self.stringValue = stringValue
            self.intValue = nil
        }

        enum DefaultCodingKeys: String, CodingKey {
            case message = "message"
            case status = "status"
            case errors = "errors"
        }

Have to create these manually since its a struct instead of an enum.

        static var data: MyCodingKeys {
            return MyCodingKeys(stringValue: T.attributeName)!
        }
        static var message: MyCodingKeys {
            return MyCodingKeys(stringValue: "message")!
        }
        static var errors: MyCodingKeys {
            return MyCodingKeys(stringValue: "errors")!
        }
        static var status: MyCodingKeys {
            return MyCodingKeys(stringValue: "status")!
        }
    }

Necessary, since even if MyCodingKeys were named CodingKeys, Swift would not use it since Swift requires enums for that.

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: MyCodingKeys.self)

        message = try values.decode(String.self, forKey: .message)
        status = try values.decode(Bool.self, forKey: .status)
        errors = try values.decode(String.self, forKey: .errors)
        data = try values.decode(T.self, forKey: .data)
    }
}
Fabian
  • 5,040
  • 2
  • 23
  • 35