{"id":13682770,"url":"https://github.com/softwaremill/quicklens","last_synced_at":"2025-05-15T15:04:28.468Z","repository":{"id":27968936,"uuid":"31462079","full_name":"softwaremill/quicklens","owner":"softwaremill","description":"Modify deeply nested case class fields","archived":false,"fork":false,"pushed_at":"2024-05-17T00:06:56.000Z","size":731,"stargazers_count":814,"open_issues_count":26,"forks_count":53,"subscribers_count":44,"default_branch":"master","last_synced_at":"2024-05-22T01:01:53.805Z","etag":null,"topics":["functional-programming","lenses","scala"],"latest_commit_sha":null,"homepage":"https://softwaremill.com/open-source/","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/softwaremill.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-02-28T12:32:34.000Z","updated_at":"2024-06-19T02:52:08.176Z","dependencies_parsed_at":"2024-01-23T21:01:10.016Z","dependency_job_id":"3ebbdca3-adc6-48e4-a207-0fcf903ee7f6","html_url":"https://github.com/softwaremill/quicklens","commit_stats":null,"previous_names":["adamw/quicklens"],"tags_count":50,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/softwaremill%2Fquicklens","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/softwaremill%2Fquicklens/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/softwaremill%2Fquicklens/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/softwaremill%2Fquicklens/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/softwaremill","download_url":"https://codeload.github.com/softwaremill/quicklens/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254364270,"owners_count":22058878,"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":["functional-programming","lenses","scala"],"created_at":"2024-08-02T13:01:52.925Z","updated_at":"2025-05-15T15:04:28.419Z","avatar_url":"https://github.com/softwaremill.png","language":"Scala","readme":"![Quicklens](https://github.com/softwaremill/quicklens/raw/master/banner.png)\n\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.softwaremill.quicklens/quicklens_2.13/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.softwaremill.quicklens/quicklens_2.13)\n[![CI](https://github.com/softwaremill/quicklens/workflows/CI/badge.svg)](https://github.com/softwaremill/quicklens/actions?query=workflow%3A%22CI%22)\n\n**Modify deeply nested fields in case classes:**\n\n````scala\nimport com.softwaremill.quicklens._\n\ncase class Street(name: String)\ncase class Address(street: Street)\ncase class Person(address: Address, age: Int)\n\nval person = Person(Address(Street(\"1 Functional Rd.\")), 35)\n\nval p2 = person.modify(_.address.street.name).using(_.toUpperCase)\nval p3 = person.modify(_.address.street.name).setTo(\"3 OO Ln.\")\n\n// or\n \nval p4 = modify(person)(_.address.street.name).using(_.toUpperCase)\nval p5 = modify(person)(_.address.street.name).setTo(\"3 OO Ln.\")\n````\n\n**Chain modifications:**\n\n````scala\nperson\n  .modify(_.address.street.name).using(_.toUpperCase)\n  .modify(_.age).using(_ - 1)\n````\n\n**Modify conditionally:**\n\n````scala\nperson.modify(_.address.street.name).setToIfDefined(Some(\"3 00 Ln.\"))\nperson.modify(_.address.street.name).setToIf(shouldChangeAddress)(\"3 00 Ln.\")\n````\n\n**Modify several fields in one go:**\n\n````scala\nimport com.softwaremill.quicklens._\n\ncase class Person(firstName: String, middleName: Option[String], lastName: String)\n\nval person = Person(\"john\", Some(\"steve\"), \"smith\")\n\nperson.modifyAll(_.firstName, _.middleName.each, _.lastName).using(_.capitalize)\n````\n\n**Traverse options/lists/maps using `.each`:**\n\n````scala\nimport com.softwaremill.quicklens._\n\ncase class Street(name: String)\ncase class Address(street: Option[Street])\ncase class Person(addresses: List[Address])\n\nval person = Person(List(\n  Address(Some(Street(\"1 Functional Rd.\"))),\n  Address(Some(Street(\"2 Imperative Dr.\")))\n))\n\nval p2 = person.modify(_.addresses.each.street.each.name).using(_.toUpperCase)\n````\n\n`.each` can only be used inside a `modify` and \"unwraps\" the container (currently supports `Seq`s, `Option`s and\n`Maps`s - only values are unwrapped for maps).\nYou can add support for your own containers by providing an implicit `QuicklensFunctor[C]` with the appropriate\n`C` type parameter.\n\n**Traverse selected elements using `.eachWhere`:**\n\nSimilarly to `.each`, you can use `.eachWhere(p)` where `p` is a predicate to modify only the elements which satisfy\nthe condition. All other elements remain unchanged.\n\n````scala\ndef filterAddress: Address =\u003e Boolean = ???\nperson\n  .modify(_.addresses.eachWhere(filterAddress)\n           .street.eachWhere(_.name.startsWith(\"1\")).name)\n  .using(_.toUpperCase)\n````\n\n**Modify specific elements in an option/sequence/map using `.at`:**\n\n````scala\nperson.modify(_.addresses.at(2).street.at.name).using(_.toUpperCase)\n````\n\nSimilarly to `.each`, `.at` modifies only the element at the given index/key. If there's no element at that index,\nan `IndexOutOfBoundsException` is thrown. In the above example, `.at(2)` selects an element in `addresses: List[Address]`\n and `.at` selects the lone possible element in `street: Option[Street]`. If `street` is `None`, a\n `NoSuchElementException` is thrown.\n \n`.at` works for map keys as well:\n\n````scala\ncase class Property(value: String)\n\ncase class PersonWithProps(name: String, props: Map[String, Property])\n\nval personWithProps = PersonWithProps(\n  \"Joe\",\n  Map(\"Role\" -\u003e Property(\"Programmmer\"), \"Age\" -\u003e Property(\"45\"))\n)\n\npersonWithProps.modify(_.props.at(\"Age\").value).setTo(\"45\")\n````\n\nSimilarly to `.each`, `.at` modifies only the element with the given key. If there's no such element,\nan `NoSuchElementException` is thrown.\n\n**Modify specific elements in an option/sequence/map using `.index`:**\n\n````scala\nperson.modify(_.addresses.index(2).street.index.name).using(_.toUpperCase)\n````\n\nSimilarly to `.at`, `.index` modifies only the element at the given index/key. If there's no element at that index,\nno modification is made. In the above example, `.index(2)` selects an element in `addresses: List[Address]`\n and `.index` selects the lone possible element in `street: Option[Street]`. If `street` is `None`, no modification\n is made.\n \n`.index` works for map keys as well:\n\n````scala\ncase class Property(value: String)\n\ncase class PersonWithProps(name: String, props: Map[String, Property])\n\nval personWithProps = PersonWithProps(\n  \"Joe\",\n  Map(\"Role\" -\u003e Property(\"Programmmer\"), \"Age\" -\u003e Property(\"45\"))\n)\n\npersonWithProps.modify(_.props.index(\"Age\").value).setTo(\"45\")\n````\n\nSimilarly to `.at`, `.index` modifies only the element with the given key. If there's no such element,\nno modification is made.\n\n**Modify specific elements in an option or map with a fallback using `.atOrElse`:**\n\n````scala\npersonWithProps.modify(_.props.atOrElse(\"NumReports\", Property(\"0\")).value).setTo(\"5\")\n````\n\nIf `props` contains an entry for `\"NumReports\"`, then `.atOrElse` behaves the same as `.at` and the second\nparameter is never evaluated. If there is no entry, then `.atOrElse` will make one using the second parameter\n and perform subsequent modifications on the newly instantiated default.\n \nFor Options, `.atOrElse` takes no arguments and acts similarly. \n \n ````scala\nperson.modify(_.addresses.at(2).street.atOrElse(Street(\"main street\")).name).using(_.toUpperCase)\n ````\n \n `.atOrElse` is currently not available for sequences because quicklens might need to insert many\n elements in the list in order to ensure that one is available at a particular position, and it's not\n clear that providing one default for all keys is the right behavior. \n\n**Modify Either fields using `.eachLeft` and `.eachRight`:**\n\n````scala\ncase class AuthContext(token: String)\ncase class AuthRequest(url: String)\ncase class Resource(auth: Either[AuthContext, AuthRequest])\n\nval devResource = Resource(auth = Left(AuthContext(\"fake\"))\n\nval prodResource = devResource.modify(_.auth.eachLeft.token).setTo(\"real\")\n\n````\n\n**Modify fields when they are of a certain subtype:**\n\n```scala\ntrait Animal\ncase class Dog(age: Int) extends Animal\ncase class Cat(ages: Seq[Int]) extends Animal\n\ncase class Zoo(animals: Seq[Animal])\n\nval zoo = Zoo(List(Dog(4), Cat(List(3, 12, 13))))\n\nval olderZoo = zoo.modifyAll(\n  _.animals.each.when[Dog].age,\n  _.animals.each.when[Cat].ages.at(0)\n).using(_ + 1)\n```\n\nThis is also known as a *prism*, see e.g. [here](https://www.optics.dev/Monocle/docs/optics/prism).\n\n**Re-usable modifications (lenses):**\n\n````scala\nimport com.softwaremill.quicklens._\n\nval modifyStreetName = modify(_: Person)(_.address.street.name)\n\nval p3 = modifyStreetName(person).using(_.toUpperCase)\nval p4 = modifyStreetName(anotherPerson).using(_.toLowerCase)\n\n//\n\nval upperCaseStreetName = modify(_: Person)(_.address.street.name).using(_.toUpperCase)\n\nval p5 = upperCaseStreetName(person)\n````\nAlternate syntax:\n````scala\nimport com.softwaremill.quicklens._\n\nval modifyStreetName = modifyLens[Person](_.address.street.name)\n\nval p3 = modifyStreetName.using(_.toUpperCase)(person)\nval p4 = modifyStreetName.using(_.toLowerCase)(anotherPerson)\n\n//\n\nval upperCaseStreetName = modifyLens[Person](_.address.street.name).using(_.toUpperCase)\n\nval p5 = upperCaseStreetName(person)\n````\n\n**Composing lenses:**\n\n````scala\nimport com.softwaremill.quicklens._\n\nval modifyAddress = modify(_: Person)(_.address)\nval modifyStreetName = modify(_: Address)(_.street.name)\n\nval p6 = (modifyAddress andThenModify modifyStreetName)(person).using(_.toUpperCase)\n````\nor, with alternate syntax:\n````scala\nimport com.softwaremill.quicklens._\n\nval modifyAddress = modifyLens[Person](_.address)\nval modifyStreetName = modifyLens[Address](_.street.name)\n\nval p6 = (modifyAddress andThenModify modifyStreetName).using(_.toUpperCase)(person)\n````\n\n\n**Modify nested sealed hierarchies \u0026 enums:**\n\n````scala\nimport com.softwaremill.quicklens._\n\nsealed trait Pet { def name: String }\ncase class Fish(name: String) extends Pet\nsealed trait LeggedPet extends Pet\ncase class Cat(name: String) extends LeggedPet\ncase class Dog(name: String) extends LeggedPet\n\nval pets = List[Pet](\n  Fish(\"Finn\"), Cat(\"Catia\"), Dog(\"Douglas\")\n)\n\nval juniorPets = pets.modify(_.each.name).using(_ + \", Jr.\")\n````\n\n---\n\nAlso check out [Monocle](https://github.com/julien-truffaut/Monocle), for a more advanced [lens](http://eed3si9n.com/learning-scalaz/Lens.html) library.\n\nRead [the blog](http://www.warski.org/blog/2015/02/quicklens-modify-deeply-nested-case-class-fields/) for more info.\n\nAvailable in Maven Central:\n\n````scala\nval quicklens = \"com.softwaremill.quicklens\" %% \"quicklens\" % \"1.9.12\"\n````\n\nAvailable for Scala 2.11, 2.12, 2.13, [3](https://dotty.epfl.ch), [Scala.js](http://www.scala-js.org) and [Scala Native](http://www.scala-native.org)!\n\n## Commercial Support\n\nWe offer commercial support for Quicklens and related technologies, as well as development services. [Contact us](https://softwaremill.com) to learn more about our offer!\n\n## Copyright\n\nCopyright (C) 2015-2021 SoftwareMill [https://softwaremill.com](https://softwaremill.com).\n","funding_links":[],"categories":["Scala","Table of Contents","Functional Programming"],"sub_categories":["Extensions"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsoftwaremill%2Fquicklens","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsoftwaremill%2Fquicklens","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsoftwaremill%2Fquicklens/lists"}