Unit testing Decodable: what's your approach for invalid data? - swift

Let's say you have a JSON with several fields:
Students: [
student: {
name: "Harry"
surname: "Smith"
age: 24
},
...
]
and decodables:
struct Students : Decodable {
let student: [Student]
}
struct Student : Decodable {
let name: String
let surname: String
let age: Int
}
If you want to test invalid data, do you write a sequence of tests with te following fake data?
Test1 data:
{
name: null
surname: "Smith"
age: 24
}
Test2 data:
{
name: "Harry"
surname: null
age: 24
}
Test3 data:
{
name: "Harry"
surname: "Smith"
age: null
}
And maybe a test with a wrong type?
{
name: "Harry"
surname: "Smith"
age: "24" //<- string
}
So do you write all these tests, one for each of the above jsons or does this not make sense to you?

It makes sense writing separate test for each key-value pair if we were not able to see the reason of failure and we had to handle it ourself for each pair. For example, old way of JSONSerialization to get Dictionary and then manually checking each key-value if they exist, if yes, does type matches etc. If we were able to get all the values and expected then we would use to initialize the type e.g, Student. In this kind of scenario, instead of putting so many if-else to report proper failure one can think of separate cases for each pair.
Now that we have Codable and error thrown clearly provide all the information for failure so a single test is enough.

The Decodable protocol provides a series of specific Decoding errors.
As already mentioned by Kamran use one test and catch all possible Decoding errors for example
do {
XCTAssertNoThrow(try JSONDecoder().decode(Students.self, from: data))
} catch let DecodingError.dataCorrupted(context) {
XCTFail("Data corrupted: \(context.debugDescription)")
} catch let DecodingError.keyNotFound(key, context) {
let message = "Key '\(key)' not found: \(context.debugDescription), codingPath: \(context.codingPath)"
XCTFail(message)
} catch let DecodingError.valueNotFound(value, context) {
let message = "Value '\(value)' not found: \(context.debugDescription), codingPath: \(context.codingPath)"
XCTFail(message)
} catch let DecodingError.typeMismatch(type, context) {
let message = "Type '\(type)' mismatch: \(context.debugDescription), codingPath: \(context.codingPath)"
XCTFail(message)
} catch {
XCTFail(error.localizedDescription)
}

I'd primarily test the success way with at least one case.
Also I'd test a few cases with "borderline" values like unusual characters in String values or Integer values around the 32/64bit threshold.
Finally I'd test for one case where all the required (non-optional) values are null. If you later change the model for some properties to be optional this test would fail so you can check if you need to make adaptions because of the changed circumstance.

Related

Vapor 4: how to map an eagerly loaded parent relation into a different format?

I am struggling a bit with how to return a model that contains a parent relationship, while mapping that eagerly loaded model into a different form.
Let's consider the following 2 models: Course and User.
final class Course: Model, Content {
static let schema = "courses"
#ID(key: .id)
var id: UUID?
#Field(key: "name")
var name: String
#Parent(key: "teacher_id")
var teacher: User
init() { }
}
final class User: Model, Content {
static let schema = "users"
#ID(key: .id)
var id: UUID?
#OptionalField(key: "avatar")
var avatar: String?
#Field(key: "name")
var name: String
#Field(key: "private")
var somePrivateField: String
init() { }
}
I have a route like this, which returns an array of courses:
func list(req: Request) throws -> EventLoopFuture<[Course]> {
return Course
.query(on: req.db)
.all()
}
The resulting JSON looks something like this:
[
{
"id": 1,
"name": "Course 1",
"teacher": {
"id": 1
}
]
What I want instead is that the teacher object is returned, which is easy enough by adding .with(\.$teacher) to the query. Vapor 4 does make this very easy!
[
{
"id": 1,
"name": "Course 1",
"teacher": {
"id": 1,
"name": "User 1",
"avatar": "https://www.example.com/avatar.jpg",
"somePrivateField": "super secret internal info"
}
]
And there's my problem: the entire User object is returned, with literally all fields, even ones I don't want to make public.
What is the easiest way to transform the teacher info a different version of the User model, like PublicUser? Does that mean I have to make a DTO for the Course, map my array from [Course] to [PublicCourse], copy all properties, keep them in sync when the Course model changes, etc?
That seems like a lot of boilerplate with lots of room for mistakes in the future. Would love to hear if there are better options.
You can do this by first encoding the original model and then decoding it into a structure with fewer fields. So, for an instance of Course stored in course to convert to PublicCourse you would do:
struct PublicCourse: Decodable {
//...
let teacher: PublicUser
//...
}
let course:Course = // result of Course.query including `with(\.$teacher)`
let encoder = JSONEncoder()
let decoder = JSONDecoder()
let data = try encoder.encode(course)
let publicCourse = try decoder.decode(PublicCourse.self, from: data)
Notice the PublicUser field in the structure. If this is the cut-down version, you can generate your minimal JSON in one go.
Okay, what about this approach? Create a second Model, called Teacher, say, that is defined as the subset of fields from User that you want to expose in your API/JSON and has the same schema/table name as User:
final class Teacher: Model, Content {
static let schema = "users"
// public fields
}
Then change your relation in Course to:
#Parent(key: "teacher_id")
var teacher: Teacher
Your original query will work unchanged, but just returning the reduced field-set. It will work certainly if you are using Teacher read-only. No need to create Migrations for Teacher as the underlying table exists. I can't see a way to avoid including the ID, but that may not be a problem.

Making a query on a mongo DB with ParseSwift

I have two collections in a Mongo DB.
Here is how a document looks in the first collection (MainCollection):
_id
:"mzWqPEDYRU"
TITLE
:"ZAZ: I want."
ownerID
:"lGutCBY52g"
accessKey
:"0kAd4TOmoK0"
_created_at
:2020-03-13T11:42:11.169+00:00
_updated_at
:2020-03-13T17:08:15.090+00:00
downloadCount
:2
And here is how it looks in the second collection (SecondCollection):
_id
:"07BOGA8bHG"
_p_unit
:"MainCollection$mzWqPEDYRU"
SENTENCE
:"I love nature peace and freedom."
Order
:5
ownerID
:"lGutCBY52g"
AUDIO
:"07067b5589d1edd1d907e96c1daf6da1_VOICE.bin"
_created_at
:2020-03-13T11:42:17.483+00:00
_updated_at
:2020-03-13T11:42:19.336+00:00
There is a parent children relationship between the first and the second collection. In the last document we can see the _p_unit field where the "mzWqPEDYRU" part points to the id of the parent in the first collection.
Though I finally get what I want, getting elements of the second collection with a given parent, it is not done how it should.
I have one problem making a selective query on SecondCollection. Here is the currently working code:
func theFunction(element: MainCollection) {
do {SecondCollection.query().find() {
result in
switch result {
case .success(let items):
print("items.count = \(items.count)")
var finalCount = 0
for item in items {
// Ignore useless elements:
if item.unit?.objectId != element.objectId! {continue}
finalCount += 1
/// .... Work with selected element.
}
print("finalCount = \(finalCount)")
case .failure(let error):
print("Error in \(#function): \(error)")
}
}
}
}
The way the above code is written works in the sense that I get the elements in SecondCollection I am interested in. But this trick inside the for loop to eliminate the non-needed element is not the way to go.
if item.unit?.objectId != element.objectId! {continue}
The filtering should happen in the query, with the line:
SecondCollection.query().find()
The problem is that everything I have tried failed. I did things like:
SecondCollection.query("unit" == element.objectId!).find()
with a zillion variations, but all with no luck.
Does anybody know the proper syntax?
In case this may be useful, here is how SecondCollection is declared:
struct SecondCollection: ParseObject,Identifiable,Equatable,Hashable {
// These fields are required for any Object.
var objectId: String?
var createdAt: Date?
var updatedAt: Date?
var ACL: ParseACL?
// Local properties.
var id:UUID {return UUID()}
var SENTENCE: String?,
Order: Int?,
ownerID: String?,
AUDIO: ParseFile?,
unit: Pointer<MainCollection>?
.......
}
First, I do not think that bringing all elements and then "eliminating" the ones you don't need is a good approach. That way you are retrieving a lot more data than needed and it is very inneficient.
I tried out your code but you did not make clear (at least for me) if you are using Pointers or Relations between those classes. The approach would be different for each one.
Which one are you using?
UPDATE: Hey there! I think I could make it work kinda the way you need it.
I created two classes ParentClass (name: string, age: number, children: relation to ChildClass):
struct ParentClass: ParseObject {
//: These are required for any Object.
var objectId: String?
var createdAt: Date?
var updatedAt: Date?
var ACL: ParseACL?
//: Your own properties.
var name: String?
var age: Int = 0
var children: ParseRelation<Self> {
ParseRelation(parent: self, key: "children", className: "ChildClass")
}
}
And ChildClass (name: string, age:number):
struct ChildClass: ParseObject {
//: These are required for any Object.
var objectId: String?
var createdAt: Date?
var updatedAt: Date?
var ACL: ParseACL?
//: Your own properties.
var name: String?
var age: Int = 0
}
Then I did a simple query to find the first Parent. You could use the find() method and bring all the ones that you need, but I did it this way to keep it simpler to explain:
let parentQuery = ParentClass.query()
parentQuery.first { result in
switch result {
case .success(let found):
print ("FOUND A PARENT!")
print(found.name!)
print(found.age)
print(found.children)
case .failure(let error):
print(error)
}
}
Now that I got the Parent (that has two children in my case), I used the query() method to generate the query on the ChildClass containing all the ChildClass' objects with the found parent:
do {
let childrenObject = ChildClass()
try found.children.query(childrenObject).find()
{ result in
switch result {
case .success(let allChildrenFromParent):
print("The following Children are part of the \(found.name!):")
for child in allChildrenFromParent {
print("\(child.name!) is a child of \(found.name!)")
}
case .failure(let error):
print("Error finding Children: \(error)")
}
}
} catch {
print("Error: \(error)")
}
And the whole code ended up like this:
let parentQuery = ParentClass.query()
parentQuery.first { result in
switch result {
case .success(let found):
print ("FOUND A PARENT!")
print(found.name!)
print(found.age)
print(found.children)
do {
let childrenObject = ChildClass()
try found.children.query(childrenObject).find()
{ result in
switch result {
case .success(let allChildrenFromParent):
print("The following Children are part of the \(found.name!):")
for child in allChildrenFromParent {
print("\(child.name!) is a child of \(found.name!)")
}
case .failure(let error):
print("Error finding Children: \(error)")
}
}
} catch {
print("Error: \(error)")
}
case .failure(let error):
print(error)
}
}

How can I encode a Codable type by specifying a subset of its CodingKeys?

I have types for which I provide CodingKeys with custom names for their data members as appropriate. I would like to encode, as required by different call sites, only a subset of a type's CodingKeys before sending the data to a server. The required encodings change from call site to call site, so there's no default implementation to consider.
Example types (for demonstration purposes):
struct Account: Codable
{
let name: String;
var balance: Float;
mutating func set(balance: Float)
{
self.balance = balance; // verifications, etc.
}
}
struct User: Codable
{
enum CodingKeys: String, CodingKey
{
case
id = "db_id",
name = "db_name",
account
// many more keys
}
let id: Int;
let name: String;
var account: Account;
// many more data members
}
Creating an instance:
var user = User(
id: 1, name: "john", account: Account(name: "checking", balance: 10_000));
Using a JSONEncoder works as expected; it produces the following:
{
"db_id" : 1,
"db_name" : "john",
"account" : {
"balance" : 10000,
"name" : "checking"
}
}
I want to encode a subset of the User type in order to send that data back to a server so that I can update specific data fields instead of updating the entire set of properties of my type. Mock usage example:
user.account.set(balance: 15_000);
let jsonEncoding = JSONEncodeSubset.of(
user, // instance to encode
keys: [User.CodingKeys.id, User.CodingKeys.account] // data to include
);
The resulting produced JSON would look like so:
{
"db_id" : 1,
"account" : {
"balance" : 15000,
"name" : "checking"
}
}
Server side, we now have the exact data we need to perform our desired update.
Another example: somebody entered the wrong name for our user, therefore another update request looks like this:
user.name = "jon"; // assume the model was modified to make this mutable
let jsonEncoding = JSONEncodeSubset.of(
user, // instance to encode
keys: [User.CodingKeys.id, User.CodingKeys.name] // only encode id & name
);
The expected resulting JSON encoding:
{
"db_id" : 1,
"name" : "jon"
}
Note that we exclude the information that isn't part of the update (user's account). The objective is to optimize the encoding to include only data that is relevant to that specific request.
Considering I have a large list of objects to update, with different call sites updating different things, I'd like to have a succinct way to perform the encoding task.
Encoding a subset of a type's data members improves encoding performance/memory footprint.
Sending a larger count of smaller encoded objects to the server becomes more efficient.
Does Swift provide any such support for encoding subsets of a type's CodingKeys?
Expanding on Joakim Danielson's answer you can use CodingUserInfoKey to pass data to userInfo property of JSONEncoder.
extension CodingUserInfoKey {
static let keysToEncode = CodingUserInfoKey(rawValue: "keysToEncode")!
}
extension JSONEncoder {
func withEncodeSubset<CodingKeys>(keysToEncode: [CodingKeys]) -> JSONEncoder {
userInfo[.keysToEncode] = keysToEncode
return self
}
}
You need to make CodingKeys conform to CaseIterable and implement a custom encode(to:) method:
struct User: Codable {
enum CodingKeys: String, CodingKey, CaseIterable { // add `CaseIterable`
...
}
...
func encode(to encoder: Encoder) throws {
let keysToEncode = encoder.userInfo[.keysToEncode] as? [CodingKeys] ?? CodingKeys.allCases
var container = encoder.container(keyedBy: CodingKeys.self)
for key in keysToEncode {
switch key {
case .id:
try container.encode(id, forKey: .id)
case .account:
try container.encode(account, forKey: .account)
case .name:
try container.encode(name, forKey: .name)
}
}
}
}
And use it like this:
let encoder = JSONEncoder().withEncodeSubset(keysToEncode: [User.CodingKeys.id, .account])
let encoded = try encoder.encode(user)
print(String(data: encoded, encoding: .utf8)!)
// prints: {"db_id":1,"account":{"name":"checking","balance":15000}}
This solution requires that you write a custom encode(to:) for all properties but once that is done it should be easy to use.
The idea is to let the encode(to:) encode only those properties/keys that exists in a given array. So given the example above the function would look like this
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
for key in selectedCodingKeys {
switch key {
case .id:
try container.encode(id, forKey: .id)
case .account:
try container.encode(account, forKey: .account)
case .name:
try container.encode(name, forKey: .name)
}
}
}
selectedCodingKeys is new property in the struct
var selectedCodingKeys = [CodingKeys]()
and we could also add a specific function for encoding
mutating func encode(for codingKeys: [CodingKeys]) throws -> Data {
self.selectedCodingKeys = codingKeys
return try JSONEncoder().encode(self)
}
and then the decoding could be done like in this example
var user = User(id: 1, name: "John", account: Account(name: "main", balance: 100.0))
do {
let data1 = try user.encode(for: [.id, .account])
let data2 = try user.encode(for: [.id, .name])
} catch {
print(error)
}

