From f48792a2b43721519cb142f69bec47ba0a183027 Mon Sep 17 00:00:00 2001 From: Nickolas Dimitrakas Date: Fri, 30 Jan 2026 14:47:20 -0500 Subject: [PATCH 1/6] refactor: migrate MPConvertJS to objc --- mParticle-Apple-SDK.xcodeproj/project.pbxproj | 12 +- mParticle-Apple-SDK/Utils/MPConvertJS.h | 33 ++ mParticle-Apple-SDK/Utils/MPConvertJS.m | 339 ++++++++++++++++++ mParticle-Apple-SDK/Utils/MPConvertJS.swift | 285 --------------- mParticle-Apple-SDK/mParticle.m | 1 + 5 files changed, 381 insertions(+), 289 deletions(-) create mode 100644 mParticle-Apple-SDK/Utils/MPConvertJS.h create mode 100644 mParticle-Apple-SDK/Utils/MPConvertJS.m delete mode 100644 mParticle-Apple-SDK/Utils/MPConvertJS.swift diff --git a/mParticle-Apple-SDK.xcodeproj/project.pbxproj b/mParticle-Apple-SDK.xcodeproj/project.pbxproj index c6543f6d6..e016e4066 100644 --- a/mParticle-Apple-SDK.xcodeproj/project.pbxproj +++ b/mParticle-Apple-SDK.xcodeproj/project.pbxproj @@ -278,7 +278,8 @@ D34286102D419CB800FEC2C8 /* MPUploadSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D342860E2D419CA700FEC2C8 /* MPUploadSettings.swift */; }; D356E0252CFE08ED0020898D /* MPResponseConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = D356E0232CFE08ED0020898D /* MPResponseConfig.swift */; }; D3724C192AE02AF60074CD67 /* mParticle_Apple_SDK_NoLocation.h in Headers */ = {isa = PBXBuildFile; fileRef = D3724C182AE02AF60074CD67 /* mParticle_Apple_SDK_NoLocation.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D3CEDACC2CB027E1001B32DF /* MPConvertJS.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3CEDACA2CB027E1001B32DF /* MPConvertJS.swift */; }; + D3CEDACB2CB027E1001B32DE /* MPConvertJS.h in Headers */ = {isa = PBXBuildFile; fileRef = D3CEDAC92CB027E1001B32DE /* MPConvertJS.h */; }; + D3CEDACC2CB027E1001B32DF /* MPConvertJS.m in Sources */ = {isa = PBXBuildFile; fileRef = D3CEDACA2CB027E1001B32DF /* MPConvertJS.m */; }; D3DE316C2D5261FC00CC537F /* MPDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3DE316A2D5261F700CC537F /* MPDevice.swift */; }; /* End PBXBuildFile section */ @@ -607,7 +608,8 @@ D3724C182AE02AF60074CD67 /* mParticle_Apple_SDK_NoLocation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mParticle_Apple_SDK_NoLocation.h; sourceTree = ""; }; D3B3E2062AE028EC001AB58C /* mParticle_Apple_SDK.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mParticle_Apple_SDK.h; sourceTree = ""; }; D3BA75152B614E3D008C3C65 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; - D3CEDACA2CB027E1001B32DF /* MPConvertJS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPConvertJS.swift; sourceTree = ""; }; + D3CEDAC92CB027E1001B32DE /* MPConvertJS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPConvertJS.h; sourceTree = ""; }; + D3CEDACA2CB027E1001B32DF /* MPConvertJS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPConvertJS.m; sourceTree = ""; }; D3DE316A2D5261F700CC537F /* MPDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDevice.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -892,7 +894,8 @@ D34286082D3806CF00FEC2C8 /* MPUserDefaults.swift */, D342860E2D419CA700FEC2C8 /* MPUploadSettings.swift */, 53A79B0229CDFB1F00E7489F /* MPMessageBuilder.m */, - D3CEDACA2CB027E1001B32DF /* MPConvertJS.swift */, + D3CEDACA2CB027E1001B32DF /* MPConvertJS.m */, + D3CEDAC92CB027E1001B32DE /* MPConvertJS.h */, 53A79B0929CDFB1F00E7489F /* NSDictionary+MPCaseInsensitive.m */, D356E0232CFE08ED0020898D /* MPResponseConfig.swift */, 53A79B0D29CDFB1F00E7489F /* MPApplication.m */, @@ -1282,6 +1285,7 @@ 53A79D5529CE23F700E7489F /* MPPersistenceController.h in Headers */, 53A79D5829CE23F700E7489F /* MPIdentityDTO.h in Headers */, 53A79D5929CE23F700E7489F /* MPMessageBuilder.h in Headers */, + D3CEDACB2CB027E1001B32DE /* MPConvertJS.h in Headers */, 53A79D5A29CE23F700E7489F /* MPURLRequestBuilder.h in Headers */, D3724C192AE02AF60074CD67 /* mParticle_Apple_SDK_NoLocation.h in Headers */, 53A79D5B29CE23F700E7489F /* MPKitContainer.h in Headers */, @@ -1587,7 +1591,7 @@ 53A79D7729CE23F700E7489F /* MPKitConfiguration.m in Sources */, 35C3DE702F2919B40077C0FD /* MPCCPAConsent.m in Sources */, 53A79D7829CE23F700E7489F /* MPConsentState.m in Sources */, - D3CEDACC2CB027E1001B32DF /* MPConvertJS.swift in Sources */, + D3CEDACC2CB027E1001B32DF /* MPConvertJS.m in Sources */, 53A79D7929CE23F700E7489F /* MPBracket.m in Sources */, 53A79D7A29CE23F700E7489F /* MParticleUserNotification.m in Sources */, 531BCF372B28A23400F5C573 /* MPIdentityCaching.m in Sources */, diff --git a/mParticle-Apple-SDK/Utils/MPConvertJS.h b/mParticle-Apple-SDK/Utils/MPConvertJS.h new file mode 100644 index 000000000..437a00ffe --- /dev/null +++ b/mParticle-Apple-SDK/Utils/MPConvertJS.h @@ -0,0 +1,33 @@ +#import + +@class MPCommerceEvent; +@class MPPromotionContainer; +@class MPPromotion; +@class MPTransactionAttributes; +@class MPProduct; +@class MPIdentityApiRequest; + +typedef NS_ENUM(NSUInteger, MPJSCommerceEventAction) { + MPJSCommerceEventActionUnknown = 0, + MPJSCommerceEventActionAddToCart, + MPJSCommerceEventActionRemoveFromCart, + MPJSCommerceEventActionCheckout, + MPJSCommerceEventActionCheckoutOptions, + MPJSCommerceEventActionClick, + MPJSCommerceEventActionViewDetail, + MPJSCommerceEventActionPurchase, + MPJSCommerceEventActionRefund, + MPJSCommerceEventActionAddToWishList, + MPJSCommerceEventActionRemoveFromWishlist +}; + +@interface MPConvertJS_PRIVATE : NSObject + ++ (MPCommerceEvent *)commerceEvent:(NSDictionary *)json; ++ (MPPromotionContainer *)promotionContainer:(NSDictionary *)json; ++ (MPPromotion *)promotion:(NSDictionary *)json; ++ (MPTransactionAttributes *)transactionAttributes:(NSDictionary *)json; ++ (MPProduct *)product:(NSDictionary *)json; ++ (MPIdentityApiRequest *)identityApiRequest:(NSDictionary *)json; + +@end diff --git a/mParticle-Apple-SDK/Utils/MPConvertJS.m b/mParticle-Apple-SDK/Utils/MPConvertJS.m new file mode 100644 index 000000000..8d7e64330 --- /dev/null +++ b/mParticle-Apple-SDK/Utils/MPConvertJS.m @@ -0,0 +1,339 @@ +#import "MPConvertJS.h" +#import "MPCommerceEvent.h" +#import "MPCommerceEvent+Dictionary.h" +#import "MPProduct.h" +#import "MPPromotion.h" +#import "MPTransactionAttributes.h" +#import "mParticle.h" +#import "MPILogger.h" +#import "MPIdentityApiRequest.h" + +@implementation MPConvertJS_PRIVATE + ++ (MPCommerceEventAction)commerceEventAction:(NSNumber *)json { + MPCommerceEventAction action; + + int actionInt = [json intValue]; + switch (actionInt) { + case MPJSCommerceEventActionAddToCart: + return MPCommerceEventActionAddToCart; + case MPJSCommerceEventActionRemoveFromCart: + return MPCommerceEventActionRemoveFromCart; + case MPJSCommerceEventActionCheckout: + return MPCommerceEventActionCheckout; + case MPJSCommerceEventActionCheckoutOptions: + return MPCommerceEventActionCheckoutOptions; + case MPJSCommerceEventActionClick: + return MPCommerceEventActionClick; + case MPJSCommerceEventActionViewDetail: + return MPCommerceEventActionViewDetail; + case MPJSCommerceEventActionPurchase: + return MPCommerceEventActionPurchase; + case MPJSCommerceEventActionRefund: + return MPCommerceEventActionRefund; + case MPJSCommerceEventActionAddToWishList: + return MPCommerceEventActionAddToWishList; + case MPJSCommerceEventActionRemoveFromWishlist: + return MPCommerceEventActionRemoveFromWishlist; + default: + MPILogError(@"Invalid commerce event action received from webview: %@", json); + return MPCommerceEventActionAddToCart; + } +} + ++ (MPCommerceEvent *)commerceEvent:(NSDictionary *)json { + if (!json || ![json isKindOfClass:[NSDictionary class]]) { + MPILogError(@"Unexpected commerce event data received from webview"); + return nil; + } + if (json[@"ProductAction"] != nil && ![json[@"ProductAction"] isKindOfClass:[NSDictionary class]]) { + MPILogError(@"Unexpected commerce event data received from webview"); + return nil; + } + + NSDictionary *productAction = json[@"ProductAction"]; + BOOL isProductAction = productAction[@"ProductActionType"] != nil; + BOOL isPromotion = json[@"PromotionAction"] != nil; + BOOL isImpression = json[@"ProductImpressions"] != nil; + BOOL isValid = isProductAction || isPromotion || isImpression; + + if (!isValid) { + MPILogError(@"Invalid commerce event dictionary received from webview: %@", json); + return nil; + } + + MPCommerceEvent *commerceEvent = nil; + if (isProductAction) { + id productActionType = productAction[@"ProductActionType"]; + if (!productActionType || ![productActionType isKindOfClass:[NSNumber class]]) { + MPILogError(@"Unexpected product action type received from webview"); + return nil; + } + MPCommerceEventAction action = [MPConvertJS_PRIVATE commerceEventAction:productActionType]; + commerceEvent = [[MPCommerceEvent alloc] initWithAction:action]; + } + else if (isPromotion) { + MPPromotionContainer *promotionContainer = [MPConvertJS_PRIVATE promotionContainer:json]; + commerceEvent = [[MPCommerceEvent alloc] initWithPromotionContainer:promotionContainer]; + } + else { + commerceEvent = [[MPCommerceEvent alloc] initWithImpressionName:nil product:nil]; + } + + if ((NSNull *)json[@"EventAttributes"] != [NSNull null]) { + commerceEvent.customAttributes = json[@"EventAttributes"]; + } + if ((NSNull *)json[@"CheckoutOptions"] != [NSNull null]) { + commerceEvent.checkoutOptions = json[@"CheckoutOptions"]; + } + if ((NSNull *)json[@"productActionListName"] != [NSNull null]) { + commerceEvent.productListName = json[@"productActionListName"]; + } + if ((NSNull *)json[@"productActionListSource"] != [NSNull null]) { + commerceEvent.productListSource = json[@"productActionListSource"]; + } + if ((NSNull *)json[@"CurrencyCode"] != [NSNull null]) { + commerceEvent.currency = json[@"CurrencyCode"]; + } + if ((NSNull *)productAction != [NSNull null]) { + commerceEvent.transactionAttributes = [MPConvertJS_PRIVATE transactionAttributes:productAction]; + } + if ([json[@"CheckoutStep"] isKindOfClass:[NSNumber class]]) { + commerceEvent.checkoutStep = [json[@"CheckoutStep"] intValue]; + } + if ((NSNull *)json[@"CustomFlags"] != [NSNull null]) { + NSDictionary *customFlags = json[@"CustomFlags"]; + for (NSString *key in customFlags.allKeys) { + id value = customFlags[key]; + if ([value isKindOfClass:[NSArray class]]) { + [commerceEvent addCustomFlags:(NSArray *)value withKey:key]; + } else if ([value isKindOfClass:[NSString class]]) { + [commerceEvent addCustomFlag:(NSString *)value withKey:key]; + } + } + } + + NSArray *jsonProducts = productAction[@"ProductList"]; + if ((NSNull *)jsonProducts != [NSNull null] && [jsonProducts isKindOfClass:[NSArray class]]) { + NSMutableArray *products = [NSMutableArray array]; + [jsonProducts enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + MPProduct *product = [MPConvertJS_PRIVATE product:obj]; + [products addObject:product]; + }]; + [commerceEvent addProducts:products]; + } + + NSArray *jsonImpressions = json[@"ProductImpressions"]; + if ((NSNull *)jsonImpressions != [NSNull null] && [jsonImpressions isKindOfClass:[NSArray class]]) { + [jsonImpressions enumerateObjectsUsingBlock:^(NSDictionary *jsonImpression, NSUInteger idx, BOOL * _Nonnull stop) { + NSString *listName = jsonImpression[@"ProductImpressionList"]; + NSArray *jsonProducts = jsonImpression[@"ProductList"]; + if ((NSNull *)jsonProducts != [NSNull null] && [jsonProducts isKindOfClass:[NSArray class]]) { + [jsonProducts enumerateObjectsUsingBlock:^(id _Nonnull jsonProduct, NSUInteger idx, BOOL * _Nonnull stop) { + MPProduct *product = [MPConvertJS_PRIVATE product:jsonProduct]; + [commerceEvent addImpression:product listName:listName]; + }]; + } + }]; + } + + return commerceEvent; +} + ++ (MPPromotionContainer *)promotionContainer:(NSDictionary *)json { + if (!json || ![json isKindOfClass:[NSDictionary class]]) { + MPILogError(@"Unexpected promotion container data received from webview"); + return nil; + } + + NSDictionary *promotionActionDictionary = json[@"PromotionAction"]; + if (!promotionActionDictionary || ![promotionActionDictionary isKindOfClass:[NSDictionary class]]) { + MPILogError(@"Unexpected promotion container action data received from webview"); + return nil; + } + + NSNumber *promotionActionTypeNumber = promotionActionDictionary[@"PromotionActionType"]; + if (promotionActionTypeNumber == nil || ![promotionActionTypeNumber isKindOfClass:[NSNumber class]]) { + MPILogError(@"Unexpected promotion container action type data received from webview"); + return nil; + } + + int promotionActionInt = [promotionActionTypeNumber intValue]; + MPPromotionAction promotionAction = promotionActionInt == 1 ? MPPromotionActionView : MPPromotionActionClick; + MPPromotionContainer *promotionContainer = [[MPPromotionContainer alloc] initWithAction:promotionAction promotion:nil]; + + NSArray *jsonPromotions = promotionActionDictionary[@"PromotionList"]; + if (!jsonPromotions || ![jsonPromotions isKindOfClass:[NSArray class]]) { + MPILogError(@"Unexpected promotion container list data received from webview"); + return nil; + } + + [jsonPromotions enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + MPPromotion *promotion = [MPConvertJS_PRIVATE promotion:obj]; + [promotionContainer addPromotion:promotion]; + }]; + + return promotionContainer; +} + ++ (MPPromotion *)promotion:(NSDictionary *)json { + if (!json || ![json isKindOfClass:[NSDictionary class]]) { + MPILogError(@"Unexpected promotion data received from webview"); + return nil; + } + MPPromotion *promotion = [[MPPromotion alloc] init]; + + if ((NSNull *)json[@"Creative"] != [NSNull null]) { + promotion.creative = json[@"Creative"]; + } + + if ((NSNull *)json[@"Name"] != [NSNull null]) { + promotion.name = json[@"Name"]; + } + + if ((NSNull *)json[@"Position"] != [NSNull null]) { + promotion.position = json[@"Position"]; + } + + if ((NSNull *)json[@"Id"] != [NSNull null]) { + promotion.promotionId = json[@"Id"]; + } + + return promotion; +} + ++ (MPTransactionAttributes *)transactionAttributes:(NSDictionary *)json { + if (!json || ![json isKindOfClass:[NSDictionary class]]) { + json = @{}; + } + MPTransactionAttributes *transactionAttributes = [[MPTransactionAttributes alloc] init]; + + if ((NSNull *)json[@"Affiliation"] != [NSNull null]) { + transactionAttributes.affiliation = json[@"Affiliation"]; + } + if ((NSNull *)json[@"CouponCode"] != [NSNull null]) { + transactionAttributes.couponCode = json[@"CouponCode"]; + } + if ((NSNull *)json[@"ShippingAmount"] != [NSNull null]) { + transactionAttributes.shipping = json[@"ShippingAmount"]; + } + if ((NSNull *)json[@"TaxAmount"] != [NSNull null]) { + transactionAttributes.tax = json[@"TaxAmount"]; + } + if ((NSNull *)json[@"TotalAmount"] != [NSNull null]) { + transactionAttributes.revenue = json[@"TotalAmount"]; + } + if ((NSNull *)json[@"TransactionId"] != [NSNull null]) { + transactionAttributes.transactionId = json[@"TransactionId"]; + } + + return transactionAttributes; +} + ++ (MPProduct *)product:(NSDictionary *)json { + if (!json || ![json isKindOfClass:[NSDictionary class]]) { + MPILogError(@"Unexpected product data received from webview"); + return nil; + } + MPProduct *product = [[MPProduct alloc] init]; + + if ((NSNull *)json[@"Brand"] != [NSNull null]) { + product.brand = json[@"Brand"]; + } + if ((NSNull *)json[@"Category"] != [NSNull null]) { + product.category = json[@"Category"]; + } + if ((NSNull *)json[@"CouponCode"] != [NSNull null]) { + product.couponCode = json[@"CouponCode"]; + } + if ((NSNull *)json[@"Name"] != [NSNull null]) { + product.name = json[@"Name"]; + } + + // Handle price as NSNumber or String + if (!json[@"Price"] || [json[@"Price"] isKindOfClass:[NSNumber class]]) { + product.price = json[@"Price"]; + } else if ([json[@"Price"] isKindOfClass:[NSString class]]) { + product.price = [NSNumber numberWithDouble:[(NSString *)json[@"Price"] doubleValue]]; + } + + if ((NSNull *)json[@"Sku"] != [NSNull null]) { + product.sku = json[@"Sku"]; + } + if ((NSNull *)json[@"Variant"] != [NSNull null]) { + product.variant = json[@"Variant"]; + } + if ((NSNull *)json[@"Position"] != [NSNull null]) { + product.position = [json[@"Position"] unsignedIntValue]; + } + if (!json[@"Quantity"] || [json[@"Quantity"] isKindOfClass:[NSNumber class]]) { + product.quantity = json[@"Quantity"]; + } + + NSDictionary *jsonAttributes = json[@"Attributes"]; + if ((NSNull *)jsonAttributes != [NSNull null] && [jsonAttributes isKindOfClass:[NSDictionary class]]) { + for (NSString *key in jsonAttributes) { + NSString *value = jsonAttributes[key]; + if ((NSNull *)value != [NSNull null]) { + [product setObject:value forKeyedSubscript:key]; + } + } + } + return product; +} + ++ (MPIdentityApiRequest *)identityApiRequest:(NSDictionary *)json { + MPIdentityApiRequest *request = [MPIdentityApiRequest requestWithEmptyUser]; + + if (!json || ![json isKindOfClass:[NSDictionary class]]) { + MPILogError(@"Unexpected identity api request data received from webview"); + return nil; + } + + NSArray *userIdentities = json[@"UserIdentities"]; + if (!userIdentities || ![userIdentities isKindOfClass:[NSArray class]]) { + MPILogError(@"Unexpected user identity data received from webview"); + return nil; + } + + if (userIdentities.count > 0) { + __block BOOL allSuccess = YES; + + [userIdentities enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull identityDictionary, NSUInteger idx, BOOL * _Nonnull stop) { + NSString *identity = identityDictionary[@"Identity"]; + NSNumber *identityTypeNumber = identityDictionary[@"Type"]; + + if (!identity || ![identity isKindOfClass:[NSString class]]) { + allSuccess = NO; + *stop = YES; + return; + } + + if (!identityTypeNumber || ![identityTypeNumber isKindOfClass:[NSNumber class]]) { + allSuccess = NO; + *stop = YES; + return; + } + + MPIdentity identityType = (MPIdentity)[identityTypeNumber unsignedIntegerValue]; + [request setIdentity:identity identityType:identityType]; + }]; + + if (!allSuccess) { + return nil; + } + } + + NSString *identity = json[@"Identity"]; + NSNumber *identityTypeNumber = json[@"Type"]; + + if (identity && [identity isKindOfClass:[NSString class]] && + identityTypeNumber && [identityTypeNumber isKindOfClass:[NSNumber class]]) { + MPIdentity identityType = (MPIdentity)[identityTypeNumber unsignedIntegerValue]; + [request setIdentity:identity identityType:identityType]; + } + + return request; +} + +@end diff --git a/mParticle-Apple-SDK/Utils/MPConvertJS.swift b/mParticle-Apple-SDK/Utils/MPConvertJS.swift deleted file mode 100644 index 71472c495..000000000 --- a/mParticle-Apple-SDK/Utils/MPConvertJS.swift +++ /dev/null @@ -1,285 +0,0 @@ -internal import mParticle_Apple_SDK_Swift - -@objc public enum MPJSCommerceEventAction: UInt, @unchecked Sendable { - case unknown = 0 - case addToCart = 1 - case removeFromCart = 2 - case checkout = 3 - case checkoutOptions = 4 - case click = 5 - case viewDetail = 6 - case purchase = 7 - case refund = 8 - case addToWishList = 9 - case removeFromWishlist = 10 -} - -@objc public final class MPConvertJS_PRIVATE: NSObject { - @objc public static func commerceEventAction(_ json: NSNumber) -> MPCommerceEventAction { - switch json.uintValue { - case MPJSCommerceEventAction.addToCart.rawValue: - return .addToCart - case MPJSCommerceEventAction.removeFromCart.rawValue: - return .removeFromCart - case MPJSCommerceEventAction.checkout.rawValue: - return .checkout - case MPJSCommerceEventAction.checkoutOptions.rawValue: - return .checkoutOptions - case MPJSCommerceEventAction.click.rawValue: - return .click - case MPJSCommerceEventAction.viewDetail.rawValue: - return .viewDetail - case MPJSCommerceEventAction.purchase.rawValue: - return .purchase - case MPJSCommerceEventAction.refund.rawValue: - return .refund - case MPJSCommerceEventAction.addToWishList.rawValue: - return .addToWishList - case MPJSCommerceEventAction.removeFromWishlist.rawValue: - return .removeFromWishlist - default: - let mparticle = MParticle.sharedInstance() - let logger = MPLog(logLevel: MPLog.from(rawValue: mparticle.logLevel.rawValue)) - logger.customLogger = mparticle.customLogger - - logger.error("Invalid commerce event action received from webview: \(json)") - return .addToCart - } - } - - @objc public static func commerceEvent(_ json: [AnyHashable: Any]) -> MPCommerceEvent? { - let mparticle = MParticle.sharedInstance() - let logger = MPLog(logLevel: MPLog.from(rawValue: mparticle.logLevel.rawValue)) - logger.customLogger = mparticle.customLogger - - guard json["ProductAction"] == nil || json["ProductAction"] is [String: Any] else { - logger.error("Unexpected commerce event data received from webview") - return nil - } - - let productAction = json["ProductAction"] as? [String: Any] - let isProductAction = productAction?["ProductActionType"] != nil - let isPromotion = json["PromotionAction"] != nil - let isImpression = json["ProductImpressions"] != nil - let isValid = isProductAction || isPromotion || isImpression - - if !isValid { - logger.error("Invalid commerce event dictionary received from webview: \(json)") - return nil - } - - let commerceEvent: MPCommerceEvent - if isProductAction { - guard let productActionType = productAction?["ProductActionType"] as? NSNumber else { - logger.error("Unexpected product action type received from webview") - return nil - } - let action = Self.commerceEventAction(productActionType) - commerceEvent = MPCommerceEvent(action: action) - } else if isPromotion { - let promotionContainer = Self.promotionContainer(json) - commerceEvent = MPCommerceEvent(promotionContainer: promotionContainer) - } else { - commerceEvent = MPCommerceEvent(impressionName: nil, product: nil) - } - - if let eventAttributes = json["EventAttributes"] as? [String: Any] { - commerceEvent.customAttributes = eventAttributes - } - if let checkoutOptions = json["CheckoutOptions"] as? String { - commerceEvent.checkoutOptions = checkoutOptions - } - if let productActionListName = json["productActionListName"] as? String { - commerceEvent.productListName = productActionListName - } - if let productActionListSource = json["productActionListSource"] as? String { - commerceEvent.productListSource = productActionListSource - } - if let currencyCode = json["CurrencyCode"] as? String { - commerceEvent.currency = currencyCode - } - if let productAction = productAction { - commerceEvent.transactionAttributes = Self.transactionAttributes(productAction) - } - if let checkoutStep = json["CheckoutStep"] as? NSNumber { - commerceEvent.checkoutStep = checkoutStep.intValue - } - if let customFlags = json["CustomFlags"] as? [String: Any] { - for key in customFlags.keys { - if let valueArray = customFlags[key] as? [String] { - commerceEvent.addCustomFlags(valueArray, withKey: key) - } else if let valueString = customFlags[key] as? String { - commerceEvent.addCustomFlag(valueString, withKey: key) - } - } - } - - if let jsonProducts = productAction?["ProductList"] as? [[AnyHashable: Any]] { - let products = jsonProducts.map { Self.product($0) } - commerceEvent.addProducts(products) - } - - if let jsonImpressions = json["ProductImpressions"] as? [[AnyHashable: Any]] { - for jsonImpression in jsonImpressions { - if let listName = jsonImpression["ProductImpressionList"] as? String, - let jsonProducts = jsonImpression["ProductList"] as? [[AnyHashable: Any]] { - for jsonObject in jsonProducts { - let product = MPConvertJS_PRIVATE.product(jsonObject) - commerceEvent.addImpression(product, listName: listName) - } - } - } - } - - return commerceEvent - } - - @objc public static func promotionContainer(_ json: [AnyHashable: Any]) -> MPPromotionContainer? { - let mparticle = MParticle.sharedInstance() - let logger = MPLog(logLevel: MPLog.from(rawValue: mparticle.logLevel.rawValue)) - logger.customLogger = mparticle.customLogger - - guard let promotionDictionary = (json["PromotionAction"] as? [String: Any]) else { - logger.error("Unexpected promotion container action data received from webview") - return nil - } - - guard let promotionActionTypeNumber = (promotionDictionary["PromotionActionType"] as? NSNumber) else { - logger.error("Unexpected promotion container action type data received from webview") - return nil - } - - let promotionAction = promotionActionTypeNumber.intValue == 1 ? MPPromotionAction.view : MPPromotionAction.click - let promotionContainer = MPPromotionContainer(action: promotionAction, promotion: nil) - if let jsonPromotions = promotionDictionary["PromotionList"] as? [[AnyHashable: Any]] { - for jsonObject in jsonPromotions { - promotionContainer.addPromotion(MPConvertJS_PRIVATE.promotion(jsonObject)) - } - } else { - logger.error("Unexpected promotion container list data received from webview") - return nil - } - - return promotionContainer - } - - @objc public static func promotion(_ json: [AnyHashable: Any]) -> MPPromotion { - let promotion = MPPromotion() - - if let creative = json["Creative"] as? String { - promotion.creative = creative - } - if let name = json["Name"] as? String { - promotion.name = name - } - if let position = json["Position"] as? String { - promotion.position = position - } - if let id = json["Id"] as? String { - promotion.promotionId = id - } - - return promotion - } - - @objc public static func transactionAttributes(_ json: [AnyHashable: Any] = [:]) -> MPTransactionAttributes! { - let transactionAttributes = MPTransactionAttributes() - - if let affiliation = json["Affiliation"] as? String { - transactionAttributes.affiliation = affiliation - } - if let couponCode = json["CouponCode"] as? String { - transactionAttributes.couponCode = couponCode - } - if let shippingAmount = json["ShippingAmount"] as? NSNumber { - transactionAttributes.shipping = shippingAmount - } - if let taxAmount = json["TaxAmount"] as? NSNumber { - transactionAttributes.tax = taxAmount - } - if let totalAmount = json["TotalAmount"] as? NSNumber { - transactionAttributes.revenue = totalAmount - } - if let transactionId = json["TransactionId"] as? String { - transactionAttributes.transactionId = transactionId - } - - return transactionAttributes - } - - @objc public static func product(_ json: [AnyHashable: Any]) -> MPProduct { - let product = MPProduct() - - if let brand = json["Brand"] as? String { - product.brand = brand - } - if let category = json["Category"] as? String { - product.category = category - } - if let couponCode = json["CouponCode"] as? String { - product.couponCode = couponCode - } - if let name = json["Name"] as? String { - product.name = name - } - if let price = json["Price"] as? NSNumber { - product.price = price - } else if let price = json["Price"] as? String, let double = double_t(price) { - product.price = NSNumber(value: double) - } - if let sku = json["Sku"] as? String { - product.sku = sku - } - if let variant = json["Variant"] as? String { - product.variant = variant - } - if let position = json["Position"] as? NSNumber { - product.position = position.uintValue - } - if let quantity = json["Quantity"] as? NSNumber { - product.quantity = quantity - } - if let jsonAttributes = json["Attributes"] as? [String: String] { - for (key, value) in jsonAttributes { - product.setValue(value, forKey: key) - } - } - - return product - } - - @objc private static func identityFromNumber(_ identityTypeNumber: NSNumber) -> MPIdentity { - return MPIdentity(rawValue: identityTypeNumber.uintValue) ?? .other - } - - @objc public static func identityApiRequest(_ json: [AnyHashable: Any]?) -> MPIdentityApiRequest? { - let request = MPIdentityApiRequest.withEmptyUser() - - guard let userIdentities = json?["UserIdentities"] as? [[AnyHashable: Any]] else { - let mparticle = MParticle.sharedInstance() - let logger = MPLog(logLevel: MPLog.from(rawValue: mparticle.logLevel.rawValue)) - logger.customLogger = mparticle.customLogger - - logger.error("Unexpected user identity data received from webview") - return nil - } - - for identityDictionary in userIdentities { - if let identity = identityDictionary["Identity"] as? String, - let identityTypeNumber = identityDictionary["Type"] as? NSNumber, - let identityType = MPIdentity(rawValue: identityTypeNumber.uintValue) { - request.setIdentity(identity, identityType: identityType) - } else { - return nil - } - } - - if let identity = json?["Identity"] as? String, - let identityTypeNumber = json?["Type"] as? NSNumber, - let identityType = MPIdentity(rawValue: identityTypeNumber.uintValue) { - request.setIdentity(identity, identityType: identityType) - } - - return request - } -} diff --git a/mParticle-Apple-SDK/mParticle.m b/mParticle-Apple-SDK/mParticle.m index a7d18bae0..d5cf60ec8 100644 --- a/mParticle-Apple-SDK/mParticle.m +++ b/mParticle-Apple-SDK/mParticle.m @@ -19,6 +19,7 @@ #import "SettingsProvider.h" #import "Executor.h" #import "AppEnvironmentProvider.h" +#import "MPConvertJS.h" @import mParticle_Apple_SDK_Swift; static NSArray *eventTypeStrings = nil; From feec0e6f9270d5d2ef0867ac9f054d7eaee5b5ef Mon Sep 17 00:00:00 2001 From: Nickolas Dimitrakas Date: Fri, 30 Jan 2026 16:28:22 -0500 Subject: [PATCH 2/6] fix import issues result of call with @denischilik --- mParticle-Apple-SDK/Ecommerce/MPPromotion.m | 1 + mParticle-Apple-SDK/Kits/MPEventProjection.m | 1 + mParticle-Apple-SDK/MPBackendController.m | 1 + .../MParticleSession+MParticlePrivate.h | 3 +++ .../MParticleSession+MParticlePrivate.m | 2 +- mParticle-Apple-SDK/Utils/MPConvertJS.h | 12 +++++------- mParticle-Apple-SDK/Utils/MPConvertJS.m | 5 ----- 7 files changed, 12 insertions(+), 13 deletions(-) diff --git a/mParticle-Apple-SDK/Ecommerce/MPPromotion.m b/mParticle-Apple-SDK/Ecommerce/MPPromotion.m index 89d24677e..738ce241b 100644 --- a/mParticle-Apple-SDK/Ecommerce/MPPromotion.m +++ b/mParticle-Apple-SDK/Ecommerce/MPPromotion.m @@ -1,6 +1,7 @@ #import "MPPromotion.h" #import "MPIConstants.h" #import "NSDictionary+MPCaseInsensitive.h" +#import "mParticle.h" #import "MParticleSwift.h" @import mParticle_Apple_SDK_Swift; diff --git a/mParticle-Apple-SDK/Kits/MPEventProjection.m b/mParticle-Apple-SDK/Kits/MPEventProjection.m index 4f027d77a..44bd4a21e 100644 --- a/mParticle-Apple-SDK/Kits/MPEventProjection.m +++ b/mParticle-Apple-SDK/Kits/MPEventProjection.m @@ -1,5 +1,6 @@ #import "MPEventProjection.h" #import "MPAttributeProjection.h" +#import "mParticle.h" #import "MParticleSwift.h" @import mParticle_Apple_SDK_Swift; diff --git a/mParticle-Apple-SDK/MPBackendController.m b/mParticle-Apple-SDK/MPBackendController.m index c3d42f39d..57a3d65e9 100644 --- a/mParticle-Apple-SDK/MPBackendController.m +++ b/mParticle-Apple-SDK/MPBackendController.m @@ -22,6 +22,7 @@ #import "MPKitContainer.h" #import "MPURLRequestBuilder.h" #import "MPIdentityCaching.h" +#import "mParticle.h" #import "MParticleSwift.h" #import "MPNetworkCommunication.h" #if TARGET_OS_IOS == 1 diff --git a/mParticle-Apple-SDK/MParticleSession+MParticlePrivate.h b/mParticle-Apple-SDK/MParticleSession+MParticlePrivate.h index 7b07b9bc1..1b0fc5ce2 100644 --- a/mParticle-Apple-SDK/MParticleSession+MParticlePrivate.h +++ b/mParticle-Apple-SDK/MParticleSession+MParticlePrivate.h @@ -1,3 +1,6 @@ +#import +#import "mParticle.h" + @interface MParticleSession () - (instancetype)initWithUUID:(NSString *)uuid; diff --git a/mParticle-Apple-SDK/MParticleSession+MParticlePrivate.m b/mParticle-Apple-SDK/MParticleSession+MParticlePrivate.m index b07c0524c..f4e445081 100644 --- a/mParticle-Apple-SDK/MParticleSession+MParticlePrivate.m +++ b/mParticle-Apple-SDK/MParticleSession+MParticlePrivate.m @@ -1,4 +1,4 @@ -#import "MParticleSwift.h" +#import "mParticle.h" #import "MParticleSession+MParticlePrivate.h" @import mParticle_Apple_SDK_Swift; diff --git a/mParticle-Apple-SDK/Utils/MPConvertJS.h b/mParticle-Apple-SDK/Utils/MPConvertJS.h index 437a00ffe..705c24f3c 100644 --- a/mParticle-Apple-SDK/Utils/MPConvertJS.h +++ b/mParticle-Apple-SDK/Utils/MPConvertJS.h @@ -1,11 +1,9 @@ #import - -@class MPCommerceEvent; -@class MPPromotionContainer; -@class MPPromotion; -@class MPTransactionAttributes; -@class MPProduct; -@class MPIdentityApiRequest; +#import "MPCommerceEvent.h" +#import "MPPromotion.h" +#import "MPTransactionAttributes.h" +#import "MPProduct.h" +#import "MPIdentityApiRequest.h" typedef NS_ENUM(NSUInteger, MPJSCommerceEventAction) { MPJSCommerceEventActionUnknown = 0, diff --git a/mParticle-Apple-SDK/Utils/MPConvertJS.m b/mParticle-Apple-SDK/Utils/MPConvertJS.m index 8d7e64330..fffc9ba92 100644 --- a/mParticle-Apple-SDK/Utils/MPConvertJS.m +++ b/mParticle-Apple-SDK/Utils/MPConvertJS.m @@ -1,12 +1,7 @@ #import "MPConvertJS.h" -#import "MPCommerceEvent.h" #import "MPCommerceEvent+Dictionary.h" -#import "MPProduct.h" -#import "MPPromotion.h" -#import "MPTransactionAttributes.h" #import "mParticle.h" #import "MPILogger.h" -#import "MPIdentityApiRequest.h" @implementation MPConvertJS_PRIVATE From 1e354026781a9a85e71f7abe97b40f75109c47b0 Mon Sep 17 00:00:00 2001 From: Nickolas Dimitrakas Date: Fri, 30 Jan 2026 16:31:34 -0500 Subject: [PATCH 3/6] remove unused var --- mParticle-Apple-SDK/Utils/MPConvertJS.m | 2 -- 1 file changed, 2 deletions(-) diff --git a/mParticle-Apple-SDK/Utils/MPConvertJS.m b/mParticle-Apple-SDK/Utils/MPConvertJS.m index fffc9ba92..266bfa6dd 100644 --- a/mParticle-Apple-SDK/Utils/MPConvertJS.m +++ b/mParticle-Apple-SDK/Utils/MPConvertJS.m @@ -6,8 +6,6 @@ @implementation MPConvertJS_PRIVATE + (MPCommerceEventAction)commerceEventAction:(NSNumber *)json { - MPCommerceEventAction action; - int actionInt = [json intValue]; switch (actionInt) { case MPJSCommerceEventActionAddToCart: From 42a96274be037da795724ceb05d084994149256a Mon Sep 17 00:00:00 2001 From: Nickolas Dimitrakas Date: Fri, 30 Jan 2026 16:36:17 -0500 Subject: [PATCH 4/6] add import to tests --- UnitTests/ObjCTests/MPConvertJSTests.m | 1 + 1 file changed, 1 insertion(+) diff --git a/UnitTests/ObjCTests/MPConvertJSTests.m b/UnitTests/ObjCTests/MPConvertJSTests.m index f55139c97..ab3d52c03 100644 --- a/UnitTests/ObjCTests/MPConvertJSTests.m +++ b/UnitTests/ObjCTests/MPConvertJSTests.m @@ -1,4 +1,5 @@ #import +#import "MPConvertJS.h" #import "mParticle.h" #import "MPBaseTestCase.h" #import "MParticleSwift.h" From c08ed8bcd3c501d12b402b927e1bb7f21d1eb5da Mon Sep 17 00:00:00 2001 From: Nickolas Dimitrakas Date: Fri, 30 Jan 2026 16:37:54 -0500 Subject: [PATCH 5/6] fix run analyzer error --- mParticle-Apple-SDK/Utils/MPConvertJS.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mParticle-Apple-SDK/Utils/MPConvertJS.m b/mParticle-Apple-SDK/Utils/MPConvertJS.m index 266bfa6dd..2f1c4ebb0 100644 --- a/mParticle-Apple-SDK/Utils/MPConvertJS.m +++ b/mParticle-Apple-SDK/Utils/MPConvertJS.m @@ -302,7 +302,7 @@ + (MPIdentityApiRequest *)identityApiRequest:(NSDictionary *)json { return; } - if (!identityTypeNumber || ![identityTypeNumber isKindOfClass:[NSNumber class]]) { + if (identityTypeNumber == nil || ![identityTypeNumber isKindOfClass:[NSNumber class]]) { allSuccess = NO; *stop = YES; return; From eec325507cf2449c9fb6918b0df5c398d049b17c Mon Sep 17 00:00:00 2001 From: Nickolas Dimitrakas Date: Fri, 30 Jan 2026 19:30:32 -0500 Subject: [PATCH 6/6] code cleanup + address @jamesnrokt comments --- mParticle-Apple-SDK/Utils/MPConvertJS.m | 281 ++++++++++++++---------- 1 file changed, 164 insertions(+), 117 deletions(-) diff --git a/mParticle-Apple-SDK/Utils/MPConvertJS.m b/mParticle-Apple-SDK/Utils/MPConvertJS.m index 2f1c4ebb0..a428a437d 100644 --- a/mParticle-Apple-SDK/Utils/MPConvertJS.m +++ b/mParticle-Apple-SDK/Utils/MPConvertJS.m @@ -35,10 +35,6 @@ + (MPCommerceEventAction)commerceEventAction:(NSNumber *)json { } + (MPCommerceEvent *)commerceEvent:(NSDictionary *)json { - if (!json || ![json isKindOfClass:[NSDictionary class]]) { - MPILogError(@"Unexpected commerce event data received from webview"); - return nil; - } if (json[@"ProductAction"] != nil && ![json[@"ProductAction"] isKindOfClass:[NSDictionary class]]) { MPILogError(@"Unexpected commerce event data received from webview"); return nil; @@ -72,73 +68,110 @@ + (MPCommerceEvent *)commerceEvent:(NSDictionary *)json { else { commerceEvent = [[MPCommerceEvent alloc] initWithImpressionName:nil product:nil]; } - - if ((NSNull *)json[@"EventAttributes"] != [NSNull null]) { - commerceEvent.customAttributes = json[@"EventAttributes"]; + + id eventAttributes = json[@"EventAttributes"]; + if ([eventAttributes isKindOfClass:[NSDictionary class]]) { + commerceEvent.customAttributes = (NSDictionary *)eventAttributes; } - if ((NSNull *)json[@"CheckoutOptions"] != [NSNull null]) { - commerceEvent.checkoutOptions = json[@"CheckoutOptions"]; + + id checkoutOptionsObj = json[@"CheckoutOptions"]; + if ([checkoutOptionsObj isKindOfClass:[NSString class]]) { + commerceEvent.checkoutOptions = (NSString *)checkoutOptionsObj; } - if ((NSNull *)json[@"productActionListName"] != [NSNull null]) { - commerceEvent.productListName = json[@"productActionListName"]; + + id productActionListNameObj = json[@"productActionListName"]; + if ([productActionListNameObj isKindOfClass:[NSString class]]) { + commerceEvent.productListName = (NSString *)productActionListNameObj; } - if ((NSNull *)json[@"productActionListSource"] != [NSNull null]) { - commerceEvent.productListSource = json[@"productActionListSource"]; + + id productActionListSourceObj = json[@"productActionListSource"]; + if ([productActionListSourceObj isKindOfClass:[NSString class]]) { + commerceEvent.productListSource = (NSString *)productActionListSourceObj; } - if ((NSNull *)json[@"CurrencyCode"] != [NSNull null]) { - commerceEvent.currency = json[@"CurrencyCode"]; + + id currencyCodeObj = json[@"CurrencyCode"]; + if ([currencyCodeObj isKindOfClass:[NSString class]]) { + commerceEvent.currency = (NSString *)currencyCodeObj; } - if ((NSNull *)productAction != [NSNull null]) { - commerceEvent.transactionAttributes = [MPConvertJS_PRIVATE transactionAttributes:productAction]; + + if (productAction != nil) { + commerceEvent.transactionAttributes = [self transactionAttributes:productAction]; } - if ([json[@"CheckoutStep"] isKindOfClass:[NSNumber class]]) { - commerceEvent.checkoutStep = [json[@"CheckoutStep"] intValue]; + + id checkoutStepObj = json[@"CheckoutStep"]; + if ([checkoutStepObj isKindOfClass:[NSNumber class]]) { + commerceEvent.checkoutStep = [(NSNumber *)checkoutStepObj intValue]; } - if ((NSNull *)json[@"CustomFlags"] != [NSNull null]) { - NSDictionary *customFlags = json[@"CustomFlags"]; - for (NSString *key in customFlags.allKeys) { + + id customFlagsObj = json[@"CustomFlags"]; + if ([customFlagsObj isKindOfClass:[NSDictionary class]]) { + NSDictionary *customFlags = (NSDictionary *)customFlagsObj; + + for (id key in customFlags) { id value = customFlags[key]; + if ([value isKindOfClass:[NSArray class]]) { - [commerceEvent addCustomFlags:(NSArray *)value withKey:key]; + BOOL allStrings = YES; + for (id item in (NSArray *)value) { + if (![item isKindOfClass:[NSString class]]) { allStrings = NO; break; } + } + if (allStrings && [key isKindOfClass:[NSString class]]) { + [commerceEvent addCustomFlags:(NSArray *)value withKey:(NSString *)key]; + } + } else if ([value isKindOfClass:[NSString class]]) { - [commerceEvent addCustomFlag:(NSString *)value withKey:key]; + if ([key isKindOfClass:[NSString class]]) { + [commerceEvent addCustomFlag:(NSString *)value withKey:(NSString *)key]; + } } } } - NSArray *jsonProducts = productAction[@"ProductList"]; - if ((NSNull *)jsonProducts != [NSNull null] && [jsonProducts isKindOfClass:[NSArray class]]) { - NSMutableArray *products = [NSMutableArray array]; - [jsonProducts enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - MPProduct *product = [MPConvertJS_PRIVATE product:obj]; - [products addObject:product]; - }]; + id productListObj = productAction[@"ProductList"]; + if ([productListObj isKindOfClass:[NSArray class]]) { + NSArray *jsonProducts = (NSArray *)productListObj; + + NSMutableArray *products = [NSMutableArray arrayWithCapacity:jsonProducts.count]; + for (id item in jsonProducts) { + if (![item isKindOfClass:[NSDictionary class]]) { continue; } + + MPProduct *p = [self product:(NSDictionary *)item]; + if (p) { [products addObject:p]; } + } [commerceEvent addProducts:products]; } - NSArray *jsonImpressions = json[@"ProductImpressions"]; - if ((NSNull *)jsonImpressions != [NSNull null] && [jsonImpressions isKindOfClass:[NSArray class]]) { - [jsonImpressions enumerateObjectsUsingBlock:^(NSDictionary *jsonImpression, NSUInteger idx, BOOL * _Nonnull stop) { - NSString *listName = jsonImpression[@"ProductImpressionList"]; - NSArray *jsonProducts = jsonImpression[@"ProductList"]; - if ((NSNull *)jsonProducts != [NSNull null] && [jsonProducts isKindOfClass:[NSArray class]]) { - [jsonProducts enumerateObjectsUsingBlock:^(id _Nonnull jsonProduct, NSUInteger idx, BOOL * _Nonnull stop) { - MPProduct *product = [MPConvertJS_PRIVATE product:jsonProduct]; + id impressionsObj = json[@"ProductImpressions"]; + if ([impressionsObj isKindOfClass:[NSArray class]]) { + NSArray *jsonImpressions = (NSArray *)impressionsObj; + + for (id impressionItem in jsonImpressions) { + if (![impressionItem isKindOfClass:[NSDictionary class]]) { continue; } + + NSDictionary *jsonImpression = (NSDictionary *)impressionItem; + id listNameObj = jsonImpression[@"ProductImpressionList"]; + id impressionProductsObj = jsonImpression[@"ProductList"]; + + if ([listNameObj isKindOfClass:[NSString class]] && + [impressionProductsObj isKindOfClass:[NSArray class]]) { + + NSString *listName = (NSString *)listNameObj; + NSArray *impressionProducts = (NSArray *)impressionProductsObj; + + for (id prodItem in impressionProducts) { + if (![prodItem isKindOfClass:[NSDictionary class]]) { continue; } + + MPProduct *product = [MPConvertJS_PRIVATE product:(NSDictionary *)prodItem]; [commerceEvent addImpression:product listName:listName]; - }]; + } } - }]; + } } return commerceEvent; } + (MPPromotionContainer *)promotionContainer:(NSDictionary *)json { - if (!json || ![json isKindOfClass:[NSDictionary class]]) { - MPILogError(@"Unexpected promotion container data received from webview"); - return nil; - } - NSDictionary *promotionActionDictionary = json[@"PromotionAction"]; if (!promotionActionDictionary || ![promotionActionDictionary isKindOfClass:[NSDictionary class]]) { MPILogError(@"Unexpected promotion container action data received from webview"); @@ -170,119 +203,133 @@ + (MPPromotionContainer *)promotionContainer:(NSDictionary *)json { } + (MPPromotion *)promotion:(NSDictionary *)json { - if (!json || ![json isKindOfClass:[NSDictionary class]]) { - MPILogError(@"Unexpected promotion data received from webview"); - return nil; - } MPPromotion *promotion = [[MPPromotion alloc] init]; - if ((NSNull *)json[@"Creative"] != [NSNull null]) { - promotion.creative = json[@"Creative"]; + id creative = json[@"Creative"]; + if ([creative isKindOfClass:[NSString class]]) { + promotion.creative = (NSString *)creative; } - - if ((NSNull *)json[@"Name"] != [NSNull null]) { - promotion.name = json[@"Name"]; + + id name = json[@"Name"]; + if ([name isKindOfClass:[NSString class]]) { + promotion.name = (NSString *)name; } - - if ((NSNull *)json[@"Position"] != [NSNull null]) { - promotion.position = json[@"Position"]; + + id position = json[@"Position"]; + if ([position isKindOfClass:[NSString class]]) { + promotion.position = (NSString *)position; } - - if ((NSNull *)json[@"Id"] != [NSNull null]) { - promotion.promotionId = json[@"Id"]; + + id promoId = json[@"Id"]; + if ([promoId isKindOfClass:[NSString class]]) { + promotion.promotionId = (NSString *)promoId; } return promotion; } + (MPTransactionAttributes *)transactionAttributes:(NSDictionary *)json { - if (!json || ![json isKindOfClass:[NSDictionary class]]) { - json = @{}; - } MPTransactionAttributes *transactionAttributes = [[MPTransactionAttributes alloc] init]; - if ((NSNull *)json[@"Affiliation"] != [NSNull null]) { - transactionAttributes.affiliation = json[@"Affiliation"]; + id affiliation = json[@"Affiliation"]; + if ([affiliation isKindOfClass:[NSString class]]) { + transactionAttributes.affiliation = (NSString *)affiliation; } - if ((NSNull *)json[@"CouponCode"] != [NSNull null]) { - transactionAttributes.couponCode = json[@"CouponCode"]; + + id couponCode = json[@"CouponCode"]; + if ([couponCode isKindOfClass:[NSString class]]) { + transactionAttributes.couponCode = (NSString *)couponCode; } - if ((NSNull *)json[@"ShippingAmount"] != [NSNull null]) { - transactionAttributes.shipping = json[@"ShippingAmount"]; + + id shippingAmount = json[@"ShippingAmount"]; + if ([shippingAmount isKindOfClass:[NSNumber class]]) { + transactionAttributes.shipping = (NSNumber *)shippingAmount; } - if ((NSNull *)json[@"TaxAmount"] != [NSNull null]) { - transactionAttributes.tax = json[@"TaxAmount"]; + + id taxAmount = json[@"TaxAmount"]; + if ([taxAmount isKindOfClass:[NSNumber class]]) { + transactionAttributes.tax = (NSNumber *)taxAmount; } - if ((NSNull *)json[@"TotalAmount"] != [NSNull null]) { - transactionAttributes.revenue = json[@"TotalAmount"]; + + id totalAmount = json[@"TotalAmount"]; + if ([totalAmount isKindOfClass:[NSNumber class]]) { + transactionAttributes.revenue = (NSNumber *)totalAmount; } - if ((NSNull *)json[@"TransactionId"] != [NSNull null]) { - transactionAttributes.transactionId = json[@"TransactionId"]; + + id transactionId = json[@"TransactionId"]; + if ([transactionId isKindOfClass:[NSString class]]) { + transactionAttributes.transactionId = (NSString *)transactionId; } - + return transactionAttributes; } + (MPProduct *)product:(NSDictionary *)json { - if (!json || ![json isKindOfClass:[NSDictionary class]]) { - MPILogError(@"Unexpected product data received from webview"); - return nil; - } MPProduct *product = [[MPProduct alloc] init]; - - if ((NSNull *)json[@"Brand"] != [NSNull null]) { - product.brand = json[@"Brand"]; + + id brand = json[@"Brand"]; + if ([brand isKindOfClass:[NSString class]]) { + product.brand = (NSString *)brand; } - if ((NSNull *)json[@"Category"] != [NSNull null]) { - product.category = json[@"Category"]; + + id category = json[@"Category"]; + if ([category isKindOfClass:[NSString class]]) { + product.category = (NSString *)category; } - if ((NSNull *)json[@"CouponCode"] != [NSNull null]) { - product.couponCode = json[@"CouponCode"]; + + id couponCode = json[@"CouponCode"]; + if ([couponCode isKindOfClass:[NSString class]]) { + product.couponCode = (NSString *)couponCode; } - if ((NSNull *)json[@"Name"] != [NSNull null]) { - product.name = json[@"Name"]; + + id name = json[@"Name"]; + if ([name isKindOfClass:[NSString class]]) { + product.name = (NSString *)name; } - - // Handle price as NSNumber or String - if (!json[@"Price"] || [json[@"Price"] isKindOfClass:[NSNumber class]]) { - product.price = json[@"Price"]; - } else if ([json[@"Price"] isKindOfClass:[NSString class]]) { - product.price = [NSNumber numberWithDouble:[(NSString *)json[@"Price"] doubleValue]]; + + id price = json[@"Price"]; + if ([price isKindOfClass:[NSNumber class]]) { + product.price = (NSNumber *)price; + } else if ([price isKindOfClass:[NSString class]]) { + product.price = @([(NSString *)price doubleValue]); } - - if ((NSNull *)json[@"Sku"] != [NSNull null]) { - product.sku = json[@"Sku"]; + + id sku = json[@"Sku"]; + if ([sku isKindOfClass:[NSString class]]) { + product.sku = (NSString *)sku; } - if ((NSNull *)json[@"Variant"] != [NSNull null]) { - product.variant = json[@"Variant"]; + + id variant = json[@"Variant"]; + if ([variant isKindOfClass:[NSString class]]) { + product.variant = (NSString *)variant; } - if ((NSNull *)json[@"Position"] != [NSNull null]) { - product.position = [json[@"Position"] unsignedIntValue]; + + id position = json[@"Position"]; + if ([position isKindOfClass:[NSNumber class]]) { + product.position = [(NSNumber *)position unsignedIntValue]; } - if (!json[@"Quantity"] || [json[@"Quantity"] isKindOfClass:[NSNumber class]]) { - product.quantity = json[@"Quantity"]; + + id quantity = json[@"Quantity"]; + if ([quantity isKindOfClass:[NSNumber class]]) { + product.quantity = (NSNumber *)quantity; } - NSDictionary *jsonAttributes = json[@"Attributes"]; - if ((NSNull *)jsonAttributes != [NSNull null] && [jsonAttributes isKindOfClass:[NSDictionary class]]) { - for (NSString *key in jsonAttributes) { - NSString *value = jsonAttributes[key]; - if ((NSNull *)value != [NSNull null]) { - [product setObject:value forKeyedSubscript:key]; + id attributes = json[@"Attributes"]; + if ([attributes isKindOfClass:[NSDictionary class]]) { + NSDictionary *jsonAttributes = (NSDictionary *)attributes; + [jsonAttributes enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + if ([key isKindOfClass:[NSString class]] && [obj isKindOfClass:[NSString class]]) { + [product setValue:obj forKey:key]; } - } + }]; } + return product; } + (MPIdentityApiRequest *)identityApiRequest:(NSDictionary *)json { MPIdentityApiRequest *request = [MPIdentityApiRequest requestWithEmptyUser]; - if (!json || ![json isKindOfClass:[NSDictionary class]]) { - MPILogError(@"Unexpected identity api request data received from webview"); - return nil; - } - NSArray *userIdentities = json[@"UserIdentities"]; if (!userIdentities || ![userIdentities isKindOfClass:[NSArray class]]) { MPILogError(@"Unexpected user identity data received from webview");