Skip to content

Instantly share code, notes, and snippets.

@jonathan-beebe
Last active June 19, 2019 16:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jonathan-beebe/a631492c6589b14d0c06fe846d266ff7 to your computer and use it in GitHub Desktop.
Save jonathan-beebe/a631492c6589b14d0c06fe846d266ff7 to your computer and use it in GitHub Desktop.
Customize json parsing of a Swift Decodable
// 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