Matej P
Matej P

Reputation: 125

Swift Codable decode changing JSON and ignoring some fields

I have some REST API, which returns JSON. This API is used by more than one service (mobile, web,...) and it returns JSON that has more fields inside than I need and some of those additional fields can be changed on weekly basis. For example I have JSON like that:

{ "name": "Toyota Prius", "horsepower": 134, "mileage": 123241, "manufactured": 2017, "location": "One city", "origin": "Japan",
"convert": true //more properties follows... }

In mobile app I would only need to decode fields: name, horsepower and manufactured date, the rest can be ignored. Also some fields like origin haven't existed before but was added few days ago.

Question is how to write bulletproof decoder that would not break if there are some changes in JSON that do not affect my model in app? Also I have no word in output JSON from server and I cannot affect it. I just get data and I have to work with what I get.

I have wrote some experimental code for playground (car isn't printed out):

import UIKit

struct Car: Codable
{
    let name: String
    let horsePower: Int
    let selected: Bool //this is used as additional property inside my app to mark selected cars
    let index: Int //this is used as additional property inside my app for some indexing purposes

    enum CodingKeys: String, CodingKey
    {
        case name
        case horsePower = "horsepower"
        case selected
        case index
    }

    init(from decoder: Decoder) throws
    {
        let values = try decoder.container(keyedBy: CodingKeys.self)

        selected = false
        index = 0

        name = try values.decode(String.self, forKey: .name)
        horsePower = try values.decode(Int.self, forKey: .horsePower)
    }
}

let json = "{\"name\": \"Toyota Prius\",\"horsepower\": 134, \"convert\", true}"

let data = json.data(using: .utf8)

if let car = try? JSONDecoder().decode(Car.self, from: data!)
{
    //does not print... :(
    print(car)
}

I would like to get car printed in my example but mostly have a working proof code that will not break if JSON get changed. Also is there a way to get an decoding error description somehow? I know there are a lot of things in apple documentation, but mostly it is just too confusing to me and I couldn't find any useful examples for my problem.

Upvotes: 8

Views: 5155

Answers (1)

vadian
vadian

Reputation: 285069

First of all never try? JSONDecoder... , catch always the error and print it. DecodingErrors are extremely descriptive. They tell you exactly what is wrong and even where.

In your example you will get

"The given data was not valid JSON. ... No value for key in object around character 52."

which is the wrong comma (instead of a colon) after convert\"

To decode only specific keys declare the CodingKeys accordingly and delete the init method. selected and index are most likely supposed to be mutable so declare them as variable with a default value.

If the backend changes the JSON structure you'll get an error. The decoding process will break anyway regardless of the parsing API.

struct Car: Codable
{
    let name: String
    let horsePower: Int
    let convert : Bool

    var selected = false
    var index = 0

    enum CodingKeys: String, CodingKey {
        case name, horsePower = "horsepower", convert
    }
}

let json = """
{"name":"Toyota Prius","horsepower":134,"convert":true}
"""

let data = Data(json.utf8)

do {
    let car = try JSONDecoder().decode(Car.self, from: data)
    print(car)
} catch { print(error) }

Upvotes: 9

Related Questions