diff --git a/AEPCore/Sources/migration/V4Migrator.swift b/AEPCore/Sources/migration/V4Migrator.swift index 4bc80b512..951d92ed5 100644 --- a/AEPCore/Sources/migration/V4Migrator.swift +++ b/AEPCore/Sources/migration/V4Migrator.swift @@ -122,7 +122,7 @@ struct V4Migrator { // save values let identityDataStore = NamedCollectionDataStore(name: V4MigrationConstants.Identity.DATASTORE_NAME) - let identityPropsData = AnyCodable.from(dictionary: identityPropsDict as [String: Any]) + let identityPropsData = AnyCodable.from(dictionary: identityPropsDict) identityDataStore.setObject(key: V4MigrationConstants.Identity.DataStoreKeys.IDENTITY_PROPERTIES, value: identityPropsData) identityDataStore.set(key: V4MigrationConstants.Identity.DataStoreKeys.PUSH_ENABLED, value: pushEnabled) @@ -142,19 +142,19 @@ struct V4Migrator { /// Migrates the v4 Lifecycle values into the v5 Lifecycle data store private func migrateLifecycleLocalStorage() { - let installDate = v4Defaults.object(forKey: V4MigrationConstants.Lifecycle.V4_INSTALL_DATE) as? Date + let installDate = v4Defaults.object(forKey: V4MigrationConstants.Lifecycle.V4_INSTALL_DATE) as? NSDate let lastVersion = v4Defaults.string(forKey: V4MigrationConstants.Lifecycle.V4_LAST_VERSION) - let lastUsedDate = v4Defaults.object(forKey: V4MigrationConstants.Lifecycle.V4_LAST_USED_DATE) as? Date + let lastUsedDate = v4Defaults.object(forKey: V4MigrationConstants.Lifecycle.V4_LAST_USED_DATE) as? NSDate let launches = v4Defaults.integer(forKey: V4MigrationConstants.Lifecycle.V4_LAUNCHES) let successfulClose = v4Defaults.bool(forKey: V4MigrationConstants.Lifecycle.V4_SUCCESSFUL_CLOSE) let lifecycleDataStore = NamedCollectionDataStore(name: V4MigrationConstants.Lifecycle.DATASTORE_NAME) - lifecycleDataStore.setObject(key: V4MigrationConstants.Lifecycle.DataStoreKeys.INSTALL_DATE, value: installDate) + lifecycleDataStore.setObject(key: V4MigrationConstants.Lifecycle.DataStoreKeys.INSTALL_DATE, value: installDate as Date?) lifecycleDataStore.set(key: V4MigrationConstants.Lifecycle.DataStoreKeys.LAST_VERSION, value: lastVersion) - lifecycleDataStore.setObject(key: V4MigrationConstants.Lifecycle.DataStoreKeys.LAST_LAUNCH_DATE, value: lastUsedDate) + lifecycleDataStore.setObject(key: V4MigrationConstants.Lifecycle.DataStoreKeys.LAST_LAUNCH_DATE, value: lastUsedDate as Date?) let persistedDict = ["launches": launches, "successfulClose": successfulClose] as [String: Any] - let persistedData = try? JSONSerialization.data(withJSONObject: persistedDict) + let persistedData = AnyCodable.from(dictionary: persistedDict) lifecycleDataStore.setObject(key: V4MigrationConstants.Lifecycle.DataStoreKeys.PERSISTED_CONTEXT, value: persistedData) v4Defaults.removeObject(forKey: V4MigrationConstants.Lifecycle.V4_INSTALL_DATE) @@ -208,7 +208,7 @@ struct V4Migrator { v4Defaults.removeObject(forKey: V4MigrationConstants.Configuration.V4_PRIVACY_STATUS) } - /// Migrates the v4 Identity values to v5 Analtyics data store + /// Migrates the v4 Identity values to v5 Analytics data store private func migrateVisitorIdLocalStorage() { // TODO: Implement when implementing the Analytics extension } diff --git a/AEPCore/Sources/migration/V5Migrator.swift b/AEPCore/Sources/migration/V5Migrator.swift index ebe7f4230..8325516f3 100644 --- a/AEPCore/Sources/migration/V5Migrator.swift +++ b/AEPCore/Sources/migration/V5Migrator.swift @@ -115,7 +115,7 @@ struct V5Migrator { // save values let identityDataStore = NamedCollectionDataStore(name: V5MigrationConstants.Identity.DATASTORE_NAME) - let identityPropsData = AnyCodable.from(dictionary: identityPropsDict as [String: Any]) + let identityPropsData = AnyCodable.from(dictionary: identityPropsDict) identityDataStore.setObject(key: V5MigrationConstants.Identity.DataStoreKeys.IDENTITY_PROPERTIES, value: identityPropsData) identityDataStore.set(key: V5MigrationConstants.Identity.DataStoreKeys.PUSH_ENABLED, value: pushEnabled) @@ -149,7 +149,7 @@ struct V5Migrator { lifecycleDataStore.setObject(key: V5MigrationConstants.Lifecycle.DataStoreKeys.LAST_LAUNCH_DATE, value: Date(timeIntervalSince1970: lastUsedDateInterval)) let persistedDict = ["launches": launches, "successfulClose": successfulClose, "osVersion": osVersion, "appId": appId] as [String: Any?] - let persistedData = try? JSONSerialization.data(withJSONObject: persistedDict) + let persistedData = AnyCodable.from(dictionary: persistedDict) lifecycleDataStore.setObject(key: V5MigrationConstants.Lifecycle.DataStoreKeys.PERSISTED_CONTEXT, value: persistedData) v5Defaults.removeObject(forKey: keyWithPrefix(datastoreName: V5MigrationConstants.Lifecycle.LEGACY_DATASTORE_NAME, key: V5MigrationConstants.Lifecycle.INSTALL_DATE)) diff --git a/AEPCore/Tests/MigrationTests/V4MigratorTests.swift b/AEPCore/Tests/MigrationTests/V4MigratorTests.swift index 94cc44122..15baf9f13 100644 --- a/AEPCore/Tests/MigrationTests/V4MigratorTests.swift +++ b/AEPCore/Tests/MigrationTests/V4MigratorTests.swift @@ -52,7 +52,7 @@ class V4MigratorTests: XCTestCase { /// Tests that data from v4 is properly migrated func testExistingV4Data() { // setup - let mockDate = Date() + let mockDate = NSDate() v4Defaults.set(["acqkey": "acqvalue"], forKey: V4MigrationConstants.MobileServices.V4_ACQUISITION_DATA) v4Defaults.set("identityIds", forKey: V4MigrationConstants.Identity.V4_IDS) v4Defaults.set("identityECID", forKey: V4MigrationConstants.Identity.V4_ECID) @@ -115,15 +115,15 @@ class V4MigratorTests: XCTestCase { XCTAssertNotNil(mockDataStore.get(collectionName: "", key: V4MigrationConstants.Identity.DataStoreKeys.IDENTITY_PROPERTIES)) XCTAssertTrue(dataStore.getBool(key: V4MigrationConstants.Identity.DataStoreKeys.PUSH_ENABLED) ?? false) let installDate: Date? = dataStore.getObject(key: V4MigrationConstants.Lifecycle.DataStoreKeys.INSTALL_DATE, fallback: nil) - XCTAssertEqual(mockDate, installDate) + XCTAssertEqual(mockDate as Date?, installDate) XCTAssertNotNil(mockDataStore.get(collectionName: "", key: V4MigrationConstants.Lifecycle.DataStoreKeys.PERSISTED_CONTEXT)) XCTAssertEqual("version", dataStore.getString(key: V4MigrationConstants.Lifecycle.DataStoreKeys.LAST_VERSION)) let lastUsedDate: Date? = dataStore.getObject(key: V4MigrationConstants.Lifecycle.DataStoreKeys.LAST_LAUNCH_DATE, fallback: nil) - XCTAssertEqual(mockDate, lastUsedDate) + XCTAssertEqual(mockDate as Date?, lastUsedDate) let msInstallDate: Date? = dataStore.getObject(key: V4MigrationConstants.MobileServices.INSTALL, fallback: nil) - XCTAssertEqual(mockDate, msInstallDate) + XCTAssertEqual(mockDate as Date?, msInstallDate) let msSeachAdInstallDate: Date? = dataStore.getObject(key: V4MigrationConstants.MobileServices.INSTALL_SEARCH_AD, fallback: nil) - XCTAssertEqual(mockDate, msSeachAdInstallDate) + XCTAssertEqual(mockDate as Date?, msSeachAdInstallDate) XCTAssertNotNil(mockDataStore.get(collectionName: "", key: V4MigrationConstants.MobileServices.V5_IN_APP_EXCLUDE_LIST)) let storedConfig: [String: AnyCodable]? = dataStore.getObject(key: ConfigurationConstants.DataStoreKeys.PERSISTED_OVERRIDDEN_CONFIG) XCTAssertEqual("optedout", storedConfig?["global.privacy"]?.stringValue) diff --git a/AEPServices/Sources/utility/AnyCodable.swift b/AEPServices/Sources/utility/AnyCodable.swift index 9d5a52c09..5344184dc 100644 --- a/AEPServices/Sources/utility/AnyCodable.swift +++ b/AEPServices/Sources/utility/AnyCodable.swift @@ -14,7 +14,11 @@ import Foundation /// A type erasing struct that can allow for dynamic `Codable` types public struct AnyCodable: Codable { - public let value: Any? + public var value: Any? { + return _value is NSNull ? nil : _value + } + + private let _value: Any public var stringValue: String? { return value as? String @@ -53,10 +57,10 @@ public struct AnyCodable: Codable { } public init(_ value: Any?) { - self.value = value + self._value = value ?? NSNull() } - public static func from(dictionary: [String: Any]?) -> [String: AnyCodable]? { + public static func from(dictionary: [String: Any?]?) -> [String: AnyCodable]? { guard let unwrappedDict = dictionary else { return nil } var newDict: [String: AnyCodable] = [:] @@ -109,12 +113,11 @@ public struct AnyCodable: Codable { public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() - guard value != nil else { + switch _value { + case is NSNull: + try container.encodeNil() + case is Void: try container.encodeNil() - return - } - - switch value { case let num as NSNumber: try encode(nsNumber: num, into: &container) case let string as String: @@ -136,7 +139,7 @@ public struct AnyCodable: Codable { case let dictionary as [String: Any?]: try container.encode(dictionary.mapValues { AnyCodable($0) }) default: - print("AnyCodable - encode: Failed to encode \(String(describing: value))") + print("AnyCodable - encode: Failed to encode \(String(describing: _value))") } } diff --git a/AEPServices/Tests/utility/AnyCodableTests.swift b/AEPServices/Tests/utility/AnyCodableTests.swift index c77c5ba71..868264c7b 100644 --- a/AEPServices/Tests/utility/AnyCodableTests.swift +++ b/AEPServices/Tests/utility/AnyCodableTests.swift @@ -83,6 +83,7 @@ class AnyCodableTests: XCTestCase { "intKey1": 1, "intKey2": 0, "intKey3": AnyCodable(NSNumber(0)), + "nullKey": nil ] let json = try JSONEncoder().encode(dictionary) @@ -96,7 +97,8 @@ class AnyCodableTests: XCTestCase { "intKey": 123, "intKey1": 1, "intKey2": 0, - "intKey3": 0 + "intKey3": 0, + "nullKey": null } """.data(using: .utf8)! let expectedJSONObject = try JSONSerialization.jsonObject(with: expected, options: []) as! NSDictionary @@ -151,4 +153,10 @@ class AnyCodableTests: XCTestCase { XCTAssertEqual(encodedJSONObject, expectedJSONObject) } + + func testInitWithNil() { + let nilAny = AnyCodable(nil) + + XCTAssertNil(nilAny.value) + } }