Swift Tests: Spying on `Encoder`? - swift

I have some Swift models that encode data to json. For example:
struct ExampleModel: Encodable {
var myComputedProperty: Bool { dependentModel.first(where: { $0.hasTrueProperty}) }
enum CodingKeys: String, CodingKey {
case firstKey = "first_key"
case secondKey = "second_key"
case myComputedProperty = "my_computed_property"
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(firstKey, forKey: .firstKey)
try container.encode(myComputedProperty, forKey: .myComputedProperty)
}
}
The encoded data is sent to an API, and cross-platform system tests in this case are logistically tricky, so it's important I write tests that ensure the encoded data is as expected. All I'm looking to do is ensure container.encode receives expected keys and values.
I'm somewhat new to Swift, and trying to override the container, its dependencies & generics, and its .encode method is taking me down a rabbit-hole of rewriting half Swift's encoding foundation. In short: the spy I'm writing is too complex to be useful.
Despite lack of Google/StackOverflow results, I'm guessing spying on encoders is common (?), and that there's an easier way to confirm container.encode receives expected values. But the way swift's Encoder functionality is written is making it hard for me to do so without rewriting half the encoder. Anyone have boilerplate code or an example of effectively spying on container.encode?

I have never done tests spying on encoder, although I have plenty of tests checking correction of encoding/decoding.
There are multiple ways to do it:
You can get something like SwiftyJSON and inspect encoded JSON
You can use OHHTTPStubs to mock sending a request to API, and examine the request the way it's sent (this allows to examine not only JSON, but headers as well)
But the most basic way to test is encoding and then decoding your data structure, and comparing the structures. They should be identical.
How it's done:
Create a Decodable extension for your struct (or if your struct is Codable, then you already ave this). It can be added directly in the test class / test target if you don't need it in the production code:
extension ExampleModel: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
firstKey = try container.decode(<...>.self, forKey: .firstKey)
// etc
}
}
Add Equatable extension for the struct:
extension ExampleModel: Equatable {
// Rely on synthesized comparison, or create your own
}
Add a test:
let given = ExampleModel(firstKey: ..., ...)
let whenEncoded = try JSONEncoder().encode(given)
let whenDecoded = try JSONDecoder().decode(ExampleModel.self, from: whenEncoded)
// Then
XCTAssertEqual(given, whenDecoded)
Couple of notes:
It's very unusual to encode computed property. It's not prohibited, but it breaks the immutability of the property you are about to send to API: what if it changes after you called encode, but before it was sent to API? Better solution is to make a let property in the struct, but have an option to create a struct with this value given with appropriate calculation (e.g. init for ExampleModel could be passing the dependentModel, and the property would be calculated in the init once)
If you still choose to have a calculated property, you obviously cannot decode into it. So in that case you will need to preserve the decoded property in some class variable to compare it separately.

Related

how to ignore incorrect types and continue json deserialization using swift codable?

I was wondering if there is an easy way to deal with the case where the type defined in my class does not match what I receive from the back-end. Documentation is scarce, so among the hundreds of parameters I've parsed I worry that there will be an error somewhere.
class Data : Codable {
var Validation: Bool?
var otherParam: String
}
For example, if validation is actually a string instead of a boolean, then parsing seems to just stop and fail with the default decoding.
My initial instinct is to override the decoding and use an optional try on each key. Is that the right idea?
Unfortunately, there isn't an easy way to do this. Type mismatches are fatal errors, and the whole thing will explode. You can do it by hand, but for your case that's likely extremely tedious. Just to show how to do it, though:
struct Data : Decodable {
enum CodingKeys: CodingKey {
case validation, otherParam
}
var validation: Bool?
var otherParam: String
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.validation = try? container.decodeIfPresent(Bool.self, forKey: .validation)
self.otherParam = try? container.decode(String.self, forKey: .otherParam) ?? ""
}
}
Obviously this is very tedious, but look at Sourcery which may be able to generate the code you need.
Another approach is to decode into arbitrary JSON, and then extract what you need. For example, see RNJSON, which allows you to parse any legal JSON, and then query it.
(I'm currently hacking around on a new decoder that would handle this kind of situation, but it's a pretty difficult problem. There's no simple switch you can throw, because in a deeply nested structure, it's not obvious at what to do at each level. For example, since otherParam isn't Optional, what should it be set to if it's not a String? Should the whole Data type fail, or do you need to define a default like "" to assign? What if otherParam were a Bool? Is the default true or false? Swift avoids implicit defaults. But still, this is a real problem, and interesting enough that I'm hacking on a toy to explore it. In the end, I suspect for this particular case it won't be any better than hand-writing the init.)

Can you define an enum to represent values explicitly known to your app, but still handle unknown values decoded from the backend?

The Problem
Can you define an enum to represent known values for a property in your model while still allowing for unknown values to be returned from the backend?
Short answer: Yes you can! The answer is below.
More Context
As part of our app, we have defined a set of feature flags that the app uses to enable/disable certain features depending on a set of criteria. These flags are sent back from the backend as an array of strings.
However, in our app, rather than dealing with the messiness of string constants, we want to define those values as an enum which we mark as Codable so the compiler handles the encoding/decoding to the actual enum cases for us automatically.
Here's a typical enum for such scenarios...
enum FeatureFlag : String, CaseIterable, Codable {
case allowsTrading
case allowsFundScreener
case allowsFundsTransfer
}
The wrinkle with this design is it doesn't handle values that may be defined, and returned from the backend in the future.
There are several ways to handle this scenario:
Abandon the enum and go to string constants. This is error prone and breaks containment/scoping since any string can participate in this logic.
Stick with an enum as-is and force an update to the app when the backend gets updated passing the buck to deployment.
Update the backend to handle versioning to only return values known to that version of the app complicating the logic on the backend to know about the various front-ends, which they shouldn't.
The most common, defensively program for unknowns by writing your own encoder/decoder methods for each class/struct that uses this enumeration, ignoring any flags not known by the current list of cases.
One through three are maintenance nightmares in their own right. Yes, four is better, but writing all those custom serializers/deserializers can be pretty time-consuming and error-prone, plus it defeats leveraging the benefits of the compiler being able to automatically do it for you!
But what if there's a number five? What if you can make the enumeration itself gracefully handle unknown values at runtime, while remaining lossless in the process, and without having to resort to optionals?
Well that's the exact solution I present below! Enjoy!
TL:DR - The Solution
For those who just want to see the solution, here it is in its entirety. This allows you to define an enum with known cases, but which can handle any raw value thrown at it at runtime, and do so in a lossless way for re-encoding purposes.
enum FeatureFlag : RawRepresentable, CaseIterable, Codable {
typealias RawValue = String
case allowsTrading
case allowsFundScreener
case allowsFundsTransfer
case unknown(RawValue)
static let allCases: AllCases = [
.allowsTrading,
.allowsFundScreener,
.allowsFundsTransfer
]
init(rawValue: RawValue) {
self = Self.allCases.first{ $0.rawValue == rawValue }
?? .unknown(rawValue)
}
var rawValue: RawValue {
switch self {
case .allowsTrading : return "ALLOWS_TRADING"
case .allowsFundScreener : return "ALLOWS_FUND_SCREENER"
case .allowsFundsTransfer : return "ALLOWS_FUNDS_TRANSFER"
case let .unknown(value) : return value
}
}
}
The Explanation
As mentioned above, our app has a certain set of known feature flags. At first, one may define them like so.
enum FeatureFlag : String, CaseIterable, Codable {
case allowsTrading
case allowsFundScreener
case allowsFundsTransfer
}
Simple enough. But again, now any value defined with the type FeatureFlag can only handle one of those specific known types.
Now say thanks to a new feature in the backend, a new flag allowsSavings is defined and pushed down to your app. Unless you have manually written the decoding logic (or resorted to optionals), the decoder will fail.
But what if the enum can handle unknown cases automatically for you, and do so in a completely transparent way?
It can! The trick is to define one additional case, unknown with an associated value of type RawValue. This new case handles all the unknown types handed to it when being decoded or even re-encoded.
Let's start by updating our enum with the new unknown case.
enum FeatureFlag : String, CaseIterable, Codable {
case allowsTrading
case allowsFundScreener
case allowsFundsTransfer
case unknown(RawValue)
}
This of course throws a ton of compiler errors thanks to that new case. Because of it, both RawRepresentable and CaseIterable can no longer be automatically synthesized by the compiler, so we have to manually implement them ourselves. Let's start with...
Manually Implementing the CaseIterable protocol
This is the easiest of the two steps. Since this 'version' of our app only knows about the first three cases, we can safely ignore all others. As such, to satisfy the protocol, we define a static allCases property that only specifies those cases we do care about.
Of Note: The property type AllCases here is an alias for [FeatureFlag] or more succinctly [Self], which we get for free when conforming to CaseIterable.
static let allCases: AllCases = [
.allowsTrading,
.allowsFundScreener,
.allowsFundsTransfer
]
With the above, this satisfies the CaseIterable protocol. Let's move on to...
Manually Implementing the RawRepresentable protocol
This is a little more complex/verbose, but this is where the 'magic' happens.
Specifying the RawValue type
Normally, to indicate your enum can be represented by a raw value, you specify a data type after an enum's name. In actuality, this is shorthand for telling the compiler you are conforming your enum to the RawRepresentable protocol and setting a RawValue typealias for that data type. However, again, because of our unknown type having an associated value, the compiler can't do that implicitly, so we must do so explicitly.
To do so, replace your raw type with RawRepresentable in the definition, then manually set the RawValue typealias inside, like so...
enum FeatureFlag : RawRepresentable, CaseIterable, Codable {
typealias RawValue = String
case allowsTrading
case allowsFundScreener
case allowsFundsTransfer
case unknown(RawValue)
}
implementing the rawValue: property (rawValues matching the case names)
Next up, we have to implement the rawValue property. For known cases where the raw value matches the case name, the implementation is simple as we can just return String(describing: self) and for the unknown cases, we return the associated value. Here's that implementation
var rawValue: RawValue {
switch self {
case let .unknown(value) : return value
default : return String(describing: self)
}
}
implementing the rawValue: property (rawValues unrelated to the case names)
But what if we wanted to express different values from the case names, or even a different data type entirely? In that situation, we have to manually expand out the switch statement and return the appropriate values like so....
var rawValue: RawValue {
switch self {
case .allowsTrading : return "ALLOWS_TRADING"
case .allowsFundScreener : return "ALLOWS_FUND_SCREENER"
case .allowsFundsTransfer : return "ALLOWS_FUNDS_TRANSFER"
case let .unknown(value) : return value
}
}
*Note: You must specify the raw values here and not up with the case definitions using the equals (=) as that's really syntactic sugar for the compiler to create what we're doing manually here, which again we have to since the compiler can't do it for us.
implementing init(rawValue:)... the 'Magic Sauce'
As mentioned, the entire purpose of this exercise is to allow your code to work with known types as usual, but to also gracefully handle unknown cases that are thrown at it. But how do we achieve that?
The trick is in the initializer, you first search for a known type within allCases and if a match is found, you use it. If however a match isn't found, rather than return nil like in the default implementation, you instead use the newly-defined unknown case, placing the unknown raw value inside.
This has the additional benefit of guaranteeing to always return an enum value from the initializer, so we can define it as a non-optional initializer as well (which is different from the implicitly created one from the compiler) making the call-site code easier to use as well.
Here's the implementation of the initializer:
init(rawValue: String) {
self = Self.allCases.first{ $0.rawValue == rawValue }
?? .unknown(rawValue)
}
In some situations, you may want to log when an unknown value is being passed for debugging purposes. That can be done with a simple guard statement (or an if if you prefer) like so...
init(rawValue: String) {
guard let knownCase = Self.allCases.first(where: { $0.rawValue == rawValue }) else {
print("Unrecognized \(FeatureFlag.self): \(rawValue)")
self = .unknown(rawValue)
return
}
self = knownCase
}
Interesting Behaviors on Equality and Hashability
One of the interesting things is that enums based on a raw value actually use that value for equality comparisons. Thanks to that piece of information, all three of these values are equal...
let a = FeatureFlag.allowsTrading // Explicitly setting a known case
let b = FeatureFlag(rawValue: "allowsTrading") // Using the initializer with a raw value from a known case
let c = FeatureFlag.unknown("allowsTrading") // Explicitly setting the 'unknown' case but with a raw value from a known case
print(a == b) // prints 'true'
print(a == c) // prints 'true'
print(b == c) // prints 'true'
Additionally, if your raw value conforms to Hashable, you can make the entire enum conform to Hashable by simply specifying its conformance to that protocol.
extension FeatureFlag : Hashable {}
With that conformance, now you can use it in sets or as keys in a dictionary as well, which again, thanks to the rules of equality above, provides for some interesting, but logically expected, behaviors.
Again, using 'a', 'b' and 'c' as defined above, you can use them like so...
var items = [FeatureFlag: Int]()
items[a] = 42 // Set using a known case
print(items[a] ?? 0) // prints 42 // Read using a known case
print(items[b] ?? 0) // prints 42 // Read using the case created from the initializer with a raw value from the known case
print(items[c] ?? 0) // prints 42 // Read using the 'unknown' case but with a raw value from the known case
Side-Benefit: Lossless Encoding/Decoding
One often-overlooked/underappreciated side-benefit of this approach is that serilization/deserialization is lossless and transparent, even for unknown values. In other words, when your app decodes data containing values you don't know about, the unknown case is still capturing and holding them.
This means if you were to then re-encode/re-serialize that data back out again, those unknown values would be re-written identically to how they would be if your app did know about them.
This is incredibly powerful!
This means for instance if an older version of your app reads in data from a server containing newer, unknown values, even if it has to re-encode that data to push it back out again, the re-encoded data looks exactly the same as if your app did know about those values without having to worry about versioning, etc. They're just silently and happily passed back.
Summary
With the above in place, you can now encode or decode any string into this enumeration type, but still have access to the known cases you care about, all without having to write any custom decoding logic in your model types. And when you do 'know' about the new type, simply add the new case as appropriate and you're good to go!
Enjoy!
Love this proposed solution! One small suggestion, add some logging in case the system encounters unknown types.
init?(rawValue: String) {
if let item = Self.allCases.first(where: { $0.rawValue == rawValue }) {
self = item
} else {
self = Self.other(rawValue)
if #available(iOS 12.0, *) {
os_log(.error, "Unknown FeatureFlag: %s", rawValue)
} else {
print("Error: Unknown FeatureFlag: \(rawValue)")
}
}
}

