0

I have the following json string:

{"weight":[{"bmi":24.75,"date":"2020-01-20","logId":1000,"source":"API","time":"23:59:59","weight":200}]}

I want to convert it to a Swift object in order to access the different values. Here is what I am trying to do, I have these structs setup:

struct FitbitResponseModel: Decodable  {
    let weight: [FitbitResponseData]
}


struct FitbitResponseData: Decodable  {
    let bmi: Int
    let date: String
    let logId: Int
    let source: String
    let time: String
    let weight: Int
}

And then I have this method to decode the json string:

func parseJSON(data: Data) -> FitbitResponseModel? {

    var returnValue: FitbitResponseModel?
    do {
        returnValue = try JSONDecoder().decode(FitbitResponseModel.self, from: data)
    } catch {
        print("Error took place: \(error.localizedDescription).")
    }

    return returnValue
}

However when I try to run it I get the error that the data couldn’t be read because it isn’t in the correct format. What am I doing wrong? Any help is appreciated.

Thanks in advance!

Hols
  • 361
  • 1
  • 5
  • 21
  • 2
    Off topic but in your `catch` you should print `error` instead of `error.localizedDescription` to get a more detailed error message – Joakim Danielson Jan 22 '20 at 20:02
  • 2
    And if you do you see that `bmi` should be a Double – Joakim Danielson Jan 22 '20 at 20:06
  • @JoakimDanielson yeah was about to comment that this was the error, the problem was that bmi was an Int and that was causing the error. Changed it to a Float and it worked! – Hols Jan 22 '20 at 20:06

4 Answers4

2

Talk to your API developer. 000 is not a valid representation of a number for json. It needs to be either 0 or 0.0. You can lint your json at https://jsonlint.com . If you really need to work around this I suggest doing a string replacement on 000, with 0, before you parse the data.

Josh Homann
  • 15,933
  • 3
  • 30
  • 33
2

change

let bmi: Int 

to

let bmi: Double 

beacuse it's value is coming out to be 24.75 in your response if any variable type doesn't match to JSON response whole model wouldn't map in Codable protocol (Encodable and Decodable)

Sand'sHell811
  • 358
  • 3
  • 15
1

Json is n't valid because logId value in your json is n't valid.

{
    "weight": [{
        "bmi": 24.75,
        "date": "2020-01-20",
        "logId": 100,
        "source": "API",
        "time": "23:59:59",
        "weight": 200
    }]
}
Muhammad Afzal
  • 201
  • 1
  • 9
0

One really neat feature of this auto-generated conformance is that if you define an enum in your type called "CodingKeys" (or use a type alias with this name) that conforms to the CodingKey protocol – Swift will automatically use this as the key type. This therefore allows you to easily customise the keys that your properties are encoded/decoded with.

struct Base: Codable {
    let weight : [Weight]?

    enum CodingKeys: String, CodingKey {
        case weight = "weight"
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        weight = try values.decodeIfPresent([Weight].self, forKey: .weight)
    }
}

struct Weight : Codable {
    let bmi : Double?
    let date : String?
    let logId : Int?
    let source : String?
    let time : String?
    let weight : Int?

    enum CodingKeys: String, CodingKey {
        case bmi = "bmi"
        case date = "date"
        case logId = "logId"
        case source = "source"
        case time = "time"
        case weight = "weight"
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        bmi = try values.decodeIfPresent(Double.self, forKey: .bmi)
        date = try values.decodeIfPresent(String.self, forKey: .date)
        logId = try values.decodeIfPresent(Int.self, forKey: .logId)
        source = try values.decodeIfPresent(String.self, forKey: .source)
        time = try values.decodeIfPresent(String.self, forKey: .time)
        weight = try values.decodeIfPresent(Int.self, forKey: .weight)
    }
}

Hope that will help!

or you can use SwiftyJSON lib: https://github.com/SwiftyJSON/SwiftyJSON

Mohit Kumar
  • 2,898
  • 3
  • 21
  • 34
  • 1
    Why `CodingKeys`? Why is everything declared optional? Why optional **and** `decodeIfPresent`? Why init methods at all? 90% of this code is redundant. – vadian Jan 23 '20 at 08:51
  • You don't have to use `CodingKeys` if the name of a key in JSON and name of a variable is the same. Also, I will avoid making all variables optional until you really know if they are optional – Maciej Gad Jan 23 '20 at 08:53
  • CodingKeys if you want to give a different name to the field in your app means if you want to call "date" as "selectedDate"...optional to avoid app crash. If you are sure on the data part then you can remove the optional part – Mohit Kumar Jan 23 '20 at 08:54
  • More info on CodingKey: https://stackoverflow.com/a/44396824/2781088 – Mohit Kumar Jan 23 '20 at 08:57
  • @MohitKumar your app will not crash if you will use `let bmi : Double` instead of `let bmi : Double?`, it will just not parse the JSON, and it should be fine until you do something like `try! JSONDecoder().decode(Base.self, from: data)`. Sometimes is much better to know if a key is missing when you fetch data from the backend, rather than putting `if let bmi = object.bmi { ... }` all over in your codebase. – Maciej Gad Jan 23 '20 at 09:30
  • The answer is generic @MaciejGad. What if some value is nil and optional is always good...right – Mohit Kumar Jan 23 '20 at 09:32