Swift 4 Codable - Bool or String values - json

Looking for some input as to how you would handle the scenario I recently ran into.
I have been using Swift 4s Codable with success but today noticed a crash that I didn't expect. The API that I am working with, says it returns a boolean for the key manage_stock.
My stubbed struct looks like:
struct Product: Codable {
var manage_stock: Bool?
}
That works fine, the problem is that the API sometimes returns a string instead of a boolean.
Because of this, my decode fails with:
Expected to decode Bool but found a string/data instead.
The string only ever equals "parent" and I want it to equate to false.
I am also fine with changing my struct to var manage_stock: String? if that makes things easier to bring the JSON data in from the API. But of course, if I change that, my error just changes to:
Expected to decode String but found a number instead.
Is there a simple way to handle this mutation or will I need to lose all the automation that Codable brings to the table and implement my own init(decoder: Decoder).
Cheers

Since you can't always be in control of the APIs you're working with, here's one simple way to solve this with Codable directly, by overriding init(from:):
struct Product : Decodable {
// Properties in Swift are camelCased. You can provide the key separately from the property.
var manageStock: Bool?
private enum CodingKeys : String, CodingKey {
case manageStock = "manage_stock"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
do {
self.manageStock = try container.decodeIfPresent(Bool.self, forKey: .manageStock)
} catch DecodingError.typeMismatch {
// There was something for the "manage_stock" key, but it wasn't a boolean value. Try a string.
if let string = try container.decodeIfPresent(String.self, forKey: .manageStock) {
// Can check for "parent" specifically if you want.
self.manageStock = false
}
}
}
}
See Encoding and Decoding Custom Types for more info on this.

Related

Codable decoding values in JSON converting snake case to camel

When using Swift Codable you can specify a keyDecodingStrategy that will convert snake_case to camelCase. This works great for keys in a dictionary, but is there any solution for decoding values in a dictionary in a similarly clean way?
I have an enumeration that is used as a key in one place and as a value in another:
enum Foo: String, Codable, CodingKey, CaseIterable {
case bar
case bazQux // baz_qux in JSON, say
}
Then this is used as a key like so:
struct MyStruct: Codable {
enum Keys: String, CodingKey {
case myKey
}
let myProperty: [Bool]
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Foo.self)
myProperty = Foo.allCases.map {
try container.decode(Bool.self, forKey: $0)
}
}
}
But it is also used as a value like so:
struct Buz: Decodable {
enum CodingKeys: String, CodingKey {
case foo
}
// Note this is called using a decoder with keyDecodingStrategy = .convertFromSnakeCase
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let foo = try container.decode(Foo.self, forKey: .foo)
}
}
The JSON for example might include a line like this:
"foo": "baz_qux"
The value decoding only works if I set the raw value of the bazQux case to be baz_qux. But then that breaks the key decoding. It would also be annoying to have to make two separate enums for the same thing just to avoid this problem.
So I can then initialize the right model corresponding to the value.
I'd also like to avoid employing a "hacky" solution… Is there something reasonably elegant that works well with Codable?
Wound up adding a custom decoding initializer on Foo:
public init(from decoder: Decoder) throws {
let rawValue = try decoder.singleValueContainer().decode(String.self)
if let foo = Self.init(rawValue: rawValue.snakeToCamelCase) {
self = foo
} else {
throw CodingError.unknownValue
}
}
snakeToCamelCase is a simple extension method on String I added locally. Plus need to add the enum CodingError: Error to Foo if you want some error handling.
This isn't ideal but at least it's not too complicated. I would rather rely on built-in case conversion methods but I don't see a way to do that here.

Swift: Parsing a JSON file where you don't know the key values