Swift Codable: Continue parsing object after nested object error

My app is, like oh so many apps, retrieving JSON from an API and converting it using the new Codable protocol in Swift 4. Most of the time, this works fine and as expected. However, sometimes the API will send me unexpected garbage. Incorrect types, arrays with just null inside, that kind of thing.
The problem is that the objects involved can be large and complicated, and when I'm parsing a child object and it fails, the whole object fails, all the way up to the root. I'm including a very simple playground example to illustrate the concept; the actual objects involved are way more complex.
let goodJSON = """
{
"name": "Fiona Glenanne",
"vehicles": [
{
"make": "Saab",
"model": "9-3",
"color": "Black"
},
{
"make": "Hyundai",
"model": "Genesis",
"color": "Blue"
}
]
}
"""
let goodJSONData = goodJSON.data(using: .utf8)!
let badJSON = """
{
"name": "Michael Westen",
"vehicles": {
"make": "Dodge",
"model": "Charger",
"color": "Black"
}
}
"""
let badJSONData = badJSON.data(using: .utf8)!
struct Character: Codable {
let name: String
let vehicles: [Vehicle]
}
struct Vehicle: Codable {
let make: String
let model: String
let color: String
}
do {
let goodCharacter = try JSONDecoder().decode(Character.self, from: goodJSONData)
print(goodCharacter)
} catch {
print(error)
}
do {
let badCharacter = try JSONDecoder().decode(Character.self, from: badJSONData)
print(badCharacter)
} catch DecodingError.typeMismatch(let type, let context) {
print("Got \(type); \(context.debugDescription) ** Path:\(context.codingPath)")
} catch {
print("Caught a different error: \(error)")
}
Output:
Character(name: "Fiona Glenanne", vehicles: [__lldb_expr_20.Vehicle(make: "Saab", model: "9-3", color: "Black"), __lldb_expr_20.Vehicle(make: "Hyundai", model: "Genesis", color: "Blue")])
Got Array<Any>; Expected to decode Array<Any> but found a dictionary instead. ** Path:[CodingKeys(stringValue: "vehicles", intValue: nil)]
vehicles is expected to be an array of objects, but in the badJSON case, it is a single object, which causes the .typeMismatch exception and kills the parsing right there.
What I'm looking for is a way to allow errors like this one to kill the parsing for the child object only and allow parsing of the parent object to continue. I'm looking to do this in a generic fashion, so I don't have to special case each and every object in my app to specifically handle whatever bad data the API delivers. I'm not sure if there even is a solution for this, I haven't had any luck finding anything, but it would sure improve my quality of life if there is. Thanks!
you can try to custom the init(from decoder: Decoder) as suggested in comments it would be something like this,
struct Character: Codable {
let name: String
let vehicles: [Vehicle]
private enum CodingKeys: String, CodingKey { case name, vehicles }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
do {
let vehicle = try container.decode(Vehicle.self, forKey: .vehicles)
vehicles = [vehicle]
} catch DecodingError.typeMismatch {
vehicles = try container.decode([Vehicle].self, forKey: .vehicles)
}
}

