Last active
June 19, 2019 16:46
-
-
Save jonathan-beebe/a631492c6589b14d0c06fe846d266ff7 to your computer and use it in GitHub Desktop.
Customize json parsing of a Swift Decodable
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// https://gist.github.com/jonathan-beebe/a631492c6589b14d0c06fe846d266ff7 | |
import UIKit | |
// The vendor sandbox returns the following values for an `undefined` string value: | |
// | |
// - `""` | |
// - `"None"` | |
// - `null` | |
// | |
// Can we normalizing these into a Swift `nil` when parsing the json? | |
// MARK: Sample JSON | |
let jsonString = """ | |
{ | |
"name": "Alice", | |
"subnet_id": "None", | |
"transaction_id": "", | |
"to": null | |
} | |
""" | |
// MARK: Swift Model | |
struct TransactionModel: Decodable { | |
let name: String? | |
let subnet_id: String? | |
let transaction_id: String? | |
let to: String? | |
} | |
// MARK: Custom Decoding | |
extension KeyedDecodingContainer { | |
/// Wraps native `decodeIfPresent` to additionally map the value, if present. | |
func decodeIfPresent<T>(_ type: T.Type, forKey key: K, map: ((T?) -> T?)) throws -> T? where T : Decodable { | |
return map(try decodeIfPresent(type, forKey: key)) | |
} | |
} | |
extension Array where Element : Equatable { | |
/// Wraps native `contains` function to allow for optional input. | |
func contains(_ element: Element?) -> Bool { | |
guard let element = element else { return false } | |
return contains(element) | |
} | |
} | |
// Mark: Vendor specific extensions for custom decoding of strings | |
struct Vendor { | |
static let undefinedStrings = ["None", ""] | |
} | |
extension KeyedDecodingContainer { | |
func decodeVendorString(forKey key: Key) throws -> String? { | |
return try decodeIfPresent(String.self, forKey: key, map: { Vendor.undefinedStrings.contains($0) ? nil : $0 }) | |
} | |
} | |
extension TransactionModel { | |
enum TransactionModelKey: String, CodingKey { | |
case name | |
case subnet_id | |
case transaction_id | |
case to | |
} | |
init(from decoder: Decoder) throws { | |
let container = try decoder.container(keyedBy: TransactionModelKey.self) | |
self.init( | |
name : try container.decodeVendorString(forKey: .name), | |
subnet_id : try container.decodeVendorString(forKey: .subnet_id), | |
transaction_id : try container.decodeVendorString(forKey: .transaction_id), | |
to : try container.decodeVendorString(forKey: .to) | |
) | |
} | |
} | |
// MARK: Try it out | |
do { | |
let decoder = JSONDecoder() | |
let parsed = try decoder.decode(TransactionModel.self, from: jsonString.data(using: .utf8)!) | |
print(parsed) // -> TransactionModel(name: Optional("Alice"), subnet_id: nil, transaction_id: nil, to: nil) | |
} catch { | |
print(error) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment