2

I have struct like this:

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

and some OrderLine has in server response

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

but sometimes it has String type:

"subscription": "",

without subscription everytthing works fine, but with I've got an error

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

so my question is - how can I decode or to String? with value "", or to Subs? without any error? p.s. if I decode it like String? only, then have error debugDescription: "Expected to decode String but found a dictionary instead.", underlyingError: nil)

nastassia
  • 807
  • 1
  • 12
  • 31
  • 1
    https://stackoverflow.com/questions/50683988/swift-4-codable-api-provides-sometimes-an-int-sometimes-a-string ? – Larme Jan 07 '19 at 16:15
  • 2
    Do you control this API? If you do, fix it, make `subscription` have value `null ` when it's not defined, instead of `""`. If you don't have control of this API, then you'll have to implement your own `init(from: Decoder)` (in stead of relying on the default one synthesized by the compiler), in which you do a type check and handle this case – Alexander Jan 07 '19 at 16:16
  • Does your API ever return a valid (non-empty) `String` value for `subscription`? Or does it only return an empty String in case there's no `subscription` value? – Dávid Pásztor Jan 07 '19 at 16:17
  • @DávidPásztor only "", which means no subscriptions, and I should display this option later – nastassia Jan 07 '19 at 16:18
  • Why don't you simply manually check if subscription isEmpty before trying to decode the data using decodable? – Leo Dabus Jan 07 '19 at 16:20
  • what if you will have 2 subscription in your struct `let subscription: Subs?` and `let subscription: String?` – canister_exister Jan 07 '19 at 16:20
  • @LeoDabus I got response as responseData, not responseJSON. actually I don't want to change this big structure, only add this block of data.. – nastassia Jan 07 '19 at 16:22
  • @canister_exister xcode error - Invalid redeclaration of 'subscription' – nastassia Jan 07 '19 at 16:23
  • @Larme Thanks it will help me! – nastassia Jan 07 '19 at 16:30
  • 1
    `if let subscription = ((try? JSONSerialization.jsonObject(with: responseData)) as? [String: Any] ?? [:])["subscription"] as? String { if !subscription.isEmpty { print("decode") } else { print("subscription is empty") } }` – Leo Dabus Jan 07 '19 at 16:30
  • 1
    this way you don't have to implement the custom decoder – Leo Dabus Jan 07 '19 at 16:31

1 Answers1

5

You simply need to implement init(from:) yourself and try decoding the value for the subscription key both as a Dictionary representing Subs and as a 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
        }
    }
}
Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116