7

My JSON looks like:

{
    "status": true,
    "data": {
        "img_url": "/images/houses/",
        "houses": [
            {
                "id": "1",
                "name": "Kapital",
                "url": "https://kapital.com/",
                "img": "10fbf4bf6fd2928affb180.svg"
            }
        ]
     }
 }

And I'm using the next structs:

struct ServerStatus: Decodable {
    let status: Bool
    let data: ServerData
}

struct ServerData: Decodable {
    let img_url: String
    let houses: [House]
}

struct House: Decodable {
    let id: Int
    let img: String
    let name: String
    let url: String
}

But when I'm using:

let houses = try JSONDecoder().decode(ServerStatus.self, from: data)

I get the next error:

3 : CodingKeys(stringValue: "id", intValue: nil)
  - debugDescription : "Expected to decode Int but found a string/data instead."

It's my first time using Decodables and I'm googling this problem but was not able to fix it. Can someone help me to find out what's wrong and maybe explain me that?

When I remove data part from the ServerStatus everything works. So the problem is in parsing data part

rmaddy
  • 314,917
  • 42
  • 532
  • 579
J. Doe
  • 521
  • 4
  • 15
  • 2
    `id` is a String in the JSON but your `struct` defines `id` as an `Int`. Change it to `String` and the problem goes away. – rmaddy Sep 10 '18 at 21:25
  • @rmaddy wow! That was fast! Thank you very much! But one quick question, how can I save `ID` as Integer? – J. Doe Sep 10 '18 at 21:28
  • @J.Doe Can't you send an Int on the server side? – ielyamani Sep 10 '18 at 21:37
  • Looks like in the future, we'll be able to opt-in to a string-as-int behavior. See https://bugs.swift.org/browse/SR-5249. Meanwhile, go to that link and upvote the issue :). – Chris Prince Mar 08 '19 at 19:23
  • @ielyamani, how do you send an int from the server? All numeric values automatically get wrapped in quotes when json encoded. I can't figure out how you would return an actual integer from the server. – Lastmboy Jul 21 '19 at 17:38
  • @Lastmboy Have a look [here](https://www.w3schools.com/js/js_json_datatypes.asp). *Number* is the corresponding JSON data type – ielyamani Jul 21 '19 at 18:02
  • Thanks, @ielyamani. I found the solution to my problem. I'm json encoding in PHP and it puts quotes around numeric values be default. I found that using `json_encode($data, JSON_NUMERIC_CHECK)` returns the proper numeric values. – Lastmboy Jul 21 '19 at 19:54

1 Answers1

15

Change you House struct to this:

House: Decodable {
    let id: String
    let name: String
    let url: String
    let img: String
}

id should be a String. And to get the houses:

let houses = try JSONDecoder().decode(ServerStatus.self, from: data).data.houses

If you don't want to change the id coming from the server to Int, you can provide a custom implementation of Encodable and Decodable to define your own encoding and decoding logic.

struct House {
    let id: Int
    let img: String
    let name: String
    let url: String

    enum CodingKeys: String, CodingKey {
        case id, img, name, url
    }
}

extension House: Decodable {
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        guard let idInt = Int(try values.decode(String.self, forKey: .id)) else {
            fatalError("The id is not an Int")
        }
        id = idInt
        img = try values.decode(String.self, forKey: .img)
        name = try values.decode(String.self, forKey: .name)
        url = try values.decode(String.self, forKey: .url)
    }
}

//Just in case you want to encode the House struct
extension House: Encodable {
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(String(id), forKey: .id)
        try container.encode(img, forKey: .img)
        try container.encode(name, forKey: .name)
        try container.encode(url, forKey: .url)
    }
}

let decoder = JSONDecoder()
let data = """
{
    "status": true,
    "data": {
        "img_url": "/images/houses/",
        "houses": [
        {
        "id": "1",
        "name": "Kapital",
        "url": "https://kapital.com/",
        "img": "10fbf4bf6fd2928affb180.svg"
        }
        ]
    }
}
""".data(using: .utf8)!

let houses = try JSONDecoder().decode(ServerStatus.self, from: data).data.houses

print(houses)
ielyamani
  • 17,807
  • 10
  • 55
  • 90
  • 6
    Based on the comments, the OP wants `id` to be an `Int` so the question now becomes how to convert the string from the JSON into the desired `Int` value in the struct. – rmaddy Sep 10 '18 at 21:32