diff --git a/Networking/Networking/Model/Product/Product.swift b/Networking/Networking/Model/Product/Product.swift index 7c325a749ec..c6ac90d2489 100644 --- a/Networking/Networking/Model/Product/Product.swift +++ b/Networking/Networking/Model/Product/Product.swift @@ -357,7 +357,12 @@ public struct Product: Codable, GeneratedCopiable, Equatable, GeneratedFakeable let fullDescription = try container.decodeIfPresent(String.self, forKey: .fullDescription) let shortDescription = try container.decodeIfPresent(String.self, forKey: .shortDescription) - let sku = try container.decodeIfPresent(String.self, forKey: .sku) + + // Even though a plain install of WooCommerce Core provides String values, + // some plugins alter the field value from String to Int or Decimal. + let sku = container.failsafeDecodeIfPresent(targetType: String.self, + forKey: .sku, + alternativeTypes: [.decimal(transform: { NSDecimalNumber(decimal: $0).stringValue })]) // Even though a plain install of WooCommerce Core provides string values, // some plugins alter the field value from String to Int or Decimal. @@ -424,7 +429,12 @@ public struct Product: Codable, GeneratedCopiable, Equatable, GeneratedFakeable let backordered = try container.decode(Bool.self, forKey: .backordered) let soldIndividually = try container.decodeIfPresent(Bool.self, forKey: .soldIndividually) ?? false - let weight = try container.decodeIfPresent(String.self, forKey: .weight) + + // Even though a plain install of WooCommerce Core provides String values, + // some plugins alter the field value from String to Int or Decimal. + let weight = container.failsafeDecodeIfPresent(targetType: String.self, + forKey: .weight, + alternativeTypes: [.decimal(transform: { NSDecimalNumber(decimal: $0).stringValue })]) let dimensions = try container.decode(ProductDimensions.self, forKey: .dimensions) let shippingRequired = try container.decode(Bool.self, forKey: .shippingRequired) diff --git a/Networking/Networking/Model/Product/ProductDimensions.swift b/Networking/Networking/Model/Product/ProductDimensions.swift index 9999b92d016..e861c194bef 100644 --- a/Networking/Networking/Model/Product/ProductDimensions.swift +++ b/Networking/Networking/Model/Product/ProductDimensions.swift @@ -17,6 +17,24 @@ public struct ProductDimensions: Codable, Equatable, GeneratedFakeable { self.width = width self.height = height } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + // Even though a plain install of WooCommerce Core provides String dimension values, + // some plugins may alter the field values from String to Int or Decimal. + let length = container.failsafeDecodeIfPresent(targetType: String.self, + forKey: .length, + alternativeTypes: [.decimal(transform: { NSDecimalNumber(decimal: $0).stringValue })]) ?? "" + let width = container.failsafeDecodeIfPresent(targetType: String.self, + forKey: .width, + alternativeTypes: [.decimal(transform: { NSDecimalNumber(decimal: $0).stringValue })]) ?? "" + let height = container.failsafeDecodeIfPresent(targetType: String.self, + forKey: .height, + alternativeTypes: [.decimal(transform: { NSDecimalNumber(decimal: $0).stringValue })]) ?? "" + + self.init(length: length, width: width, height: height) + } } /// Defines all of the Dimensions CodingKeys diff --git a/Networking/Networking/Model/Product/ProductDownload.swift b/Networking/Networking/Model/Product/ProductDownload.swift index 73c116db1e2..09378c2ad5d 100644 --- a/Networking/Networking/Model/Product/ProductDownload.swift +++ b/Networking/Networking/Model/Product/ProductDownload.swift @@ -23,7 +23,11 @@ public struct ProductDownload: Codable, Equatable, GeneratedFakeable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - let downloadID = try container.decode(String.self, forKey: .downloadID) + // Even though a plain install of WooCommerce Core provides String values, + // some plugins alter the field value from String to Int or Decimal. + let downloadID = container.failsafeDecodeIfPresent(targetType: String.self, + forKey: .downloadID, + alternativeTypes: [.decimal(transform: { NSDecimalNumber(decimal: $0).stringValue })]) ?? "0" let name = try container.decodeIfPresent(String.self, forKey: .name) let fileURL = try container.decodeIfPresent(String.self, forKey: .fileURL) diff --git a/Networking/NetworkingTests/Mapper/ProductMapperTests.swift b/Networking/NetworkingTests/Mapper/ProductMapperTests.swift index 22ba41eb71a..563f2f6b70c 100644 --- a/Networking/NetworkingTests/Mapper/ProductMapperTests.swift +++ b/Networking/NetworkingTests/Mapper/ProductMapperTests.swift @@ -117,6 +117,12 @@ final class ProductMapperTests: XCTestCase { XCTAssertFalse(product.soldIndividually) XCTAssertTrue(product.purchasable) XCTAssertEqual(product.permalink, "") + XCTAssertEqual(product.sku, "123") + XCTAssertEqual(product.weight, "213") + XCTAssertEqual(product.dimensions.length, "12") + XCTAssertEqual(product.dimensions.width, "33") + XCTAssertEqual(product.dimensions.height, "54") + XCTAssertEqual(product.downloads.first?.downloadID, "12345") } /// Verifies that the `salePrice` field of the Product are parsed correctly when the product is on sale, and the sale price is an empty string diff --git a/Networking/NetworkingTests/Responses/product-alternative-types.json b/Networking/NetworkingTests/Responses/product-alternative-types.json index 08f6d14fba5..dfad347304f 100644 --- a/Networking/NetworkingTests/Responses/product-alternative-types.json +++ b/Networking/NetworkingTests/Responses/product-alternative-types.json @@ -14,7 +14,7 @@ "catalog_visibility": "visible", "description": "
This is the party room!
\n", "short_description": "[contact-form]\nThe green room’s max capacity is 30 people. Reserving the date / time of your event is free. We can also accommodate large groups, with seating for 85 board game players at a time. If you have a large group, let us know and we’ll send you our large group rate.
\nGROUP RATES
\nReserve your event for up to 30 guests for $100.
\n", - "sku": "", + "sku": 123, "price": 17, "regular_price": 12.89, "sale_price": 26.73, @@ -28,7 +28,11 @@ "total_sales": 0, "virtual": true, "downloadable": false, - "downloads": [], + "downloads": [{ + "id" : 12345, + "name" : "Song #1", + "file" : "https://example.com/woo-single-1.ogg" + }], "download_limit": -1, "download_expiry": -1, "external_url": "http://somewhere.com", @@ -42,11 +46,11 @@ "backorders_allowed": false, "backordered": false, "sold_individually": null, - "weight": "213", + "weight": 213, "dimensions": { - "length": "12", - "width": "33", - "height": "54" + "length": 12, + "width": 33, + "height": 54 }, "shipping_required": false, "shipping_taxable": false, diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index e4e3bb9b543..06e38896aa9 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -3,6 +3,7 @@ 13.9 ----- - [*] Payments: Location permissions request is not shown to TTP users who grant "Allow once" permission on first foregrounding the app any more [https://github.com/woocommerce/woocommerce-ios/pull/9821] +- [*] Products: Allow alternative types for the `sku` and `weight` in `Product`, the dimensions in `ProductDimensions`, and the `downloadID` in `ProductDownload`, as some third-party plugins alter the types in the API. This helps with the product list not loading due to product decoding errors. [https://github.com/woocommerce/woocommerce-ios/pull/9846] 13.8 -----