diff --git a/MVMCore/MVMCore/Constants/JSON/JSON.swift b/MVMCore/MVMCore/Constants/JSON/JSON.swift new file mode 100644 index 0000000..cf58455 --- /dev/null +++ b/MVMCore/MVMCore/Constants/JSON/JSON.swift @@ -0,0 +1,10 @@ +import Foundation + +public typealias JSONArray = [[String: Any]] +public typealias JSONObject = [String: Any] + +public enum JSONError: Error { + case pathNotFound + case data(path: String) + case other(error: Error) +} diff --git a/MVMCore/MVMCore/Constants/JSON/JSONValue.swift b/MVMCore/MVMCore/Constants/JSON/JSONValue.swift new file mode 100644 index 0000000..38f4e61 --- /dev/null +++ b/MVMCore/MVMCore/Constants/JSON/JSONValue.swift @@ -0,0 +1,122 @@ +import Foundation + +public typealias JSONValueArray = [[String: JSONValue]] +public typealias JSONValueObject = [String: JSONValue] + +public enum JSONValueError: Error { + case encode +} + +extension Optional { + func or(_ other: Optional) -> Optional { + switch self { + case .none: return other + case .some: return self + } + } + + func resolve(with error: @autoclosure () -> Error) throws -> Wrapped { + switch self { + case .none: throw error() + case .some(let wrapped): return wrapped + } + } +} + +public enum JSONValue: Codable, Equatable { + case string(String) + case int(Int) + case double(Double) + case bool(Bool) + case object([String: JSONValue]) + case array([JSONValue]) + case null + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let value = ((try? container.decode(String.self)).map(JSONValue.string)) + .or((try? container.decode(Int.self)).map(JSONValue.int)) + .or((try? container.decode(Double.self)).map(JSONValue.double)) + .or((try? container.decode(Bool.self)).map(JSONValue.bool)) + .or((try? container.decode([String: JSONValue].self)).map(JSONValue.object)) + .or((try? container.decode([JSONValue].self)).map(JSONValue.array)) + + self = value ?? JSONValue.null + } + + func decode() throws -> T { + let encoded = try JSONEncoder().encode(self) + return try JSONDecoder().decode(T.self, from: encoded) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case .string(let string): try container.encode(string) + case .int(let int): try container.encode(int) + case .double(let double): try container.encode(double) + case .bool(let bool): try container.encode(bool) + case .object(let object): try container.encode(object) + case .array(let array): try container.encode(array) + case .null: break + } + } +} + +public func ==(lhs: JSONValue, rhs: JSONValue) -> Bool { + let ld = try? lhs.encode() + let rd = try? lhs.encode() + return ld == rd +} + +extension JSONValue: ExpressibleByStringLiteral { + public init(stringLiteral value: String) { + self = .string(value) + } +} + +extension JSONValue: ExpressibleByIntegerLiteral { + public init(integerLiteral value: Int) { + self = .int(value) + } +} + +extension JSONValue: ExpressibleByFloatLiteral { + public init(floatLiteral value: Double) { + self = .double(value) + } +} + +extension JSONValue: ExpressibleByBooleanLiteral { + public init(booleanLiteral value: Bool) { + self = .bool(value) + } +} + +extension JSONValue: ExpressibleByArrayLiteral { + public init(arrayLiteral elements: JSONValue...) { + self = .array(elements) + } +} + +extension JSONValue: ExpressibleByDictionaryLiteral { + public init(dictionaryLiteral elements: (String, JSONValue)...) { + self = .object([String: JSONValue](uniqueKeysWithValues: elements)) + } +} + +extension Dictionary where Key == String, Value == Any { + public func toJSONValue() throws -> [String: JSONValue] { + let data = try JSONSerialization.data(withJSONObject: self, options: .prettyPrinted) + return try JSONDecoder().decode([String:JSONValue].self, from: data) + } +} + +extension Dictionary where Key == String, Value == JSONValue { + public func toJSONObject() throws -> JSONObject { + let encoded = try JSONEncoder().encode(self) + guard let json = try JSONSerialization.jsonObject(with: encoded, options: .mutableContainers) as? JSONObject else { + throw JSONValueError.encode + } + return json + } +} diff --git a/MVMCore/MVMCore/Models/Decoder.swift b/MVMCore/MVMCore/Models/Decoder.swift new file mode 100644 index 0000000..c04e494 --- /dev/null +++ b/MVMCore/MVMCore/Models/Decoder.swift @@ -0,0 +1,45 @@ +import Foundation + +public protocol AnyDecoder { + func decode(_ type: T.Type, from data: Data) throws -> T +} + +extension JSONDecoder: AnyDecoder {} +extension PropertyListDecoder: AnyDecoder {} + +extension Data { + public func decode(using decoder: AnyDecoder = JSONDecoder()) throws -> T { + return try decoder.decode(T.self, from: self) + } +} + +extension KeyedDecodingContainerProtocol { + public func decode(forKey key: Key) throws -> T { + return try decode(T.self, forKey: key) + } + + public func decode( + forKey key: Key, + default defaultExpression: @autoclosure () -> T + ) throws -> T { + return try decodeIfPresent(T.self, forKey: key) ?? defaultExpression() + } +} + +extension Decodable { + public static func decode(fileName: String, bundle: Bundle = Bundle.main) throws -> Self { + guard let path = bundle.path(forResource: fileName, ofType: ".json") else { + throw JSONError.pathNotFound + } + + guard let jsonData = try? Data(contentsOf: URL(fileURLWithPath: path)) else { + throw JSONError.data(path: path) + } + + do { + return try jsonData.decode() + } catch { + throw JSONError.other(error: error) + } + } +} diff --git a/MVMCore/MVMCore/Models/Encoder.swift b/MVMCore/MVMCore/Models/Encoder.swift new file mode 100644 index 0000000..0a236a5 --- /dev/null +++ b/MVMCore/MVMCore/Models/Encoder.swift @@ -0,0 +1,27 @@ +import Foundation + +public protocol AnyEncoder { + func encode(_ value: T) throws -> Data +} + +extension JSONEncoder: AnyEncoder {} +extension PropertyListEncoder: AnyEncoder {} + +extension Encodable { + public func encode(using encoder: AnyEncoder = JSONEncoder()) throws -> Data { + return try encoder.encode(self) + } + + public func toJSON() -> JSONObject? { + guard let data = try? encode() else { return nil } + return (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)).flatMap { $0 as? JSONObject } + } + + public func toJSONString() -> String? { + guard let data = try? encode(), + let string = String(data: data, encoding: .utf8) else { + return nil + } + return string + } +} diff --git a/MVMCore/MVMCore/Models/Holder.swift b/MVMCore/MVMCore/Models/Holder.swift new file mode 100644 index 0000000..bee0637 --- /dev/null +++ b/MVMCore/MVMCore/Models/Holder.swift @@ -0,0 +1,5 @@ +import Foundation + +public protocol Holdable: Codable { + static var identifier: String { get set } +} diff --git a/MVMCore/MVMCore/Models/HolderManager.swift b/MVMCore/MVMCore/Models/HolderManager.swift new file mode 100644 index 0000000..8f28a7c --- /dev/null +++ b/MVMCore/MVMCore/Models/HolderManager.swift @@ -0,0 +1,83 @@ +import Foundation + +public struct HolderManager { + public enum Error: Swift.Error { + case codingKeyNotFound + case encoderError + case decoderError + case decoderErrorObjectNotPresent + case decoderErrorModelNotMapped + } + fileprivate static var types: [String: Holdable.Type] = [:] + public static func register(_ type: A.Type) { + types[A.identifier] = type + } +} + +extension KeyedDecodingContainer where Key : CodingKey { + public enum TypeCodingKey: String, CodingKey { + case actionType + } + + //MARK: - Decode + public func decode(codingKey: KeyedDecodingContainer.Key, typeCodingKey: TypeCodingKey) throws -> T { + guard let holdable: Holdable = try decodeIfPresent(codingKey: codingKey, typeCodingKey: typeCodingKey) else { + throw HolderManager.Error.decoderErrorObjectNotPresent + } + + guard let h = holdable as? T else { + throw HolderManager.Error.decoderError + } + return h + } + + public func decode(codingKey: KeyedDecodingContainer.Key) throws -> ActionTypeProtocol { + return try decode(codingKey: codingKey, typeCodingKey: .actionType) + } + + //MARK: - DecodeIfPresent + public func decodeIfPresent(codingKey: KeyedDecodingContainer.Key, typeCodingKey: TypeCodingKey) throws -> T? { + + //get the type + let meta = try self.nestedContainer(keyedBy: TypeCodingKey.self, forKey: codingKey) + + guard let type = try meta.decodeIfPresent(String.self, forKey: typeCodingKey) else { + return nil + } + + //get the type + guard let found = HolderManager.types[type] else { + throw HolderManager.Error.decoderErrorModelNotMapped + } + + //get the decoder for the propertyKey + let decoder = try self.superDecoder(forKey: codingKey) + + //decode the type using the decoder + let holdable = try found.init(from: decoder) + + guard let h = holdable as? T else { + throw HolderManager.Error.decoderError + } + return h + } + + public func decodeIfPresent(codingKey: KeyedDecodingContainer.Key) throws -> ActionTypeProtocol? { + return try decodeIfPresent(codingKey: codingKey, typeCodingKey: .actionType) + } +} + +extension KeyedEncodingContainer where Key : CodingKey { + + public mutating func encodeIfPresent(_ value: Holdable?, forKey key: KeyedEncodingContainer.Key) throws { + if let v = value { + let encoder = self.superEncoder(forKey: key) + try v.encode(to: encoder) + } + } + + public mutating func encode(_ value: Holdable, forKey key: KeyedEncodingContainer.Key) throws { + let encoder = self.superEncoder(forKey: key) + try value.encode(to: encoder) + } +}