How to show Array of Dictionaries in a SwiftUI List?

After a few months of learning Swift by migrating parts of a legacy ObjC app, I'm looking forward to starting a new app in pure Swift - I'm hoping that working with pure Swift base classes will lead to less unwrapping and other bridging shenanigans.
However, very quickly I've found myself facing similar problems.
I want to read some JSON from a web service, and show in in a list implemented with SwiftUI - should be simple, right?
The data (actually read from the Twitter API) comes in, and I deserialise it,
do {
if let results = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments ) as? [String: Any] {
print(results)
if let followers = results["users"] as? [[String: Any]] {
print (followers.count)
print("followers class \(type(of: followers))")
} else {
print(results["errors"])
}
}
} catch let error as NSError {
print(error.localizedDescription)
}
You'll see I print the class of followers, and this shows,
followers class Array<Dictionary<String, Any>>
...a nice array of Dictionaries, using Swift base classes. That would seem a reasonable data structure to present via a List in SwiftUI. However, it doesn't seem to be so simple, as the data elements need to be Identifiable (fair enough) but I don't have a struct for each element, and I don't want the overhead of processing the array into an array of structs carrying identifiers.
A bit of research, and it seems there's a solution available, as I can initialise List with an Array, something like the following,
var body: some View {
List (twitter.followers!, id: \.self) { follower in // <<< Compilation error
Text("\(follower["name"])")
}
}
However, that code gives the compilation error on the flagged line,
Protocol type 'Any' cannot conform to 'Hashable' because only concrete
types can conform to protocols
I think the issue is that the compiler sees followers as 'Any', rather than an Array, but why?
btw, I've seen the answer to this question, but it seems the List initialiser should be a more elegant solution, if I can get it to work...
You have to create a struct for follower/user and declare it as Decodable and Identifiable.
Then you will be able to use JSONDecoder to read the data from the struct into an array for you.
As a result your twitter.followers will be an array of identifiable objects and you will be able to use it in ForEach().

