5

The services I use for backend calls returns all this json-structure:

{
    "status" : "OK",
    "payload" : **something**
}

Where something can be a simple string:

{
    "status" : "OK",
    "payload" : "nothing changed"
}

or a nested json (any json with any properties), for example:

{
    "status" : "OK",
    "payload" : {
                    "someInt" : 2,
                    "someString" : "hi",
                    ...
                }
}

This is my struct:

struct GenericResponseModel: Codable {

    let status:String?
    let payload:String?
}

I want to decode "payload" always as a string. So in the second case I want that the payload property of my "GenericResponseModel" contains the json string of that field, but If I try to decode that response I get the error:

Type 'String' mismatch: Expected to decode String but found a dictionary instead

Is possible to archive what I want?

Many thanks

7
  • Yes, this is possible by specifying how your GenericResponseModel should decode itself. Check init(from decoder: Decoder). Commented Oct 17, 2018 at 9:56
  • stackoverflow.com/a/50674899/6630644 Commented Oct 17, 2018 at 9:58
  • This service returns a JSON with a key that might have different types? Commented Oct 17, 2018 at 10:25
  • @JBL: yes, it can return different object types, based on the service endpoint Commented Oct 17, 2018 at 10:26
  • In that case it looks like you'll need to decode payload as a string first, and if it fails, as an arbitrary JSON, and this might help. Then serialize it (e.g. through JSONSerialization to Data to String with init?(data:encoding:) ) Commented Oct 17, 2018 at 10:29

1 Answer 1

4

How about this…

Declare a PayloadType protocol…

protocol PayloadType: Decodable { }

and make String, and struct Payload conform to it…

extension String: PayloadType { }

struct Payload: Decodable, PayloadType {
    let someInt: Int
    let someString: String
}

Then make GenericResponseModel generic…

struct GenericResponseModel<T: PayloadType>: Decodable {

    let status: String
    let payload: T

    enum CodingKeys: CodingKey {
        case status, payload
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        status = try container.decode(String.self, forKey: .status)            
        payload = try container.decode(T.self, forKey: .payload)
    }
}

Then you can decode as follows…

let data = """
{
"status" : "OK",
"payload" : "nothing changed"
}
""".data(using: .utf8)!

print(try JSONDecoder().decode(GenericResponseModel<String>.self, from: data))

// GenericResponseModel<String>(status: "OK", payload: "nothing changed")

and

let data2 = """
{
"status" : "OK",
"payload" : {
"someInt" : 2,
"someString" : "hi"
}
}
""".data(using: .utf8)!

print(try JSONDecoder().decode(GenericResponseModel<Payload>.self, from: data2))

// GenericResponseModel<Payload>(status: "OK", payload: Payload(someInt: 2, someString: "hi"))


Of course, this relies on you knowing the payload type in advance. You could get around this by throwing a specific error if payload is the wrong type…

enum GenericResponseModelError: Error {
    case wrongPayloadType
}

and then…

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

    do {
        payload = try container.decode(T.self, forKey: .payload)
    } catch {
        throw GenericResponseModelError.wrongPayloadType
    }
}

Then handle this error when you decode…

let data = """
{
"status" : "OK",
"payload" : {
"someInt" : 2,
"someString" : "hi"
}
}
""".data(using: .utf8)!

do {
    let response = try JSONDecoder().decode(GenericResponseModel<String>.self, from: data) // Throws
    print(response) 
} catch let error as GenericResponseModelError where error == .wrongPayloadType {
    let response = try JSONDecoder().decode(GenericResponseModel<Payload>.self, from: data2) // Success!
    print(response)
}
Sign up to request clarification or add additional context in comments.

3 Comments

Also in this case, It won't work on all kind of json-object contained in "payload" but only with the example I made. Payload can be every kind of object.
How can we be expected to answer the question when you don't give all the relevant information? Also, I don't believe "Payload can be every kind of object." - if it can be literally anything, then Codable is the wrong solution. Use JSONSerialization and create a Dictionary from your JSON. Do you actually mean "The payload varies for each request"?
In the question is written "any json with any properties", so all informations has been given. In fact, my question is how to treat the param as a string, regardless its real type, and I just asked if this goal could be reached.

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.