Decodable returning object

I have a decodable class:
struct AuthenticationResponse : Decodable {
var status: String
var error: Error
var access_token: String? = ""
var expires_in: Double? = 0
var token_type: String? = ""
var scope: String? = ""
var refresh_token: String? = "
}
struct Error : Decodable {
var desc: String
var code: String
}
In the Error class I have:
And to decode to this class, I have:
URLSession.shared.dataTask(with: request) { (data:Data?, response:URLResponse?, error:Error?) in
if let jsonData = data{
let decoder = JSONDecoder()
print("hey")
print("response: \(String(data:jsonData, encoding:.utf8))")
completion(try! decoder.decode(AuthenticationResponse.self, from: jsonData))
}
}.resume()
As some of the responses I receive are (Success response):
{
“status”: “SUCCESS” “error”: null, "access_token":
"MWVmOWQxMDYwMjQyNDQ4NzQyNTdkZjQ3NmI4YmVjMGZjZGM5N2IyZmNkOTA1 N2M0NDUzODEwYjM5ZWQyNGNkZg",
"expires_in": 3600, "token_type": "bearer", "scope": null,
"refresh_token":
"ZGEwOGZiOWZhMzhhYjBmMzAyOGRmZTA5NjJhMjY2MTk3YzMyMmE1ZDlkNWI2N mJjYmIxMjNkMjE1NWFhNWY0Mg"
}
And then a failed response just contains an error object with desc and code in it.
What i am trying to achieve is a decodable class suitable for both scenarios (When a response is successful and failed) however im not sure how to achieve this. I'm aware i can make 2 separate decodable classes but this would make things messier as i'd have to determine if the response is an error and populate to return different classes.
Does anyone know how i should acheive this>
I will give it a try, but first we need to sort out what I consider a somewhat shoddy question. Since Error is the name of a (famous and widely used) protocol it should be renamed and since you want to be able to leave it empty in your AuthenticationResponse it must obviously be an optional there (bearing the question why it is in the Response at all, but I will leave this aside). This leaves us with the following:
struct AuthError : Decodable {
var desc: String
var code: String
}
struct AuthenticationResponse : Decodable {
var status: String
var error: AuthError?
var access_token: String? = ""
var expires_in: Double? = 0
var token_type: String? = ""
var scope: String? = ""
var refresh_token: String? = ""
}
Then we need some example data for the two relevant cases in question, I used:
let okData = """
{
"status": "SUCCESS",
"error": null,
"access_token":
"MWVmOWQxMDYwMjQyNDQ4NzQyNTdkZjQ3NmI4YmVjMGZjZGM5N2IyZmNkOTA1N2M0NDUzODEwYjM5ZWQyNGNkZg",
"expires_in": 3600,
"token_type": "bearer",
"scope": null,
"refresh_token":
"ZGEwOGZiOWZhMzhhYjBmMzAyOGRmZTA5NjJhMjY2MTk3YzMyMmE1ZDlkNWI2NmJjYmIxMjNkMjE1NWFhNWY0Mg"
}
""".data(using: .utf8)!
let errData = """
{
"desc": "username or password incorrect",
"code": "404"
}
""".data(using: .utf8)!
Now we can define a single enum return type which allows for all our cases:
enum AuthResult {
case ok(response: AuthenticationResponse)
case authError(error: AuthError)
case parseError(description: String)
case fatal
}
which finally allows us to write our parse function for the received authentication data:
func parse(_ jsonData:Data) -> AuthResult {
let decoder = JSONDecoder()
do {
let authRes = try decoder.decode(AuthenticationResponse.self, from: jsonData)
return .ok(response: authRes)
} catch {
do {
let errRes = try decoder.decode(AuthError.self, from: jsonData)
return .authError(error: errRes)
} catch let errDecode {
return .parseError(description: errDecode.localizedDescription)
}
}
}
All this in a Playground will permit usage as in
switch parse(okData) {
case let .ok(response):
print(response)
case let .authError(error):
print(error)
case let .parseError(description):
print("You threw some garbage at me and I was only able to \(description)")
default:
print("don't know what to do here")
}
That is still elegant compared to the mess you would make in most other languages, but the call is still out on wether it would not make more sense to just define AuthenticationResponse as the (regular) return type of the parse function and provide the rest by throwing some enum (conforming to Error) and some suitable payload.
Coming (mainly) from Java I still shun from using exceptions as "somewhat" regular control flow (as in a "regular" login failure), but given Swifts much more reasonable approach to exceptions this might have to be reconsidered.
Anyways, this leaves you with a function to parse either case of your services replies and a decent way to handle them in a "uniform" manner. As you might not be able to modify the behaviour of the service handling your request this might be the only viable option. However, if you are able to modify the service you should strive for a "uniform" reply that would be parseable by a single call to JSONDecoder.decode. You would still have to interpret the optionals (as you should in the above example, since they are still a pain to work with, even given Swifts brilliant compiler support forcing you to "do the right thing"), but it would make your parsing less error prone.