Swift - Is there a way to differentiate between a field not being present or a field being nil/null when decoding an optional Codable value

The Necessary Functionality
I'm in the process of modifying a system to save a queue of currently unsent API requests to UserDefaults to be re-sent when the user's connection allows.
As some patch requests require the ability to send an actual NULL value to the API (and not just ignore out the field if its a nil optional), this means I need the ability to encode and decode nil/NULL values from defaults for certain fields.
The Issue
I have the encoding side down, and can happily encode requests to either send NULL fields to the server or encode them to Defaults. However, my issue is that when it comes to decoding saved unsent requests, I can't find a way to differentiate between an actual Nil value and the field just not being there.
I am currently using decodeIfPresent to decode my fields (all of the fields for these requests are optional), which returns nil if the field is empty OR if the field is set to Nil/NULL. Obviously this doesn't work for my fields that can explicitly be set to Nil, as there is no way for me to differentiate between the two cases.
Question
Is there any decode methodology I could implement that would allow for differentiating between a field not being there and a field actually being set to nil?
There is no way , but you can add another info to know that
struct Root : Codable {
let code : Int?
let codeExists:Bool?
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
code = try values.decodeIfPresent(Int.self, forKey: .code)
codeExists = values.contains(.code)
}
}
According to docs decodeIfPresent
This method returns nil if the container does not have a value associated with key, or if the value is null. The difference between these states can be distinguished with a contains(_:) call.
So decoding
let str = """
{
"code" : 12
}
"""
gives
Root(code: Optional(12), codeExists: Optional(true))
&&
This
let str = """
{
"code" : null
}
"""
gives
Root(code: nil, codeExists: Optional(true))
and this
let str = """
{
}
"""
gives
Root(code: nil, codeExists: Optional(false))

