Skip to content

Instantly share code, notes, and snippets.

@JasonCanCode
Created October 30, 2020 16:56
Show Gist options
  • Save JasonCanCode/084108ad8e4428427e149bb45bceedab to your computer and use it in GitHub Desktop.
Save JasonCanCode/084108ad8e4428427e149bb45bceedab to your computer and use it in GitHub Desktop.
A protocol to add JSON conversion abilities to any Codable object
import Foundation
public typealias JSON = [String: Any]
/// A Codable object with added JSON conversion abilities
public protocol JSONCodable: Codable, JSONAttributable {
// Replace declaration in adoptor's extension to change expected date format
static var dateFormatter: DateFormatter { get }
}
public extension JSONCodable {
static var dateFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en-US_POSIX")
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
return formatter
}
var attributes: [String: Any?] {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
encoder.dateEncodingStrategy = .formatted(Self.dateFormatter)
do {
let jsonData = try encoder.encode(self)
return try JSONSerialization.jsonObject(with: jsonData, options: []) as? JSON ?? [:]
} catch {
return [:]
}
}
init(json: JSON) throws {
guard let value = Self.generateNew(fromJSON: json) else {
throw NetworkError.dataConversion
}
self = value
}
static func generateNew(fromJSON json: JSON) -> Self? {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(dateFormatter)
do {
let data = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)
let decoded = try decoder.decode(Self.self, from: data)
return decoded
} catch {
print(
"🗺💥 Failed to map JSONCodable model with:\n",
prettifyJSON(json),
"\n\nReceived Error:",
error
)
return nil
}
}
/// Convert JSON into readable text for logging to console
static func prettifyJSON(_ json: JSON) -> String {
do {
let data: Data = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)
return String(data: data, encoding: .utf8) ?? ""
} catch _ {
return ""
}
}
/// Convert JSON into readable text for logging to console
static func prettifyJSONArray(_ jsonArray: [JSON]) -> String {
let arrayString = jsonArray.reduce("") { result, json -> String in
let addition = self.prettifyJSON(json)
if result.isEmpty {
return addition
} else {
return result + ",\n" + addition
}
}
return "[\n\(arrayString)\n]"
}
}
/**
A model that can provide a dictionary of its attributes. This is used to provide parameters for creating and updating entites on your server.
An adopter only needs to include the `attributes` computed property in order to access `validAttributes`. This provides a dictionary of only attributes that have a value (removing optionals that are nil).
*/
public protocol JSONAttributable {
/// A dictionary representation of an entity's attributes.
var attributes: [String: Any?] { get }
var valuedAttributes: [String: Any] { get }
var nullifiedAttributes: [String: Any] { get }
}
public extension JSONAttributable {
/// Provides a dictionary of only attributes that have a value (removing optionals that are nil).
var valuedAttributes: [String: Any] {
var validAttributes: [String: Any] = [:]
for case let (key, value?) in attributes {
validAttributes[key] = value
}
return validAttributes
}
/// Provides a dictionary of attributes in which nil values are converted to `NSNull`.
var nullifiedAttributes: [String: Any] {
var validAttributes: [String: Any] = [:]
for (key, value) in attributes {
if let value = value {
validAttributes[key] = value
} else {
validAttributes[key] = NSNull()
}
}
return validAttributes
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment