{"id":19745654,"url":"https://github.com/lowmelvin/hammer-scala","last_synced_at":"2025-04-30T07:34:38.701Z","repository":{"id":183929304,"uuid":"671017984","full_name":"lowmelvin/hammer-scala","owner":"lowmelvin","description":"Convert your case classes automatically","archived":false,"fork":false,"pushed_at":"2024-11-07T17:08:56.000Z","size":144,"stargazers_count":4,"open_issues_count":2,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-11-07T18:23:31.043Z","etag":null,"topics":["data-conversion","generics","metaprogramming","scala","scala-3"],"latest_commit_sha":null,"homepage":"","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/lowmelvin.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-07-26T10:58:55.000Z","updated_at":"2024-11-07T17:09:00.000Z","dependencies_parsed_at":null,"dependency_job_id":"ecbfca16-7cdd-44de-9e82-c4c9dcf30092","html_url":"https://github.com/lowmelvin/hammer-scala","commit_stats":null,"previous_names":["lowmelvin/hammer-scala"],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lowmelvin%2Fhammer-scala","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lowmelvin%2Fhammer-scala/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lowmelvin%2Fhammer-scala/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lowmelvin%2Fhammer-scala/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lowmelvin","download_url":"https://codeload.github.com/lowmelvin/hammer-scala/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224202851,"owners_count":17272807,"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-conversion","generics","metaprogramming","scala","scala-3"],"created_at":"2024-11-12T02:10:42.875Z","updated_at":"2024-11-12T02:10:43.542Z","avatar_url":"https://github.com/lowmelvin.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Hammer-Scala\n\nHammer-Scala is a Scala 3 utility library that allows quick and painless\ndata conversion between different product types.\n\nTo include Hammer in your project, add the following to your dependencies:\n\n```scala\nlibraryDependencies += \"com.melvinlow\" %% \"hammer\" % \u003cversion\u003e\n```\n\n## Quick Start\n\n\nAssume you have an Algebraic Data Type (ADT) like this:\n\n```scala\ncase class AccountEntity(id: String, email: String, name: String, secret: String, createdAt: Instant)\n\nval entity = AccountEntity(\"123\", \"test@example.com\", \"nobo\", \"should-be-hashed\", Instant.now)\n```\n\nAnd you want to convert it to something like this:\n\n```scala\ncase class Account(id: String, name: String, email: String)\n```\n\nWith Hammer, you can make this conversion in one function call:\n\n```scala\nimport com.melvinlow.hammer.instances.auto.given\nimport com.melvinlow.hammer.syntax.all.*\nimport com.melvinlow.hammer.*\n\nentity.hammerTo[Account]\n// res0: Account = Account(\n//   id = \"123\",\n//   name = \"nobo\",\n//   email = \"test@example.com\"\n// )\n```\n\n## Introduction\n\nIn many scenarios, we create case classes that are simpler versions of others. For example, you might have a comprehensive model representation for your database and a leaner version for your API consumers.\n\nManually constructing these leaner versions can be tedious and error-prone:\n\n```scala\ncase class Octagon(a: Int, b: Int, c: Int, d: Int, e: Int, f: Int, g: Int, h: Int)\ncase class Hexagon(b: Int, c: Int, d: Int, e: Int, f: Int, g: Int, h: Int)\n\nval octagon = Octagon(1, 2, 3, 4, 5, 6, 7, 8)\n// octagon: Octagon = Octagon(\n//   a = 1,\n//   b = 2,\n//   c = 3,\n//   d = 4,\n//   e = 5,\n//   f = 6,\n//   g = 7,\n//   h = 8\n// )\n\nval hexagon = Hexagon(octagon.b, octagon.c, octagon.d, octagon.e, octagon.f, octagon.g, octagon.h)\n// hexagon: Hexagon = Hexagon(b = 2, c = 3, d = 4, e = 5, f = 6, g = 7, h = 8)\n```\n\nHammer uses generic programming techniques to automate the process:\n\n```scala\noctagon.hammerTo[Hexagon]\n// res1: Hexagon = Hexagon(b = 2, c = 3, d = 4, e = 5, f = 6, g = 7, h = 8)\n```\n\nIt can handle missing and swapped fields:\n\n```scala\ncase class A(x: String, y: String, z: String)\ncase class B(z: String, x: String)\n\nA(\"x\", \"y\", \"z\").hammerTo[B]\n// res2: B = B(z = \"z\", x = \"x\")\n```\n\nAnd also nested fields:\n\n```scala\ncase class CompanyEntity(name: String, createdAt: Instant)\ncase class PersonEntity(name: String, company: CompanyEntity, createdAt: Instant)\n\ncase class Company(name: String)\ncase class Person(name: String, company: Company)\n\nPersonEntity(\"John\", CompanyEntity(\"Scala\", Instant.now), Instant.now)\n  .hammerTo[Person]\n// res3: Person = Person(name = \"John\", company = Company(name = \"Scala\"))\n```\n\nIt can also convert types, such as for auto-unboxing wrapper types:\n\n```scala\nopaque type EmailAddress = String\nobject EmailAddress {\n  def apply(email: String) = email\n  extension (email: EmailAddress) def underlying: String = email\n\n  // Define how to hammer an email address to a string\n  given Hammer[EmailAddress, String] = (e: EmailAddress) =\u003e e.underlying\n}\n\ncase class Boxed(email: EmailAddress)\ncase class Unboxed(email: String)\n\nBoxed(EmailAddress(\"test@example.com\")).hammerTo[Unboxed]\n// res4: Unboxed = Unboxed(email = \"test@example.com\")\n```\n\nImportantly, it will error at compile time if conversion is not possible:\n\n```scala\ncase class Cat(name: String, voice: \"Meow\")\ncase class Dog(name: String, voice: \"Bark\")\n\nCat(\"Peanuts\", \"Meow\").hammerTo[Dog]\n// error: \n// No given instance of type com.melvinlow.hammer.Hammer[(\"Meow\" : String), (\"Bark\" : String)] was found.\n// I found:\n// \n//     com.melvinlow.hammer.instances.auto.given_Hammer_I_O[(\"Meow\" : String),\n//       (\"Bark\" : String)](\n//       /* missing */summon[deriving.Mirror.ProductOf[(\"Meow\" : String)]], ???)\n// \n// But Failed to synthesize an instance of type deriving.Mirror.ProductOf[(\"Meow\" : String)]: class String is not a generic product because it is not a case class.\n```\n\n## Usage\n\nSimply include the correct imports and call the `hammerTo` method:\n\n```scala\nimport com.melvinlow.hammer.instances.auto.given\nimport com.melvinlow.hammer.syntax.all.*\n```\n\n## Advanced Usage\n\nHammer includes patching functionality that allows you to override\nspecific fields. To do this, call the `hammerWith` extension method:\n\n```scala\ncase class HumanEntity(name: String, age: Int)\ncase class Human(name: String, age: Int)\n\n// Override the name field\nHumanEntity(\"Hami\", 18).hammerWith[Human](Patch[\"name\"](\"Arno\"))\n// res6: Human = Human(name = \"Arno\", age = 18)\n```\n\nThe same method can also be used to convert a case class to a richer case class\nby providing the values of the missing fields:\n\n```scala\ncase class Engineer(name: String)\ncase class EngineerEntity(name: String, createdAt: Instant, updatedAt: Instant)\n\nEngineer(\"Lynn\").hammerWith[EngineerEntity](\n  Patch[\"createdAt\"](Instant.now),\n  Patch[\"updatedAt\"](Instant.now)\n)\n// res7: EngineerEntity = EngineerEntity(\n//   name = \"Lynn\",\n//   createdAt = 2023-07-31T06:14:57.426081Z,\n//   updatedAt = 2023-07-31T06:14:57.426083Z\n// )\n```\n\nAs shown, you are required to provide the desired output type (can be inferred) along with a\nvariable number of `Patch` objects that each contain the field name and value to inject.\nThe `Patch` objects can be specified in any order.\n\nFor comparison, these two usages are equivalent:\n\n```scala\noctagon.hammerTo[Hexagon]\n// res8: Hexagon = Hexagon(b = 2, c = 3, d = 4, e = 5, f = 6, g = 7, h = 8)\n\noctagon.hammerWith[Hexagon]()\n// res9: Hexagon = Hexagon(b = 2, c = 3, d = 4, e = 5, f = 6, g = 7, h = 8)\n```\n\nFinally, note that only top level patching is supported--it is not possible\nto inject a value somewhere deep into a nested case class.\n\n## Typeclasses and Extensions\n\nTo automatically convert from type `I` to `O`, provide an instance of `Hammer[I, O]`:\n\n```scala\nimport com.melvinlow.hammer.*\n\ntrait Hammer[I, O] {\n  def hammer(input: I): O\n}\n```\n\nFor example, you could automatically lift values to `Option`:\n\n\n```scala\ngiven [I]: Hammer[I, Option[I]] = (i: I) =\u003e Some(i)\n\ncase class F(x: Int)\ncase class G(x: Option[Int])\n\nF(1).hammerTo[G]\n// res11: G = G(x = Some(value = 1))\n```\n\nUnderneath the hood, Hammer simply looks for a matching field name\nwhere a `Hammer[I, O]` is provided. The default imports include an\nidentity hammer (i.e., `Hammer[I, I]`) so that if two fields\nhave the same name and type, they are always compatible.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flowmelvin%2Fhammer-scala","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flowmelvin%2Fhammer-scala","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flowmelvin%2Fhammer-scala/lists"}