diff --git a/Sources/Purchasing/Offering.swift b/Sources/Purchasing/Offering.swift index 109b9a2a7a..867c012427 100644 --- a/Sources/Purchasing/Offering.swift +++ b/Sources/Purchasing/Offering.swift @@ -215,10 +215,8 @@ import Foundation extension Offering { - /** - - Returns: the `metadata` value associated to `key` for the expected type, - or `default` if not found, or it's not the expected type. - */ + /// - Returns: The `metadata` value associated to `key` for the expected type, + /// or `default` if not found or it's not the expected type. public func getMetadataValue(for key: String, default: T) -> T { guard let rawValue = self.metadata[key], let value = rawValue as? T else { return `default` @@ -226,6 +224,25 @@ extension Offering { return value } + /// - Returns: The `metadata` value associated to `key` for the expected `Decodable` type, + /// or `nil` if not found. + /// - Throws: Error if the content couldn't be deserialized to the expected type. + /// - Note: This decodes JSON using `JSONDecoder.KeyDecodingStrategy.convertFromSnakeCase`. + public func getMetadataValue(for key: String) -> T? { + do { + guard let value = self.metadata[key] else { return nil } + let data = try JSONSerialization.data(withJSONObject: value) + + return try JSONDecoder.default.decode( + T.self, + jsonData: data, + logErrors: true + ) + } catch { + return nil + } + } + } extension Offering: Identifiable { diff --git a/Tests/APITesters/SwiftAPITester/SwiftAPITester/OfferingAPI.swift b/Tests/APITesters/SwiftAPITester/SwiftAPITester/OfferingAPI.swift index dae1ca4e34..0d442494c3 100644 --- a/Tests/APITesters/SwiftAPITester/SwiftAPITester/OfferingAPI.swift +++ b/Tests/APITesters/SwiftAPITester/SwiftAPITester/OfferingAPI.swift @@ -16,6 +16,8 @@ import RevenueCat var off: Offering! func checkOfferingAPI() { + struct Data: Decodable {} + let ident: String = off.identifier let sDesc: String = off.serverDescription let aPacks: [Package] = off.availablePackages @@ -33,10 +35,12 @@ func checkOfferingAPI() { let metadataString: String = off.getMetadataValue(for: "", default: "") let metadataInt: Int = off.getMetadataValue(for: "", default: 0) let metadataOptionalInt: Int? = off.getMetadataValue(for: "", default: nil) + let metadataDecodable: Data? = off.getMetadataValue(for: "") let _: PaywallData? = off.paywall print(off!, ident, sDesc, aPacks, lPack!, annPack!, smPack!, thmPack!, twmPack!, - mPack!, wPack!, pPack!, package!, metadata, metadataString, metadataInt, metadataOptionalInt!) + mPack!, wPack!, pPack!, package!, metadata, metadataString, metadataInt, metadataOptionalInt!, + metadataDecodable!) } private func checkCreateOfferingAPI(package: Package) { diff --git a/Tests/UnitTests/Purchasing/OfferingsTests.swift b/Tests/UnitTests/Purchasing/OfferingsTests.swift index 76126eb8e9..c18e8061ed 100644 --- a/Tests/UnitTests/Purchasing/OfferingsTests.swift +++ b/Tests/UnitTests/Purchasing/OfferingsTests.swift @@ -174,6 +174,17 @@ class OfferingsTests: TestCase { "array": ["five"], "dictionary": [ "string": "five" + ], + "elements": [ + [ + "number": 1 + ], + [ + "number": 2 + ] + ], + "element": [ + "number": 3 ] ] @@ -212,7 +223,7 @@ class OfferingsTests: TestCase { expect(offerings.current) == offerings["offering_a"] let offeringA = try XCTUnwrap(offerings["offering_a"]) - expect(offeringA.metadata).to(haveCount(6)) + expect(offeringA.metadata).to(haveCount(8)) expect(offeringA.getMetadataValue(for: "int", default: 0)) == 5 expect(offeringA.getMetadataValue(for: "double", default: 0.0)) == 5.5 expect(offeringA.getMetadataValue(for: "boolean", default: false)) == true @@ -225,6 +236,31 @@ class OfferingsTests: TestCase { let wrongMetadataType = offeringA.getMetadataValue(for: "string", default: 5.5) expect(wrongMetadataType) == 5.5 + + struct Data: Decodable, Equatable { + var number: Int + } + + let elements: [Data]? = offeringA.getMetadataValue(for: "elements") + expect(elements) == [.init(number: 1), .init(number: 2)] + + let element: Data? = offeringA.getMetadataValue(for: "element") + expect(element) == .init(number: 3) + + let missing: Data? = offeringA.getMetadataValue(for: "missing") + expect(missing).to(beNil()) + + do { + let logger = TestLogHandler() + + expect(offeringA.getMetadataValue(for: "dictionary") as Data?) + .to(beNil()) + + logger.verifyMessageWasLogged("Error deserializing `Data`", + level: .debug, + expectedCount: 1) + } + } func testLifetimePackage() throws {