Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift Codable decode changing JSON and ignoring some fields

Tags:

json

swift

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.

like image 708
Matej P Avatar asked May 28 '19 11:05

Matej P


People also ask

What's the importance of key decoding strategies when using Codable?

Suggested approach: Give a specific answer first – “key decoding strategies let us handle difference between JSON keys and property names in our Decodable struct” – then provide some kind of practical sample.

How do you handle null in Codable Swift?

A null value (no string) is treated as nil by default so the decoding is supposed to succeed if the property is optional. By the way: You can omit the CodingKeys. If the name of the properties are the same as the keys you don't need explicit CodingsKeys .

What is Codable and Codable in Swift?

Codable; the data-parsing dream come true!Codable is the combined protocol of Swift's Decodable and Encodable protocols. Together they provide standard methods of decoding data for custom types and encoding data to be saved or transferred.

What does the Codable protocol do in Swift?

The Codable protocol in Swift is really a union of two protocols: Encodable and Decodable . These two protocols are used to indicate whether a certain struct, enum, or class, can be encoded into JSON data, or materialized from JSON data.


1 Answers

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) }
like image 56
vadian Avatar answered Oct 19 '22 11:10

vadian