KeyPathKit
Context
Swift 4 has introduced a new type called KeyPath
, with allows to access the properties of an object with a very nice syntax. For instance:
The great part is that the syntax can be very concise, because it supports type inference and property chaining.
Purpose of KeyPathKit
Consequently, I thought it would be nice to leverage this new concept in order to build an API that allows to perform data manipulation in a very declarative fashion.
SQL is a great language for such manipulations, so I took inspiration from it and implemented most of its standard operators in Swift 4 using KeyPath
.
But what really stands KeyPathKit
appart from the competition is its clever syntax that allows to express queries in a very seamless fashion. For instance :
Installation
CocoaPods
Add the following to your Podfile
:
pod "KeyPathKit"
Carthage
Add the following to your Cartfile
:
github "vincent-pradeilles/KeyPathKit"
Swift Package Manager
Create a file Package.swift
:
Operators
- and
- average
- between
- contains
- distinct
- drop
- filter
- filterIn
- filterLess
- filterLike
- filterMore
- first
- groupBy
- join
- map
- mapTo
- max
- min
- or
- patternMatching
- prefix
- sum
- sort
Operator details
For the purpose of demonstrating the usage of the operators, the following mock data is defined:
and
Performs a boolean AND operation on a property of type Bool
.
false
true
average
Calculates the average of a numerical property.
25
between
Filters out elements whose value for the property is not within the range.
[Person(firstName: "Alex", lastName: "Elexson", age: 22, hasDriverLicense: false, isAmerican: true),
Person(firstName: "John", lastName: "Webb", age: 28, hasDriverLicense: true, isAmerican: true),
Person(firstName: "Webb", lastName: "Elexson", age: 30, hasDriverLicense: true, isAmerican: true)]
contains
Returns whether the sequence contains one element for which the specified boolean property or predicate is true.
true
false
distinct
Returns all the distinct values for the property.
["Webb", "Elexson", "Zunino", "Alexson"]
drop
Returns a subsequence by skipping elements while a property of type Bool
or a predicate evaluates to true, and returning the remaining elements.
[Person(firstName: "Charles", lastName: "Webb", age: 45, hasDriverLicense: true, isAmerican: true),
Person(firstName: "Alex", lastName: "Zunino", age: 34, hasDriverLicense: true, isAmerican: true),
Person(firstName: "Alex", lastName: "Alexson", age: 8, hasDriverLicense: false, isAmerican: true),
Person(firstName: "John", lastName: "Webb", age: 28, hasDriverLicense: true, isAmerican: true),
Person(firstName: "Webb", lastName: "Elexson", age: 30, hasDriverLicense: true, isAmerican: true)]
filter
Filters out elements whose value is false
for one (or several) boolean property.
[Person(firstName: "Charles", lastName: "Webb", age: 45, hasDriverLicense: true, isAmerican: true),
Person(firstName: "Alex", lastName: "Zunino", age: 34, hasDriverLicense: true, isAmerican: true),
Person(firstName: "John", lastName: "Webb", age: 28, hasDriverLicense: true, isAmerican: true),
Person(firstName: "Webb", lastName: "Elexson", age: 30, hasDriverLicense: true, isAmerican: true)]
Filter also works with predicates:
[Person(firstName: "Charlie", lastName: "Webb", age: 10, hasDriverLicense: false, isAmerican: true),
Person(firstName: "Charles", lastName: "Webb", age: 45, hasDriverLicense: true, isAmerican: true),
Person(firstName: "John", lastName: "Webb", age: 28, hasDriverLicense: true, isAmerican: true)]
filterIn
Filters out elements whose value for an Equatable
property is not in a given Sequence
.
[Person(firstName: "Alex", lastName: "Elexson", age: 22, hasDriverLicense: false, isAmerican: true),
Person(firstName: "Alex", lastName: "Zunino", age: 34, hasDriverLicense: true, isAmerican: true),
Person(firstName: "Alex", lastName: "Alexson", age: 8, hasDriverLicense: false, isAmerican: true),
Person(firstName: "John", lastName: "Webb", age: 28, hasDriverLicense: true, isAmerican: true)]
filterLess
Filters out elements whose value is greater than a constant for a Comparable
property.
[Person(firstName: "Charlie", lastName: "Webb", age: 10, hasDriverLicense: false, isAmerican: true),
Person(firstName: "Alex", lastName: "Elexson", age: 22, hasDriverLicense: false, isAmerican: true),
Person(firstName: "Alex", lastName: "Alexson", age: 8, hasDriverLicense: false, isAmerican: true),
Person(firstName: "John", lastName: "Webb", age: 28, hasDriverLicense: true, isAmerican: true)]
[Person(firstName: "Charlie", lastName: "Webb", age: 10, hasDriverLicense: false, isAmerican: true),
Person(firstName: "Alex", lastName: "Elexson", age: 22, hasDriverLicense: false, isAmerican: true),
Person(firstName: "Alex", lastName: "Alexson", age: 8, hasDriverLicense: false, isAmerican: true),
Person(firstName: "John", lastName: "Webb", age: 28, hasDriverLicense: true, isAmerican: true),
Person(firstName: "Webb", lastName: "Elexson", age: 30, hasDriverLicense: true, isAmerican: true)]
filterLike
Filters out elements whose value for a string property does not match a regular expression.
[Person(firstName: "Alex", lastName: "Elexson", age: 22, hasDriverLicense: false, isAmerican: true),
Person(firstName: "Alex", lastName: "Alexson", age: 8, hasDriverLicense: false, isAmerican: true),
Person(firstName: "Webb", lastName: "Elexson", age: 30, hasDriverLicense: true, isAmerican: true)]
filterMore
Filters out elements whose value is lesser than a constant for a Comparable
property.
[Person(firstName: "Charles", lastName: "Webb", age: 45, hasDriverLicense: true, isAmerican: true),
Person(firstName: "Alex", lastName: "Zunino", age: 34, hasDriverLicense: true, isAmerican: true)]
[Person(firstName: "Charles", lastName: "Webb", age: 45, hasDriverLicense: true, isAmerican: true),
Person(firstName: "Alex", lastName: "Zunino", age: 34, hasDriverLicense: true, isAmerican: true),
Person(firstName: "Webb", lastName: "Elexson", age: 30, hasDriverLicense: true, isAmerican: true)]
first
Returns the first element matching a predicate.
Optional(Person(firstName: "Charlie", lastName: "Webb", age: 10, hasDriverLicense: false, isAmerican: true))
groupBy
Groups values by equality on the property.
["Alexson": [Person(firstName: "Alex", lastName: "Alexson", age: 8, hasDriverLicense: false, isAmerican: true)],
"Webb": [Person(firstName: "Charlie", lastName: "Webb", age: 10, hasDriverLicense: false, isAmerican: true), Person(firstName: "Charles", lastName: "Webb", age: 45, hasDriverLicense: true, isAmerican: true), Person(firstName: "John", lastName: "Webb", age: 28, hasDriverLicense: true, isAmerican: true)],
"Elexson": [Person(firstName: "Alex", lastName: "Elexson", age: 22, hasDriverLicense: false, isAmerican: true), Person(firstName: "Webb", lastName: "Elexson", age: 30, hasDriverLicense: true, isAmerican: true)],
"Zunino": [Person(firstName: "Alex", lastName: "Zunino", age: 34, hasDriverLicense: true, isAmerican: true)]]
join
Joins values of two sequences in tuples by the equality on their respective property.
[(Person(firstName: "Webb", lastName: "Elexson", age: 30, hasDriverLicense: true, isAmerican: true), Person(firstName: "Charlie", lastName: "Webb", age: 10, hasDriverLicense: false, isAmerican: true)),
(Person(firstName: "Webb", lastName: "Elexson", age: 30, hasDriverLicense: true, isAmerican: true), Person(firstName: "Charles", lastName: "Webb", age: 45, hasDriverLicense: true, isAmerican: true)),
(Person(firstName: "Webb", lastName: "Elexson", age: 30, hasDriverLicense: true, isAmerican: true), Person(firstName: "John", lastName: "Webb", age: 28, hasDriverLicense: true, isAmerican: true))]
Joining on more than one attribute is also supported:
map
Maps elements to their values of the property.
["Webb", "Elexson", "Webb", "Zunino", "Alexson", "Webb", "Elexson"]
mapTo
Maps a sequence of properties to a function. This is, for instance, useful to extract a subset of properties into a structured type.
[ContactCellModel(firstName: "Webb", lastName: "Charlie"),
ContactCellModel(firstName: "Elexson", lastName: "Alex"),
ContactCellModel(firstName: "Webb", lastName: "Charles"),
ContactCellModel(firstName: "Zunino", lastName: "Alex"),
ContactCellModel(firstName: "Alexson", lastName: "Alex"),
ContactCellModel(firstName: "Webb", lastName: "John"),
ContactCellModel(firstName: "Elexson", lastName: "Webb")]
max
Returns the element with the greatest value for a Comparable
property.
Optional(Person(firstName: "Charles", lastName: "Webb", age: 45, hasDriverLicense: true, isAmerican: true))
Optional(45)
min
Returns the element with the minimum value for a Comparable
property.
Optional(Person(firstName: "Alex", lastName: "Alexson", age: 8, hasDriverLicense: false, isAmerican: true))
Optional(8)
or
Performs a boolean OR operation on an property of type Bool
.
true
patternMatching
Allows the use of predicates inside a switch
statement:
prefix
Returns a subsequence containing the initial, consecutive elements for whose a property of type Bool
or a predicate evaluates to true.
[Person(firstName: "Charlie", lastName: "Webb", age: 10, hasDriverLicense: false, isAmerican: true),
Person(firstName: "Alex", lastName: "Elexson", age: 22, hasDriverLicense: false, isAmerican: true)]
sum
Calculates the sum of the values for a numerical property.
177
sort
Sorts the elements with respect to a Comparable
property.
[Person(firstName: "Alex", lastName: "Alexson", age: 8, hasDriverLicense: false, isAmerican: true),
Person(firstName: "Charlie", lastName: "Webb", age: 10, hasDriverLicense: false, isAmerican: true),
Person(firstName: "Alex", lastName: "Elexson", age: 22, hasDriverLicense: false, isAmerican: true), Person(firstName: "John", lastName: "Webb", age: 28, hasDriverLicense: true, isAmerican: true),
Person(firstName: "Webb", lastName: "Elexson", age: 30, hasDriverLicense: true, isAmerican: true),
Person(firstName: "Alex", lastName: "Zunino", age: 34, hasDriverLicense: true, isAmerican: true),
Person(firstName: "Charles", lastName: "Webb", age: 45, hasDriverLicense: true, isAmerican: true)]
It's also possible to specify the sorting order, to sort on multiple criteria, or to do both.
[Person(firstName: "Alex", lastName: "Alexson", age: 8, hasDriverLicense: false, isAmerican: true),
Person(firstName: "Webb", lastName: "Elexson", age: 30, hasDriverLicense: true, isAmerican: true),
Person(firstName: "Alex", lastName: "Elexson", age: 22, hasDriverLicense: false, isAmerican: true),
Person(firstName: "Charles", lastName: "Webb", age: 45, hasDriverLicense: true, isAmerican: true),
Person(firstName: "John", lastName: "Webb", age: 28, hasDriverLicense: true, isAmerican: true),
Person(firstName: "Charlie", lastName: "Webb", age: 10, hasDriverLicense: false, isAmerican: true),
Person(firstName: "Alex", lastName: "Zunino", age: 34, hasDriverLicense: true, isAmerican: true)]
Author
- Twitter: @v_pradeilles
Thanks
A big thank you to Jérôme Alves (elegantswift.com) for coming up with the right modelization to allow sorting on multiple properties with heterogenous type.