swift 4 Codable - как декодировать, если есть строка или словарь?

У меня есть такая структура:

struct OrderLine: Codable{
    let absUrl: String?
    let restApiUrl : String?
    let description : String?
    let quantity : Int?
    let subscription: Subs?
    let total: Double?
 }

struct Subs: Codable{
    let quantity: Int?
    let name: String?
}

и некоторые OrderLine в ответе сервера

"subscription": {
   "quantity": 6,
   "name": "3 Months"
},

но иногда он имеет тип String:

"subscription": "",

без subscription все нормально работает, а вот с ошибкой

CodingKeys(stringValue: "subscription", intValue: nil)], 
   debugDescription: "Expected to decode Dictionary<String, Any> 
   but found a string/data instead.", underlyingError: nil)

поэтому мой вопрос - как я могу декодировать или в String? со значением "", или в Subs? без ошибок? p.s. если я декодирую только как String?, то будет ошибка debugDescription: "Expected to decode String but found a dictionary instead.", underlyingError: nil)


person nastassia    schedule 07.01.2019    source источник
comment
stackoverflow.com/questions/50683988/?   -  person Larme    schedule 07.01.2019
comment
Вы контролируете этот API? Если вы это сделаете, исправьте это, заставьте subscription иметь значение null , когда оно не определено, вместо "". Если у вас нет контроля над этим API, вам придется реализовать свой собственный init(from: Decoder) (вместо того, чтобы полагаться на стандартный, синтезированный компилятором), в котором вы выполняете проверку типа и обрабатываете этот случай.   -  person Alexander    schedule 07.01.2019
comment
Ваш API когда-либо возвращает допустимое (не пустое) значение String для subscription? Или он возвращает пустую строку только в случае отсутствия значения subscription?   -  person Dávid Pásztor    schedule 07.01.2019
comment
@DávidPásztor only , что означает отсутствие подписки, и я должен отобразить эту опцию позже   -  person nastassia    schedule 07.01.2019
comment
Почему бы вам просто вручную не проверить, пуста ли подписка, прежде чем пытаться декодировать данные с помощью декодируемого?   -  person Leo Dabus    schedule 07.01.2019
comment
что если у вас будет 2 подписки в вашей структуре let subscription: Subs? и let subscription: String?   -  person canister_exister    schedule 07.01.2019
comment
@LeoDabus Я получил ответ как responseData, а не responseJSON. на самом деле я не хочу менять эту большую структуру, просто добавляю этот блок данных.   -  person nastassia    schedule 07.01.2019
comment
@canister_exister ошибка xcode - недопустимое повторное объявление «подписки»   -  person nastassia    schedule 07.01.2019
comment
@Larme Спасибо, это поможет мне!   -  person nastassia    schedule 07.01.2019
comment
таким образом вам не нужно реализовывать пользовательский декодер   -  person Leo Dabus    schedule 07.01.2019


Ответы (1)


Вам просто нужно реализовать init(from:) самостоятельно и попробовать расшифровать значение ключа subscription как Dictionary, представляющее Subs, так и String.

struct OrderLine: Codable {
    let absUrl: String?
    let restApiUrl : String?
    let description : String?
    let quantity : Int?
    let subscription: Subs?
    let total: Double?

    private enum CodingKeys: String, CodingKey {
        case absUrl, restApiUrl, description, quantity, subscription, total
    }

    init(from decoder:Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.absUrl = try container.decodeIfPresent(String.self, forKey: .absUrl)
        self.restApiUrl = try container.decodeIfPresent(String.self, forKey: .restApiUrl)
        self.description = try container.decodeIfPresent(String.self, forKey: .description)
        self.quantity = try container.decodeIfPresent(Int.self, forKey: .quantity)
        self.total = try container.decodeIfPresent(Double.self, forKey: .total)
        if (try? container.decodeIfPresent(String.self, forKey: .subscription)) == nil {
            self.subscription = try container.decodeIfPresent(Subs.self, forKey: .subscription)
        } else {
            self.subscription = nil
        }
    }
}
person Dávid Pásztor    schedule 07.01.2019