Created @LossyArray and @LossyDictionary#1543
Conversation
LossyArray and LossyDictionaryLossyArray and LossyDictionary
177e79e to
ba4dadf
Compare
02a2b09 to
db374f2
Compare
There was a problem hiding this comment.
These tests check that both property wrappers can be composed.
LossyArray and LossyDictionaryLossyArray and LossyDictionary
db374f2 to
1819dc6
Compare
LossyArray and LossyDictionary@LossyArray and @LossyDictionary
ba4dadf to
258721e
Compare
1819dc6 to
f7315e1
Compare
- Removed all custom deserialization code thanks to #1537, #1541, and #1543. - The format of the customer info response is now very concise and only in one place (`CustomerInfoResponse`) instead of being passed through dictionaries and magic strings everywhere. - `CustomerInfo` was combining the dictionaries inside of `"subscriptions"` and `"non_subscriptions"`, despite them having different formats. This is now made explicit through type conversions between the two, (see `CustomerInfoResponse.allTransactionsByProductId`). - Removed all custom `CustomerInfo` errors since deserialization is automatic, and the underlying `Error` information will be provided thanks to `ErrorUtils.logDecodingError`. - `CustomerInfo` deserialization always wraps errors into `ErrorCode.customerInfoError`, which is now also covered in a new test. - Added a new snapshot test that covers `CustomerInfo` serialization by storing the result in a JSON file. - `CustomerInfo` equality is now automatic through the `CustomerInfoResponse` `Equatable` conformance. - Simplified `schemaVersion` handling - All tests still use `CustomerInfo(data: [String: Any])`, but that method is only visible for tests now, and it uses the underlying `Decodable` implementation (see `CustomerInfo+TestExtensions.swift`). - Added extra tests for `CustomerInfoResponse` that use a fixture `CustomerInfo.json`, which is easier to maintain than having JSON in code. - Improved definition of the protocol so that our types only have to implement one method: ```swift extension CustomerInfo: RawDataContainer { @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, *) public var underlyingData: some Encodable { return self.data.response } } ``` - The protocol will provide the `rawData` public method through the default implementation automatically. - The only downside is that this needs to be iOS 13+ only because of `some Encodable`, which allows us to implement this without needing to leak the underlying type.
joshdholtz
left a comment
There was a problem hiding this comment.
This looks awesome 😍 A few small questions but otherwise this looks 💯 and good to merge!
| @propertyWrapper | ||
| struct LossyArrayDictionary<Value: Decodable> { | ||
|
|
||
| var wrappedValue: [String: [Value]] |
There was a problem hiding this comment.
Is it safe to assume the key will always be a String? I mean, I can't see why it wouldn't be in any of our cases but not sure if that something we want to open up 🤷♂️
There was a problem hiding this comment.
Good question. Swift's CodingKey allows keys to be String or Int, in a way that Integer keys can be implicitly converted to String when decoding JSON dictionaries. We don't use that, so for simplicity these property wrappers assume just String, but it would be trivial to change these by adding a CodingKey associatedtype so that integer keys are supported as well.
|
|
||
| class DecoderExtensionsLossyCollectionTests: XCTestCase { | ||
|
|
||
| private struct Data: Codable, Equatable { |
There was a problem hiding this comment.
I don't think this needs to be changed but I was confused at how Data was being used thinking that all the Data objects in the tests were the NSData type of Data objects and then not realizing there is a custom Data struct here.
I would also name this something like TestData or something but that is just kind of a personal nit 🙃
There was a problem hiding this comment.
Well this isn't just Data (as in the global namespace), it's DecoderExtensionsLossyCollectionTests.Data (it's namespaced inside the test class).
So in a way, it is like "Test Data". Does that make sense?
258721e to
d93db85
Compare
These property wrappers allows decoding `Array`s and `Dictionary`s and ignore elements that fail to decode.
For example, this will ignore any elements that aren't numbers, instead of failing to decode altogether.
```swift
struct Data: Decodable {
@LossyArray var list: [Int]
@LossyDictionary var map: [String: Int]
}
```
This does require that the values are the right type, but these wrappers can be composed with `@DefaultDecodable.EmptyArray` and `@DefaultDecodable.EmptyDictionary` introduced in #1537 to make it produce an empty array in case of any other type error:
```swift
struct Data: Decodable {
@DefaultDecodable.EmptyArray @LossyArray var list: [Int]
@DefaultDecodable.EmptyDictionary @LossyDictionary var map: [String: Int]
}
```
Because of limitations of the property wrappers in Swift, an extra type `LossyArrayDictionary` allows lossy decoding of types `[String: [Decodable]`.
f7315e1 to
10b1caf
Compare
- Removed all custom deserialization code thanks to #1537, #1541, and #1543. - The format of the customer info response is now very concise and only in one place (`CustomerInfoResponse`) instead of being passed through dictionaries and magic strings everywhere. - `CustomerInfo` was combining the dictionaries inside of `"subscriptions"` and `"non_subscriptions"`, despite them having different formats. This is now made explicit through type conversions between the two, (see `CustomerInfoResponse.allTransactionsByProductId`). - Removed all custom `CustomerInfo` errors since deserialization is automatic, and the underlying `Error` information will be provided thanks to `ErrorUtils.logDecodingError`. - `CustomerInfo` deserialization always wraps errors into `ErrorCode.customerInfoError`, which is now also covered in a new test. - Added a new snapshot test that covers `CustomerInfo` serialization by storing the result in a JSON file. - `CustomerInfo` equality is now automatic through the `CustomerInfoResponse` `Equatable` conformance. - Simplified `schemaVersion` handling - All tests still use `CustomerInfo(data: [String: Any])`, but that method is only visible for tests now, and it uses the underlying `Decodable` implementation (see `CustomerInfo+TestExtensions.swift`). - Added extra tests for `CustomerInfoResponse` that use a fixture `CustomerInfo.json`, which is easier to maintain than having JSON in code. - Improved definition of the protocol so that our types only have to implement one method: ```swift extension CustomerInfo: RawDataContainer { @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, *) public var underlyingData: some Encodable { return self.data.response } } ``` - The protocol will provide the `rawData` public method through the default implementation automatically. - The only downside is that this needs to be iOS 13+ only because of `some Encodable`, which allows us to implement this without needing to leak the underlying type.
- Removed all custom deserialization code thanks to #1537, #1541, and #1543. - The format of the customer info response is now very concise and only in one place (`CustomerInfoResponse`) instead of being passed through dictionaries and magic strings everywhere. - `CustomerInfo` was combining the dictionaries inside of `"subscriptions"` and `"non_subscriptions"`, despite them having different formats. This is now made explicit through type conversions between the two, (see `CustomerInfoResponse.allTransactionsByProductId`). - Removed all custom `CustomerInfo` errors since deserialization is automatic, and the underlying `Error` information will be provided thanks to `ErrorUtils.logDecodingError`. - `CustomerInfo` deserialization always wraps errors into `ErrorCode.customerInfoError`, which is now also covered in a new test. - Added a new snapshot test that covers `CustomerInfo` serialization by storing the result in a JSON file. - `CustomerInfo` equality is now automatic through the `CustomerInfoResponse` `Equatable` conformance. - Simplified `schemaVersion` handling - All tests still use `CustomerInfo(data: [String: Any])`, but that method is only visible for tests now, and it uses the underlying `Decodable` implementation (see `CustomerInfo+TestExtensions.swift`). - Added extra tests for `CustomerInfoResponse` that use a fixture `CustomerInfo.json`, which is easier to maintain than having JSON in code. - Improved definition of the protocol so that our types only have to implement one method: ```swift extension CustomerInfo: RawDataContainer { @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, *) public var underlyingData: some Encodable { return self.data.response } } ``` - The protocol will provide the `rawData` public method through the default implementation automatically. - The only downside is that this needs to be iOS 13+ only because of `some Encodable`, which allows us to implement this without needing to leak the underlying type.
- Removed all custom deserialization code thanks to #1537, #1541, and #1543. - The format of the customer info response is now very concise and only in one place (`CustomerInfoResponse`) instead of being passed through dictionaries and magic strings everywhere. - `CustomerInfo` was combining the dictionaries inside of `"subscriptions"` and `"non_subscriptions"`, despite them having different formats. This is now made explicit through type conversions between the two, (see `CustomerInfoResponse.allTransactionsByProductId`). - Removed all custom `CustomerInfo` errors since deserialization is automatic, and the underlying `Error` information will be provided thanks to `ErrorUtils.logDecodingError`. - `CustomerInfo` deserialization always wraps errors into `ErrorCode.customerInfoError`, which is now also covered in a new test. - Added a new snapshot test that covers `CustomerInfo` serialization by storing the result in a JSON file. - `CustomerInfo` equality is now automatic through the `CustomerInfoResponse` `Equatable` conformance. - Simplified `schemaVersion` handling - All tests still use `CustomerInfo(data: [String: Any])`, but that method is only visible for tests now, and it uses the underlying `Decodable` implementation (see `CustomerInfo+TestExtensions.swift`). - Added extra tests for `CustomerInfoResponse` that use a fixture `CustomerInfo.json`, which is easier to maintain than having JSON in code. - Improved definition of the protocol so that our types only have to implement one method: ```swift extension CustomerInfo: RawDataContainer { @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, *) public var underlyingData: some Encodable { return self.data.response } } ``` - The protocol will provide the `rawData` public method through the default implementation automatically. - The only downside is that this needs to be iOS 13+ only because of `some Encodable`, which allows us to implement this without needing to leak the underlying type.
### Depends on: - #1537 - #1540 - #1541 - #1543 - #1546 - #1547 - #1550 - #1551, - #1565 ### Changes - Removed all custom deserialization code thanks to #1537, #1541, and #1543. - The format of the customer info response is now very concise and only in one place (`CustomerInfoResponse`) instead of being passed through dictionaries and magic strings everywhere. - `CustomerInfo` was combining the dictionaries inside of `"subscriptions"` and `"non_subscriptions"`, despite them having different formats. This is now made explicit through type conversions between the two, (see `CustomerInfoResponse.allTransactionsByProductId`). - Removed all custom `CustomerInfo` errors since deserialization is automatic, and the underlying `Error` information will be provided thanks to `ErrorUtils.logDecodingError`. - `CustomerInfo` deserialization always wraps errors into `ErrorCode.customerInfoError`, which is now also covered in a new test. - Added a new snapshot test that covers `CustomerInfo` serialization by storing the result in a JSON file. ### Other changes: - `CustomerInfo` equality is now automatic through the `CustomerInfoResponse` `Equatable` conformance. - Simplified `schemaVersion` handling - All tests still use `CustomerInfo(data: [String: Any])`, but that method is only visible for tests now, and it uses the underlying `Decodable` implementation (see `CustomerInfo+TestExtensions.swift`). - Added extra tests for `CustomerInfoResponse` that use a fixture `CustomerInfo.json`, which is easier to maintain than having JSON in code. - `RawDataContainer` implementation is changed in #1565. ### TODO: - [x] Fix all tests - [x] Update `RawValueContainer` - [x] Consider recovering `CustomerInfoError` - [x] Look at all previously logged errors - [x] Finish `CustomerInfoResponseTests` - [x] Handle all TODOs - [x] Make `RawDataContainer` actually store all the original data: #1565
Inspired by https://github.com/marksands/BetterCodable/blob/master/Sources/BetterCodable but supporting nested collections and with simplified code.
These property wrappers allows decoding
Arrays andDictionarys and ignore elements that fail to decode.For example, this will ignore any elements that aren't numbers, instead of failing to decode altogether.
This does require that the values are the right type, but these wrappers can be composed with
@DefaultDecodable.EmptyArrayand@DefaultDecodable.EmptyDictionaryintroduced in #1537 to make it produce an empty array in case of any other type error:Because of limitations of the property wrappers in Swift, an extra type
LossyArrayDictionaryallows lossy decoding of types[String: [Decodable].This will be used to vastly simplify #1496.