I'm building an app that queries wikidata using json. It works so far, but the issue I'm running into is the response I get from wikidata isn't the actual data, but an identifier. Then, to convert that identifier, I need to send it to another url and receive another json response, but the issue I'm running into is the the key value isn't something I know until I get the first json response in. So, lets say my key is Q1169621. When I run it through the api, I get this response:
I'm using codable and JSONDecoder, but I don't know how to tell the decoder the value of the key in entities is Q1169621 to get at the value I want ("Jim Lauderdale")...some of my code is below, I have structs to define the data of the response, but how do I replace the key value in my struct with the one defined from the previous json that is decoded?
struct InfoFromWikiConverted: Codable {
let entities: Locator //this is the value I need to set before parsing the json
}
struct Locator: Codable {
let labels: Labels //how do I link this to the struct above?
}
struct Labels: Codable {
let en: EN
}
struct EN: Codable {
let value: String
}
The simplest approach would be to decode entities as [String: Locator]:
struct InfoFromWikiConverted: Decodable {
let entities: [String: Locator]
}
Of course, if you want your model to just be a single Locator (which means, potentially ignoring multiple keys under entities), then you'd need to manually decode it.
You'd need to create a type to represent any string Coding Key and implement init(from:):
struct InfoFromWikiConverted: Decodable {
let entities: Locator
struct CodingKeys: CodingKey {
var stringValue: String
var intValue: Int? = nil
init(stringValue: String) { self.stringValue = stringValue }
init?(intValue: Int) { return nil }
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
// get the first key (ignore the rest), and throw if there are none
guard let key = container.allKeys.first else {
throw DecodingError.dataCorrupted(
.init(codingPath: container.codingPath,
debugDescription: "Expected a non-empty object"))
}
let entities = try container.decode(Locator.self, forKey: key)
}
}
Note that because you aren't retaining the ID, you can't encode it back to the same form, so I only conformed to Decodable instead of Codable

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.
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) }

Ignoring not supported Decodables

I've been using Codables in my current project with a great pleasure - everything is fine, most of the stuff I get out of the box and it's built in - perfect! Though, recently I've stumbled on a first real issue, which can't be solved automatically they way I want it.
Problem description
I have a JSON coming from the backend which is a nested thing. It looks like this
{
"id": "fef08c8d-0b16-11e8-9e00-069b808d0ecc",
"title": "Challenge_Chapter",
"topics": [
{
"id": "5145ea2c-0b17-11e8-9e00-069b808d0ecc",
"title": "Automation_Topic",
"elements": [
{
"id": "518dfb8c-0b18-11e8-9e00-069b808d0ecc",
"title": "Automated Line examle",
"type": "text_image",
"video": null,
"challenge": null,
"text_image": {
"background_url": "",
"element_render": ""
}
},
{
"id": "002a1776-0b18-11e8-9e00-069b808d0ecc",
"title": "Industry 3.0 vs. 4.0: A vision of the new manufacturing world",
"type": "video",
"video": {
"url": "https://www.youtube.com/watch?v=xxx",
"provider": "youtube"
},
"challenge": null,
"text_image": null
},
{
"id": "272fc2b4-0b18-11e8-9e00-069b808d0ecc",
"title": "Classmarker_element",
"type": "challenge",
"video": null,
"challenge": {
"url": "https://www.classmarker.com/online-test/start/",
"description": null,
"provider": "class_marker"
},
"text_image": null
}
]
}
]
}
Chapter is the root object and it contains a list of Topics and each topic contains a list of Elements. Pretty straightforward, but I get stuck with the lowest level, Elements. Each Element has an enum coming from the backend, which looks like this:
[ video, challenge, text_image ], but iOS app doesn't support challenges, so my ElementType enum in Swift looks like:
public enum ElementType: String, Codable {
case textImage = "text_image"
case video = "video"
}
Of, course, it throws, because the first thing which happens is it tries to decode challenge value for this enum and it's not there, so my whole decoding fails.
What I want
I simply want decoding process to ignore Elements which can't be decoded. I don't need any Optionals. I just want them not to be present in Topic's array of Elements.
My reasoning and it's drawbacks
Of course, I've made a couple of attempts to solve this problem. First one, and the simples one is just to marks ElementType as Optional, but with this approach later on I'll have to unwrap everything and handle this - which is rather a tedious task. My second thought was to have something like .unsupported case in my enum, but again, later I want to use this to generate cells and I'll have to throw or return Optional - basically, same issues as previous idea.
My last idea, but I haven't tested it yet, is to write a custom init() for decodable and somehow deal with it there, but I'm not sure whether it's Element or Topic which should be responsible for this? If I write it in Element, I can't return nothing, I'll have to throw, but if I put it in Topic I'll have to append successfully decoded elements to array. The thing is what will happen if at some point I will be fetching Elements directly - again I won't be able to do it without throwing.
TL;DR
I want init(from decoder: Decoder) throws not to throw, but to return Optional.
I finally found something about this in SR-5953, but I think this is a hacky one.
Anyway, for the curious ones to allow this lossy decoding you need to manually decode everything. You can write it in you init(from decoder: Decoder), but a better approach would be to write a new helper struct called FailableCodableArray. Implementation would look like:
struct FailableCodableArray<Element: Decodable>: Decodable {
// https://github.com/phynet/Lossy-array-decode-swift4
private struct DummyCodable: Codable {}
private struct FailableDecodable<Base: Decodable>: Decodable {
let base: Base?
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
self.base = try? container.decode(Base.self)
}
}
private(set) var elements: [Element]
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
var elements = [Element]()
if let count = container.count {
elements.reserveCapacity(count)
}
while !container.isAtEnd {
guard let element = try container.decode(FailableDecodable<Element>.self).base else {
_ = try? container.decode(DummyCodable.self)
continue
}
elements.append(element)
}
self.elements = elements
}
}
And than for the actual decoding of those failable elements you jusy have to write a simple init(from decoder: Decoder) implementation like:
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
elements = try container.decode(FailableCodableArray<Element>.self, forKey: .elements).elements
}
As I've said, this solution works fine, but it feels a little hacky. It's an open bug, so you can vote it and let the Swift team see, that something like this built in would be a nice addition!
I recommend to create an umbrella protocol for all three types
protocol TypeItem {}
Edit: To conform to the requirement that only two types can be considered you have to use classes to get reference semantics
Then create classes TextImage and Video and a Dummy class adopting the protocol. All instances of the Dummy class will be removed after the decoding process.
class TextImage : TypeItem, Decodable {
let backgroundURL : String
let elementRender : String
private enum CodingKeys : String, CodingKey {
case backgroundURL = "background_url"
case elementRender = "element_render"
}
}
class Video : TypeItem, Decodable {
let url : URL
let provider : String
}
class Dummy : TypeItem {}
Use the enum to decode type properly
enum Type : String, Decodable {
case text_image, video, challenge
}
In the struct Element you have to implement a custom initializer which decodes the JSON to the structs depending on the type. The unwanted challange type is decoded into a Dummy instance. Due to the umbrella protocol you need only one property.
class Element : Decodable {
let type : Type
let id : String
let title : String
let item : TypeItem
private enum CodingKeys : String, CodingKey {
case id, title, type, video, text_image
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
title = try container.decode(String.self, forKey: .title)
type = try container.decode(Type.self, forKey: .type)
switch type {
case .text_image: item = try container.decode(TextImage.self, forKey: .text_image)
case .video: item = try container.decode(Video.self, forKey: .video)
default: item = Dummy()
}
}
}
Finally create a Root struct for the root element and Topic for the topics array. In Topic add a method to filter the Dummy instances.
class Root : Decodable {
let id : String
let title : String
var topics : [Topic]
}
class Topic : Decodable {
let id : String
let title : String
var elements : [Element]
func filterDummy() {
elements = elements.filter{!($0.item is Dummy)}
}
}
After the decoding call filterDummy() in each Topic to remove the dead items.
Another downside is that you have to cast item to the static type for example
let result = try decoder.decode(Root.self, from: data)
result.topics.forEach({$0.filterDummy()})
if let videoElement = result.topics[0].elements.first(where: {$0.type == .video}) {
let video = videoElement.item as! Video
print(video.url)
}

