{"id":1044,"url":"https://github.com/vincent-pradeilles/KeyPathKit","last_synced_at":"2025-08-06T13:32:07.103Z","repository":{"id":56917871,"uuid":"124228436","full_name":"vincent-pradeilles/KeyPathKit","owner":"vincent-pradeilles","description":"KeyPathKit is a library that provides the standard functions to manipulate data along with a call-syntax that relies on typed keypaths to make the call sites as short and clean as possible.","archived":false,"fork":false,"pushed_at":"2019-10-03T13:44:59.000Z","size":129,"stargazers_count":426,"open_issues_count":0,"forks_count":20,"subscribers_count":10,"default_branch":"master","last_synced_at":"2024-11-19T12:15:19.093Z","etag":null,"topics":["data","sql","swift"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vincent-pradeilles.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-03-07T11:49:44.000Z","updated_at":"2024-11-01T03:52:39.000Z","dependencies_parsed_at":"2022-08-20T21:20:31.963Z","dependency_job_id":null,"html_url":"https://github.com/vincent-pradeilles/KeyPathKit","commit_stats":null,"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vincent-pradeilles%2FKeyPathKit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vincent-pradeilles%2FKeyPathKit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vincent-pradeilles%2FKeyPathKit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vincent-pradeilles%2FKeyPathKit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vincent-pradeilles","download_url":"https://codeload.github.com/vincent-pradeilles/KeyPathKit/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228905450,"owners_count":17989770,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["data","sql","swift"],"created_at":"2024-01-05T20:15:37.735Z","updated_at":"2024-12-09T14:30:46.178Z","avatar_url":"https://github.com/vincent-pradeilles.png","language":"Swift","readme":"# KeyPathKit\n\n[![Build Status](https://travis-ci.org/vincent-pradeilles/KeyPathKit.svg?branch=master)](https://travis-ci.org/vincent-pradeilles/KeyPathKit)\n![platforms](https://img.shields.io/badge/platforms-iOS%20%7C%20macOS%20%7C%20tvOS%20%7C%20watchOS-333333.svg)\n![pod](https://img.shields.io/cocoapods/v/KeyPathKit.svg)\n[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)\n[![Swift Package Manager compatible](https://img.shields.io/badge/Swift%20Package%20Manager-compatible-brightgreen.svg)](https://github.com/apple/swift-package-manager)\n\n## Context\n\nSwift 4 has introduced a new type called `KeyPath`, with allows to access the properties of an object with a very nice syntax. For instance:\n\n```swift\nlet string = \"Foo\"\nlet keyPathForCount = \\String.count\n\nlet count = string[keyPath: keyPathForCount] // count == 3\n```\n\nThe great part is that the syntax can be very concise, because it supports type inference and property chaining.\n\n## Purpose of `KeyPathKit`\n\nConsequently, 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.\n\nSQL 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`.\n\nBut what really stands `KeyPathKit` appart from the competition is its clever syntax that allows to express queries in a very seamless fashion. For instance :\n\n```swift\ncontacts.filter(where: \\.lastName == \"Webb\" \u0026\u0026 \\.age \u003c 40)\n```\n\n## Installation\n\n### CocoaPods\n\nAdd the following to your `Podfile`:\n\n`pod \"KeyPathKit\"`\n\n### Carthage\n\nAdd the following to your `Cartfile`:\n\n`github \"vincent-pradeilles/KeyPathKit\"`\n\n### Swift Package Manager\n\nCreate a file `Package.swift`:\n\n```swift\n// swift-tools-version:4.0\n\nimport PackageDescription\n\nlet package = Package(\n    name: \"YourProject\",\n    dependencies: [\n        .package(url: \"https://github.com/vincent-pradeilles/KeyPathKit.git\", \"1.0.0\" ..\u003c \"2.0.0\")\n    ],\n    targets: [\n        .target(name: \"YourProject\", dependencies: [\"KeyPathKit\"])\n    ]\n)\n```\n\n## Operators\n\n* [and](#and)\n* [average](#average)\n* [between](#between)\n* [contains](#contains)\n* [distinct](#distinct)\n* [drop](#drop)\n* [filter](#filter)\n* [filterIn](#filterin)\n* [filterLess](#filterless)\n* [filterLike](#filterlike)\n* [filterMore](#filtermore)\n* [first](#first)\n* [groupBy](#groupby)\n* [join](#join)\n* [map](#map)\n* [mapTo](#mapto)\n* [max](#max)\n* [min](#min)\n* [or](#or)\n* [patternMatching](#patternMatching)\n* [prefix](#prefix)\n* [sum](#sum)\n* [sort](#sort)\n\n## Operator details\n\nFor the purpose of demonstrating the usage of the operators, the following mock data is defined:\n\n```swift\nstruct Person {\n    let firstName: String\n    let lastName: String\n    let age: Int\n    let hasDriverLicense: Bool\n    let isAmerican: Bool\n}\n\nlet contacts = [\n    Person(firstName: \"Charlie\", lastName: \"Webb\", age: 10, hasDriverLicense: false, isAmerican: true),\n    Person(firstName: \"Alex\", lastName: \"Elexson\", age: 22, hasDriverLicense: false, isAmerican: true),\n    Person(firstName: \"Charles\", lastName: \"Webb\", age: 45, hasDriverLicense: true, isAmerican: true),\n    Person(firstName: \"Alex\", lastName: \"Zunino\", age: 34, hasDriverLicense: true, isAmerican: true),\n    Person(firstName: \"Alex\", lastName: \"Alexson\", age: 8, hasDriverLicense: false, isAmerican: true),\n    Person(firstName: \"John\", lastName: \"Webb\", age: 28, hasDriverLicense: true, isAmerican: true),\n    Person(firstName: \"Webb\", lastName: \"Elexson\", age: 30, hasDriverLicense: true, isAmerican: true)\n]\n``` \n \n### and\n\nPerforms a boolean AND operation on a property of type `Bool`.\n\n```swift\ncontacts.and(\\.hasDriverLicense)\ncontacts.and(\\.isAmerican)\n```\n\n```\nfalse\ntrue\n```\n\n### average\n\nCalculates the average of a numerical property.\n\n```swift\ncontacts.average(of: \\.age).rounded()\n```\n\n```\n25\n```\n\n### between\n\nFilters out elements whose value for the property is not within the range.\n\n```swift\ncontacts.between(\\.age, range: 20...30)\n// or\ncontacts.filter(where: 20...30 ~= \\.age)\n```\n\n```\n[Person(firstName: \"Alex\", lastName: \"Elexson\", age: 22, hasDriverLicense: false, isAmerican: true),\n Person(firstName: \"John\", lastName: \"Webb\", age: 28, hasDriverLicense: true, isAmerican: true), \n Person(firstName: \"Webb\", lastName: \"Elexson\", age: 30, hasDriverLicense: true, isAmerican: true)]\n```\n\n### contains\n\nReturns whether the sequence contains one element for which the specified boolean property or predicate is true.\n\n```swift\ncontacts.contains(where: \\.hasDriverLicense)\ncontacts.contains(where: \\.lastName.count \u003e 10)\n```\n\n```\ntrue\nfalse\n```\n\n### distinct\n\nReturns all the distinct values for the property.\n\n```swift\ncontacts.distinct(\\.lastName)\n```\n\n```\n[\"Webb\", \"Elexson\", \"Zunino\", \"Alexson\"]\n```\n\n### drop\n\nReturns a subsequence by skipping elements while a property of type `Bool` or a predicate evaluates to true, and returning the remaining elements.\n\n```swift\ncontacts.drop(while: \\.age \u003c 40)\n```\n\n```\n[Person(firstName: \"Charles\", lastName: \"Webb\", age: 45, hasDriverLicense: true, isAmerican: true), \n Person(firstName: \"Alex\", lastName: \"Zunino\", age: 34, hasDriverLicense: true, isAmerican: true), \n Person(firstName: \"Alex\", lastName: \"Alexson\", age: 8, hasDriverLicense: false, isAmerican: true), \n Person(firstName: \"John\", lastName: \"Webb\", age: 28, hasDriverLicense: true, isAmerican: true), \n Person(firstName: \"Webb\", lastName: \"Elexson\", age: 30, hasDriverLicense: true, isAmerican: true)]\n```\n\n### filter\n\nFilters out elements whose value is `false` for one (or several) boolean property.\n\n```swift\ncontacts.filter(where: \\.hasDriverLicense)\n```\n\n```\n[Person(firstName: \"Charles\", lastName: \"Webb\", age: 45, hasDriverLicense: true, isAmerican: true), \n Person(firstName: \"Alex\", lastName: \"Zunino\", age: 34, hasDriverLicense: true, isAmerican: true), \n Person(firstName: \"John\", lastName: \"Webb\", age: 28, hasDriverLicense: true, isAmerican: true), \n Person(firstName: \"Webb\", lastName: \"Elexson\", age: 30, hasDriverLicense: true, isAmerican: true)]\n```\n\nFilter also works with predicates:\n\n```swift\ncontacts.filter(where: \\.firstName == \"Webb\")\n```\n\n```\n[Person(firstName: \"Charlie\", lastName: \"Webb\", age: 10, hasDriverLicense: false, isAmerican: true),\n Person(firstName: \"Charles\", lastName: \"Webb\", age: 45, hasDriverLicense: true, isAmerican: true),\n Person(firstName: \"John\", lastName: \"Webb\", age: 28, hasDriverLicense: true, isAmerican: true)]\n```\n\n### filterIn\n\nFilters out elements whose value for an `Equatable` property is not in a given `Sequence`.\n\n```swift\ncontacts.filter(where: \\.firstName, in: [\"Alex\", \"John\"])\n```\n\n```\n[Person(firstName: \"Alex\", lastName: \"Elexson\", age: 22, hasDriverLicense: false, isAmerican: true), \n Person(firstName: \"Alex\", lastName: \"Zunino\", age: 34, hasDriverLicense: true, isAmerican: true), \n Person(firstName: \"Alex\", lastName: \"Alexson\", age: 8, hasDriverLicense: false, isAmerican: true), \n Person(firstName: \"John\", lastName: \"Webb\", age: 28, hasDriverLicense: true, isAmerican: true)]\n```\n\n### filterLess\n\nFilters out elements whose value is greater than a constant for a `Comparable` property.\n\n```swift\ncontacts.filter(where: \\.age, lessThan: 30)\n// or\ncontacts.filter(where: \\.age \u003c 30)\n```\n\n```\n[Person(firstName: \"Charlie\", lastName: \"Webb\", age: 10, hasDriverLicense: false, isAmerican: true), \n Person(firstName: \"Alex\", lastName: \"Elexson\", age: 22, hasDriverLicense: false, isAmerican: true), \n Person(firstName: \"Alex\", lastName: \"Alexson\", age: 8, hasDriverLicense: false, isAmerican: true), \n Person(firstName: \"John\", lastName: \"Webb\", age: 28, hasDriverLicense: true, isAmerican: true)]\n```\n \n```swift\ncontacts.filter(where: \\.age, lessOrEqual: 30)\n// or\ncontacts.filter(where: \\.age \u003c= 30)\n```\n\n```\n[Person(firstName: \"Charlie\", lastName: \"Webb\", age: 10, hasDriverLicense: false, isAmerican: true), \n Person(firstName: \"Alex\", lastName: \"Elexson\", age: 22, hasDriverLicense: false, isAmerican: true), \n Person(firstName: \"Alex\", lastName: \"Alexson\", age: 8, hasDriverLicense: false, isAmerican: true), \n Person(firstName: \"John\", lastName: \"Webb\", age: 28, hasDriverLicense: true, isAmerican: true), \n Person(firstName: \"Webb\", lastName: \"Elexson\", age: 30, hasDriverLicense: true, isAmerican: true)]\n```\n\n### filterLike\n\nFilters out elements whose value for a string property does not match a regular expression.\n\n```swift\ncontacts.filter(where: \\.lastName, like: \"^[A-Za-z]*son$\")\n```\n\n```\n[Person(firstName: \"Alex\", lastName: \"Elexson\", age: 22, hasDriverLicense: false, isAmerican: true), \n Person(firstName: \"Alex\", lastName: \"Alexson\", age: 8, hasDriverLicense: false, isAmerican: true), \n Person(firstName: \"Webb\", lastName: \"Elexson\", age: 30, hasDriverLicense: true, isAmerican: true)]\n```\n\n### filterMore\n\nFilters out elements whose value is lesser than a constant for a `Comparable` property.\n\n```swift\ncontacts.filter(where: \\.age, moreThan: 30)\n// or\ncontacts.filter(where: \\.age \u003e 30)\n```\n\n```\n[Person(firstName: \"Charles\", lastName: \"Webb\", age: 45, hasDriverLicense: true, isAmerican: true), \n Person(firstName: \"Alex\", lastName: \"Zunino\", age: 34, hasDriverLicense: true, isAmerican: true)]\n```\n\n```swift\ncontacts.filter(where: \\.age, moreOrEqual: 30)\n// or\ncontacts.filter(where: \\.age \u003e= 30)\n```\n\n```\n[Person(firstName: \"Charles\", lastName: \"Webb\", age: 45, hasDriverLicense: true, isAmerican: true), \n Person(firstName: \"Alex\", lastName: \"Zunino\", age: 34, hasDriverLicense: true, isAmerican: true), \n Person(firstName: \"Webb\", lastName: \"Elexson\", age: 30, hasDriverLicense: true, isAmerican: true)]\n```\n\n### first\n\nReturns the first element matching a predicate.\n\n```swift\ncontacts.first(where: \\.lastName == \"Webb\")\n```\n\n```\nOptional(Person(firstName: \"Charlie\", lastName: \"Webb\", age: 10, hasDriverLicense: false, isAmerican: true))\n```\n\n### groupBy\n\nGroups values by equality on the property. \n\n```swift\ncontacts.groupBy(\\.lastName)\n```\n\n```\n[\"Alexson\": [Person(firstName: \"Alex\", lastName: \"Alexson\", age: 8, hasDriverLicense: false, isAmerican: true)], \n \"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)], \n \"Elexson\": [Person(firstName: \"Alex\", lastName: \"Elexson\", age: 22, hasDriverLicense: false, isAmerican: true), Person(firstName: \"Webb\", lastName: \"Elexson\", age: 30, hasDriverLicense: true, isAmerican: true)], \n \"Zunino\": [Person(firstName: \"Alex\", lastName: \"Zunino\", age: 34, hasDriverLicense: true, isAmerican: true)]]\n```\n\n### join\n\nJoins values of two sequences in tuples by the equality on their respective property.\n\n```swift\ncontacts.join(\\.firstName, with: contacts, on: \\.lastName)\n// or\ncontacts.join(with: contacts, where: \\.firstName == \\.lastName)\n```\n\n```\n[(Person(firstName: \"Webb\", lastName: \"Elexson\", age: 30, hasDriverLicense: true, isAmerican: true), Person(firstName: \"Charlie\", lastName: \"Webb\", age: 10, hasDriverLicense: false, isAmerican: true)), \n (Person(firstName: \"Webb\", lastName: \"Elexson\", age: 30, hasDriverLicense: true, isAmerican: true), Person(firstName: \"Charles\", lastName: \"Webb\", age: 45, hasDriverLicense: true, isAmerican: true)), \n (Person(firstName: \"Webb\", lastName: \"Elexson\", age: 30, hasDriverLicense: true, isAmerican: true), Person(firstName: \"John\", lastName: \"Webb\", age: 28, hasDriverLicense: true, isAmerican: true))]\n```\n\nJoining on more than one attribute is also supported:\n\n```swift\ncontacts.join(with: contacts, .where(\\.firstName, equals: \\.lastName), .where(\\.hasDriverLicense, equals: \\.isAmerican))\n// or\ncontacts.join(with: contacts, where: \\.firstName == \\.lastName, \\.hasDriverLicense == \\.isAmerican)\n```\n\n### map\n\nMaps elements to their values of the property.\n\n```swift\ncontacts.map(\\.lastName)\n```\n\n```\n[\"Webb\", \"Elexson\", \"Webb\", \"Zunino\", \"Alexson\", \"Webb\", \"Elexson\"]\n```\n\n### mapTo\n\nMaps a sequence of properties to a function. This is, for instance, useful to extract a subset of properties into a structured type.\n\n```swift\nstruct ContactCellModel {\n    let firstName: String\n    let lastName: String\n}\n\ncontacts.map(\\.lastName, \\.firstName, to: ContactCellModel.init)\n```\n\n```\n[ContactCellModel(firstName: \"Webb\", lastName: \"Charlie\"), \n ContactCellModel(firstName: \"Elexson\", lastName: \"Alex\"), \n ContactCellModel(firstName: \"Webb\", lastName: \"Charles\"), \n ContactCellModel(firstName: \"Zunino\", lastName: \"Alex\"), \n ContactCellModel(firstName: \"Alexson\", lastName: \"Alex\"), \n ContactCellModel(firstName: \"Webb\", lastName: \"John\"), \n ContactCellModel(firstName: \"Elexson\", lastName: \"Webb\")]\n```\n\n### max\n\nReturns the element with the greatest value for a `Comparable` property.\n\n```swift\ncontacts.max(by: \\.age)\ncontacts.max(\\.age)\n```\n\n```\nOptional(Person(firstName: \"Charles\", lastName: \"Webb\", age: 45, hasDriverLicense: true, isAmerican: true))\nOptional(45)\n```\n\n### min\n\nReturns the element with the minimum value for a `Comparable` property.\n\n```swift\ncontacts.min(by: \\.age)\ncontacts.min(\\.age)\n```\n\n```\nOptional(Person(firstName: \"Alex\", lastName: \"Alexson\", age: 8, hasDriverLicense: false, isAmerican: true))\nOptional(8)\n```\n\n### or\n\nPerforms a boolean OR operation on an property of type `Bool`.\n\n```swift\ncontacts.or(\\.hasDriverLicense)\n```\n\n```\ntrue\n```\n\n### patternMatching\n\nAllows the use of predicates inside a `switch` statement:\n\n```swift\nswitch person {\ncase \\.firstName == \"Charlie\":\n    print(\"I'm Charlie!\")\n    fallthrough\ncase \\.age \u003c 18:\n    print(\"I'm not an adult...\")\n    fallthrough\ndefault:\n    break\n}\n```\n\n### prefix\n\nReturns a subsequence containing the initial, consecutive elements for whose a property of type `Bool` or a predicate evaluates to true.\n\n```swift\ncontacts.prefix(while: \\.age \u003c 40)\n```\n\n```\n[Person(firstName: \"Charlie\", lastName: \"Webb\", age: 10, hasDriverLicense: false, isAmerican: true),\n Person(firstName: \"Alex\", lastName: \"Elexson\", age: 22, hasDriverLicense: false, isAmerican: true)]\n```\n\n### sum\n\nCalculates the sum of the values for a numerical property.\n\n```swift\ncontacts.sum(of: \\.age)\n```\n\n```\n177\n```\n\n### sort\n\nSorts the elements with respect to a `Comparable` property.\n\n```swift\ncontacts.sorted(by: \\.age)\n```\n\n```\n[Person(firstName: \"Alex\", lastName: \"Alexson\", age: 8, hasDriverLicense: false, isAmerican: true), \n Person(firstName: \"Charlie\", lastName: \"Webb\", age: 10, hasDriverLicense: false, isAmerican: true), \n Person(firstName: \"Alex\", lastName: \"Elexson\", age: 22, hasDriverLicense: false, isAmerican: true), Person(firstName: \"John\", lastName: \"Webb\", age: 28, hasDriverLicense: true, isAmerican: true), \n Person(firstName: \"Webb\", lastName: \"Elexson\", age: 30, hasDriverLicense: true, isAmerican: true), \n Person(firstName: \"Alex\", lastName: \"Zunino\", age: 34, hasDriverLicense: true, isAmerican: true), \n Person(firstName: \"Charles\", lastName: \"Webb\", age: 45, hasDriverLicense: true, isAmerican: true)]\n```\n\nIt's also possible to specify the sorting order, to sort on multiple criteria, or to do both.\n\n```swift\ncontacts.sorted(by: .ascending(\\.lastName), .descending(\\.age))\n```\n\n```\n[Person(firstName: \"Alex\", lastName: \"Alexson\", age: 8, hasDriverLicense: false, isAmerican: true), \n Person(firstName: \"Webb\", lastName: \"Elexson\", age: 30, hasDriverLicense: true, isAmerican: true), \n Person(firstName: \"Alex\", lastName: \"Elexson\", age: 22, hasDriverLicense: false, isAmerican: true), \n Person(firstName: \"Charles\", lastName: \"Webb\", age: 45, hasDriverLicense: true, isAmerican: true), \n Person(firstName: \"John\", lastName: \"Webb\", age: 28, hasDriverLicense: true, isAmerican: true), \n Person(firstName: \"Charlie\", lastName: \"Webb\", age: 10, hasDriverLicense: false, isAmerican: true), \n Person(firstName: \"Alex\", lastName: \"Zunino\", age: 34, hasDriverLicense: true, isAmerican: true)]\n```\n\n## Author\n\n* Twitter: [@v_pradeilles](https://twitter.com/v_pradeilles)\n\n## Thanks\n\nA big thank you to Jérôme Alves ([elegantswift.com](http://elegantswift.com)) for coming up with the right modelization to allow sorting on multiple properties with heterogenous type.\n","funding_links":[],"categories":["Data Structures / Algorithms","Libs","Swift","Data Management [🔝](#readme)"],"sub_categories":["Getting Started","Data Management","Other free courses","Linter"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvincent-pradeilles%2FKeyPathKit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvincent-pradeilles%2FKeyPathKit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvincent-pradeilles%2FKeyPathKit/lists"}