Swift Codable с вложенным JSON в качестве строки - PullRequest
0 голосов
/ 17 октября 2018

Службы, которые я использую для внутренних вызовов, возвращают всю эту json-структуру:

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

Где что-то может быть простой строкой:

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

иливложенный json (любой json с любыми свойствами), например:

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

Это моя структура:

struct GenericResponseModel: Codable {

    let status:String?
    let payload:String?
}

Я хочу всегда декодировать «полезную нагрузку» какстрока .Итак, во втором случае я хочу, чтобы свойство payload моего «GenericResponseModel» содержало строку json этого поля, но если я пытаюсь декодировать этот ответ, я получаю ошибку:

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

Возможно архивироватьчто я хочу?

Большое спасибо

Ответы [ 2 ]

0 голосов
/ 17 октября 2018

Как насчет этого ...

Объявить протокол PayloadType ...

protocol PayloadType: Decodable { }

и заставить String и struct Payload соответствовать ему ...

extension String: PayloadType { }

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

Затем сделайте GenericResponseModel универсальный…

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)
    }
}

Затем вы сможете расшифровать его следующим образом…

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")

и

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"))


Конечно,это зависит от того, знаете ли вы заранее тип payload.Вы можете обойти это, выдав конкретную ошибку, если полезный груз имеет неправильный тип…

enum GenericResponseModelError: Error {
    case wrongPayloadType
}

, а затем…

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
    }
}

Затем обработайте эту ошибку при декодировании…

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)
}
0 голосов
/ 17 октября 2018
I think this is working for you



struct GenericResponseModel: Codable {
    let status: String?
    let payload: Payload?
  }

  struct Payload:   {
        let someInt: Int?
        let someString: String?
    }
...