Can Codable APIs be used to decode data encoded with NSKeyedArchiver.archivedData?

I'm converting a codebase from using NSCoding to using Codable. I have run into an issue when trying to restore data encoded with NSCoding. I have an object that was encoded with the code:
let encodedUser = NSKeyedArchiver.archivedData(withRootObject: user)
let userDefaults = UserDefaults.standard
userDefaults.set(encodedUser, forKey: userKey)
userDefaults.synchronize()
It was previously being decoded with the code:
if let encodedUser = UserDefaults.standard.data(forKey: userKey) {
if let user = NSKeyedUnarchiver.unarchiveObject(with: encodedUser) as? User {
\\ do stuff
}
}
After updating the User object to be Codable, I attempted to update the archive/unarchive code to use PropertyListDecoder because I saw that archivedData(withRootObject:) returns data formatted as NSPropertyListBinaryFormat_v1_0. When running this code:
if let encodedUser = UserDefaults.standard.data(forKey: userKey) {
let user = try! PropertyListDecoder().decode(User.self, from: encodedUser)
\\ do stuff
}
I get a keyNotFound error for the first key I'm looking for, and breakpointing in my init(from: Decoder), I can see that the container has no keys. I also tried to use PropertyListSerialization.propertyList(from:options:format) to see if I could pass that result into PropertyListDecoder, but that function gave me an NSCFDictionary, which is structured in a really strange way but does appear to contain all the data I'm looking for.
So is it possible to decode an object using Codable APIs if it was encoded with NSKeyedArchiver.archivedData(withRootObject:)? How can I achieve this?
I decided that the answer is almost certainly no, you cannot use Codable to decode what NSCoding encoded. I wanted to get rid of all the NSCoding cruft in my codebase, and settled on a migration path where I have both protocols implemented in the critical data models to convert stored data from NSCoding to Codable formats, and I will remove NSCoding in the future when enough of my users have updated.