Swift 4 Decodable - Additional Variables

Something I havent figured out or have been able to find online as of yet.
Is there a way to add additional fields onto a struct containing the decodable protocol in which are not present in the JSON Data?
For example and simplicity, say I have an array of json objects structured as such
{
"name": "name1",
"url": "www.google.com/randomImage"
}
but say I want to add a UIImage variable to that struct containing the decodable such as
struct Example1: Decodable {
var name: String?
var url: String?
var urlImage: UIImage? //To add later
}
Is there a way to implement the decodable protocol in order to get the name and url from the JSON but allow me to add the UIImage later?
To exclude urlImage you must manually conform to Decodable instead of letting its requirements be synthesized:
struct Example1 : Decodable { //(types should be capitalized)
var name: String?
var url: URL? //try the `URL` type! it's codable and much better than `String` for storing URLs
var urlImage: UIImage? //not decoded
private enum CodingKeys: String, CodingKey { case name, url } //this is usually synthesized, but we have to define it ourselves to exclude `urlImage`
}
Before Swift 4.1 this only works if you add = nil to urlImage, even though the default value of nil is usually assumed for optional properties.
If you want to provide a value for urlImage at initialization, rather than using = nil, you can also manually define the initializer:
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
url = try container.decode(URL.self, forKey: .url)
urlImage = //whatever you want!
}
Actually, I'd make urlImage a lazy var. That way you don't have to worry about modifying the coding keys. All you have to do is write your getter, like so...
struct Example1 : Decodable {
var name : String?
var url : URL? // Better than String
lazy var urlImage: UIImage? = {
// Put your image-loading code here using 'url'
}()
}