From 321502075697bbd99a4fbd2b0b14ccf8e9e9c453 Mon Sep 17 00:00:00 2001 From: Jason Pepas Date: Tue, 21 Mar 2017 16:08:01 -0500 Subject: [PATCH 1/7] Adding marshaled() to Array --- Sources/Marshaling.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Sources/Marshaling.swift b/Sources/Marshaling.swift index 958e940..eeade12 100644 --- a/Sources/Marshaling.swift +++ b/Sources/Marshaling.swift @@ -18,3 +18,9 @@ public protocol Marshaling { func marshaled() -> Self.MarshalType } + +extension Array where Element: Marshaling { + public func marshaled() -> [Any] { + return self.map { $0.marshaled() } + } +} From 9cfc8efbdd52e46adbd57e347c5d38f546578abb Mon Sep 17 00:00:00 2001 From: Rick Mann Date: Fri, 13 Dec 2019 15:43:10 -0800 Subject: [PATCH 2/7] Fix issue #142 by conditionally compiling NSDictionary extensions only if the Objective-C runtime is available. --- Sources/MarshalDictionary.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Sources/MarshalDictionary.swift b/Sources/MarshalDictionary.swift index 63d800b..bc67965 100644 --- a/Sources/MarshalDictionary.swift +++ b/Sources/MarshalDictionary.swift @@ -28,6 +28,15 @@ extension Dictionary: MarshaledObject { } } +/* + On Swift platforms without the Objective-C runtime (like Linux), + there’s no `NSObject.value(forKeyPath:)`. Given that it’s unlikely + a client will be trying to use Objective-C in these environments, + conditionally compiling these extensions is probably okay. +*/ + +#if canImport(ObjectiveC) + extension NSDictionary: ValueType { } extension NSDictionary: MarshaledObject { @@ -45,3 +54,5 @@ extension NSDictionary: MarshaledObject { return self[key] } } + +#endif From 8439b60a794687145c3588bee4bca9b879ce79a2 Mon Sep 17 00:00:00 2001 From: Rick M Date: Sat, 21 Jan 2023 03:06:08 -0800 Subject: [PATCH 3/7] Added UUID support --- Sources/ValueType.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Sources/ValueType.swift b/Sources/ValueType.swift index cd14ca2..d9d8d85 100755 --- a/Sources/ValueType.swift +++ b/Sources/ValueType.swift @@ -140,6 +140,18 @@ extension URL: ValueType { } } +extension UUID: ValueType { + public static func value(from object: Any) throws -> UUID { + guard let s = object as? String else { + throw MarshalError.typeMismatch(expected: String.self, actual: type(of: object)) + } + guard let objectValue = UUID(uuidString: s) else { + throw MarshalError.typeMismatch(expected: "valid UUID string", actual: s) + } + return objectValue + } +} + extension Int8: ValueType { public static func value(from object: Any) throws -> Int8 { guard let value = object as? Int else { From 7893fe1a0dc9ceecff9bc34ab0169191ecae5062 Mon Sep 17 00:00:00 2001 From: Rick M Date: Wed, 25 Jan 2023 22:39:58 -0800 Subject: [PATCH 4/7] Added comprehensive Date and UUID support. --- Marshal.xcodeproj/project.pbxproj | 4 + MarshalTests/DateAndUUIDTests.swift | 92 ++++++++++ Sources/ValueType.swift | 251 ++++++++++++++++++++++++++-- 3 files changed, 335 insertions(+), 12 deletions(-) create mode 100755 MarshalTests/DateAndUUIDTests.swift diff --git a/Marshal.xcodeproj/project.pbxproj b/Marshal.xcodeproj/project.pbxproj index 6c1c7c5..92f76a7 100755 --- a/Marshal.xcodeproj/project.pbxproj +++ b/Marshal.xcodeproj/project.pbxproj @@ -30,6 +30,7 @@ AFBED2761C7E1E5100622331 /* JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFBED2751C7E1E5100622331 /* JSON.swift */; }; AFBED27C1C7F65BB00622331 /* Unmarshaling.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFBED27B1C7F65BB00622331 /* Unmarshaling.swift */; }; AFBED27E1C7F699600622331 /* UnmarshalUpdating.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFBED27D1C7F699600622331 /* UnmarshalUpdating.swift */; }; + C638F8C02982569100851751 /* DateAndUUIDTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C638F8BF2982569100851751 /* DateAndUUIDTests.swift */; }; CC7504601DA6B27500643B7A /* Migration.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC75045F1DA6B27500643B7A /* Migration.swift */; }; CCAE549F1CFDF9D30069AC65 /* UnmarshalingWithContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCAE549E1CFDF9D30069AC65 /* UnmarshalingWithContext.swift */; }; CCB6D6C21CF90E7F00422F4C /* UnmarshalingWithContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCB6D6C01CF8F2F500422F4C /* UnmarshalingWithContextTests.swift */; }; @@ -73,6 +74,7 @@ AFBED2751C7E1E5100622331 /* JSON.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = JSON.swift; path = ../Sources/JSON.swift; sourceTree = ""; }; AFBED27B1C7F65BB00622331 /* Unmarshaling.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Unmarshaling.swift; path = ../Sources/Unmarshaling.swift; sourceTree = ""; }; AFBED27D1C7F699600622331 /* UnmarshalUpdating.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UnmarshalUpdating.swift; path = ../Sources/UnmarshalUpdating.swift; sourceTree = ""; }; + C638F8BF2982569100851751 /* DateAndUUIDTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateAndUUIDTests.swift; sourceTree = ""; }; CC75045F1DA6B27500643B7A /* Migration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Migration.swift; path = Sources/Migration.swift; sourceTree = SOURCE_ROOT; }; CCAE549E1CFDF9D30069AC65 /* UnmarshalingWithContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UnmarshalingWithContext.swift; path = Sources/UnmarshalingWithContext.swift; sourceTree = SOURCE_ROOT; }; CCB6D6C01CF8F2F500422F4C /* UnmarshalingWithContextTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnmarshalingWithContextTests.swift; sourceTree = ""; }; @@ -154,6 +156,7 @@ 71EFC3AB1CCB513300394E57 /* PerformanceTests.swift */, CCB6D6C01CF8F2F500422F4C /* UnmarshalingWithContextTests.swift */, E4605F131EE94EF4000147B7 /* FloatArray.json */, + C638F8BF2982569100851751 /* DateAndUUIDTests.swift */, ); path = MarshalTests; sourceTree = ""; @@ -299,6 +302,7 @@ files = ( 71EFC3A81CCB4EAF00394E57 /* PerformanceTestObjects.swift in Sources */, AFBED25F1C7E1AAB00622331 /* MarshalTests.swift in Sources */, + C638F8C02982569100851751 /* DateAndUUIDTests.swift in Sources */, CCB6D6C21CF90E7F00422F4C /* UnmarshalingWithContextTests.swift in Sources */, 71EFC3AC1CCB513300394E57 /* PerformanceTests.swift in Sources */, ); diff --git a/MarshalTests/DateAndUUIDTests.swift b/MarshalTests/DateAndUUIDTests.swift new file mode 100755 index 0000000..00570cd --- /dev/null +++ b/MarshalTests/DateAndUUIDTests.swift @@ -0,0 +1,92 @@ +// +// DateAndUUIDTests.swift +// +// Created by Rick Mann on 2021-05-18. +// + +import Foundation + +import XCTest +import Marshal + + + +class +TestMarshalAdditions : XCTestCase +{ + func + testDateFromMilliseconds() + throws + { + let json: [String:Any] = + [ + "dateInt" : 1621391590892, + "dateDouble" : 1621391590892.0, + "dateString" : "1621391590892", + "notADate" : "pizza", + ] + let dateInt: Date = try json.value(for: "dateInt", dateEncoding: .millisecondsSince1970) + let dateDouble: Date = try json.value(for: "dateDouble", dateEncoding: .millisecondsSince1970) + let dateString: Date = try json.value(for: "dateString", dateEncoding: .millisecondsSince1970) + XCTAssertEqual(dateInt, Date(timeIntervalSince1970: 1621391590.892)) + XCTAssertEqual(dateDouble, Date(timeIntervalSince1970: 1621391590.892)) + XCTAssertEqual(dateString, Date(timeIntervalSince1970: 1621391590.892)) + + let notADate: Date? = try? json.value(for: "notADate", dateEncoding: .millisecondsSince1970) + XCTAssertNil(notADate) + + XCTAssertThrowsError(try json.value(for: "notADate", dateEncoding: .millisecondsSince1970)) + } + + func + testDateFromSeconds() + throws + { + let json: [String:Any] = + [ + "dateInt" : 1621391590, + "dateDouble" : 1621391590.0, + "dateString" : "1621391590", + "notADate" : "pizza", + ] + let dateInt: Date = try json.value(for: "dateInt", dateEncoding: .secondsSince1970) + let dateDouble: Date = try json.value(for: "dateDouble", dateEncoding: .secondsSince1970) + let dateString: Date = try json.value(for: "dateString", dateEncoding: .secondsSince1970) + XCTAssertEqual(dateInt, Date(timeIntervalSince1970: 1621391590)) + XCTAssertEqual(dateDouble, Date(timeIntervalSince1970: 1621391590)) + XCTAssertEqual(dateString, Date(timeIntervalSince1970: 1621391590)) + + let notADate: Date? = try? json.value(for: "notADate", dateEncoding: .secondsSince1970) + XCTAssertNil(notADate) + + XCTAssertThrowsError(try json.value(for: "notADate", dateEncoding: .secondsSince1970)) + } + + func + testDateFromString() + throws + { + let json: [String:Any] = + [ + "createdAt" : "Tue, 25 May 2021 05:21:16 GMT", + ] + let createdAt: Date = try json.value(for: "createdAt", dateEncoding: .formatted("EEE, dd MMM yyyy HH:mm:ss ZZZ")) + XCTAssertEqual(createdAt, Date(timeIntervalSinceReferenceDate: 643612876.0)) + } + + func + testDateFromDateFormatter() + throws + { + let json: [String:Any] = + [ + "createdAt" : "Tue, 25 May 2021 05:21:16 GMT", + ] + + let df = DateFormatter() + df.dateFormat = "EEE, dd MMM yyyy HH:mm:ss ZZZ" + + let createdAt: Date = try json.value(for: "createdAt", dateEncoding: .formatter(df)) + XCTAssertEqual(createdAt, Date(timeIntervalSinceReferenceDate: 643612876.0)) + } +} diff --git a/Sources/ValueType.swift b/Sources/ValueType.swift index d9d8d85..4c82006 100755 --- a/Sources/ValueType.swift +++ b/Sources/ValueType.swift @@ -140,18 +140,6 @@ extension URL: ValueType { } } -extension UUID: ValueType { - public static func value(from object: Any) throws -> UUID { - guard let s = object as? String else { - throw MarshalError.typeMismatch(expected: String.self, actual: type(of: object)) - } - guard let objectValue = UUID(uuidString: s) else { - throw MarshalError.typeMismatch(expected: "valid UUID string", actual: s) - } - return objectValue - } -} - extension Int8: ValueType { public static func value(from object: Any) throws -> Int8 { guard let value = object as? Int else { @@ -229,3 +217,242 @@ extension Character: ValueType { } } #endif + + + +extension +Date : ValueType +{ + /** + Parses seconds since 1970. + */ + + public + static + func + value(from inObj: Any) + throws + -> Date + { + if let secondsSince1970 = inObj as? Double + { + let date = Date(timeIntervalSince1970: secondsSince1970) + return date + } + else if let str = inObj as? String, + let secondsSince1970 = Double(str) + { + let date = Date(timeIntervalSince1970: secondsSince1970) + return date + } + + throw MarshalError.typeMismatch(expected: Double.self, actual: type(of: inObj)) + } +} + +extension +Decimal : ValueType +{ + public + static + func + value(from inObj: Any) + throws + -> Decimal + { + if let jsonV = inObj as? Int + { + let v = Decimal(jsonV) + return v + } + else if let jsonV = inObj as? Double + { + let v = Decimal(jsonV) + return v + } + else if let str = inObj as? String, + let v = Decimal(string: str) + { + return v + } + else if let v = inObj as? Decimal + { + return v + } + + throw MarshalError.typeMismatch(expected: Decimal.self, actual: type(of: inObj)) + } +} + +extension +UUID : ValueType +{ + public + static + func + value(from inObj: Any) + throws + -> UUID + { + if let v = inObj as? UUID + { + return v + } + else if let jsonV = inObj as? String, + let v = UUID(uuidString: jsonV) + { + return v + } + + throw MarshalError.typeMismatch(expected: UUID.self, actual: type(of: inObj)) + } +} + +extension +MarshaledObject +{ + public + func + value(for inKey: KeyType, dateEncoding inEncoding: Date.JSONEncoding) + throws + -> Date + { + let any = try self.any(for: inKey) + + switch (inEncoding) + { + case .secondsSince1970, .millisecondsSince1970, .secondsSinceReferenceDate: + // Get the time interval as a Double from either a proper number or a string… + + let timeInterval: Double + if let v = any as? Double + { + timeInterval = v + } + else if let v = any as? Int + { + timeInterval = Double(v) + } + else if let s = any as? String, + let v = Double(s) + { + timeInterval = v + } + else + { + throw MarshalError.typeMismatch(expected: Double.self, actual: type(of: any)) + } + switch (inEncoding) + { + case .secondsSince1970: return Date(timeIntervalSince1970: timeInterval) + case .millisecondsSince1970: return Date(timeIntervalSince1970: timeInterval / 1000.0) + case .secondsSinceReferenceDate: return Date(timeIntervalSinceReferenceDate: timeInterval) + + default: + fatalError("Impossible encoding") + } + + // The date is coming in the specified (text) format… + + case .formatted(let format): + let df = DateFormatter() + df.dateFormat = format + return try date(fromString: any, formatter: df) + + case .formatter(let df): + return try date(fromString: any, formatter: df) + } + } + + func + value(for inKey: KeyType, dateEncoding inEncoding: Date.JSONEncoding) + throws + -> Date? + { + do + { + let d: Date = try self.value(for: inKey, dateEncoding: inEncoding) + return d + } + + catch + { + return nil + } + } + + fileprivate + func + date(fromString inS: Any, formatter inDF: DateFormatter) + throws + -> Date + { + if let s = inS as? String, + let date = inDF.date(from: s) + { + return date + } + else + { + throw MarshalError.typeMismatch(expected: String.self, actual: type(of: any)) + } + } +} + + +extension +Date +{ + public + enum + JSONEncoding + { + case secondsSince1970 + case millisecondsSince1970 + case secondsSinceReferenceDate + case formatted(String) + case formatter(DateFormatter) + } +} + +/* + I can't make the following work, in that Marshal never calls it. + I think it's a bug in Marshal, because it takes a slightly inconsistent + path when using Context. +*/ + +#if false +protocol +MarshalDateDeserializationContext +{ + var dateFormat: MarshalDateFormat { get set } +} + +enum +MarshalDateFormat +{ + case secondsSince1970 + case millisecondsSince1970 +} + +public +struct +AuctionDeserializationContext : MarshalDateDeserializationContext +{ + var dateFormat = MarshalDateFormat.millisecondsSince1970 +} + +extension +Date : UnmarshalingWithContext +{ + public + static + func + value(from inObj: MarshaledObject, inContext: AuctionDeserializationContext) + throws + -> Date + { + return Date() + } +} +#endif From 288859c26ae435052382c8f17fab5029120eb146 Mon Sep 17 00:00:00 2001 From: Rick M Date: Wed, 9 Aug 2023 23:21:03 -0700 Subject: [PATCH 5/7] Trying to address import error. --- Package.swift | 2 +- Package@swift-4.swift | 17 ----------------- 2 files changed, 1 insertion(+), 18 deletions(-) delete mode 100644 Package@swift-4.swift diff --git a/Package.swift b/Package.swift index fc60320..d142d53 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.1 +// swift-tools-version: 5.1 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription diff --git a/Package@swift-4.swift b/Package@swift-4.swift deleted file mode 100644 index d6b23c0..0000000 --- a/Package@swift-4.swift +++ /dev/null @@ -1,17 +0,0 @@ -// swift-tools-version:4.0 -import PackageDescription - -let package = Package( - name: "Marshal", - products: [ - .library(name: "Marshal", targets: ["Marshal"]), - ], - targets: [ - .target( - name: "Marshal", - dependencies: [], - path: "Sources" - ) - ] -) - From 799176f4da233c3439e8788d7ef02b0d423fdac7 Mon Sep 17 00:00:00 2001 From: Rick M Date: Wed, 9 Aug 2023 23:25:01 -0700 Subject: [PATCH 6/7] wtf --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index d142d53..5f45b89 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.1 +// swift-tools-version: 5.4 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription From 78ec3a1346a5842a0b9dee9d5bc51dda150cf1ed Mon Sep 17 00:00:00 2001 From: Rick M Date: Wed, 9 Aug 2023 23:27:55 -0700 Subject: [PATCH 7/7] grr --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 5f45b89..cf19f4e 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.4 +// swift-tools-version: 5.7 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription