{"id":37016727,"url":"https://github.com/tethys-json/tethys","last_synced_at":"2026-01-14T01:54:20.478Z","repository":{"id":26476409,"uuid":"104213478","full_name":"tethys-json/tethys","owner":"tethys-json","description":"AST free JSON library for Scala ","archived":false,"fork":false,"pushed_at":"2025-11-01T16:03:25.000Z","size":2165,"stargazers_count":116,"open_issues_count":19,"forks_count":41,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-11-01T18:05:33.746Z","etag":null,"topics":["jackson","json","json-derivation","json-parsing","json-serialization","scala","scala-json","scala-json-library","tethys","tethys-json"],"latest_commit_sha":null,"homepage":"https://tethys-json.github.io/tethys/","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/tethys-json.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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2017-09-20T12:37:23.000Z","updated_at":"2025-11-01T16:03:29.000Z","dependencies_parsed_at":"2025-07-14T16:23:17.714Z","dependency_job_id":null,"html_url":"https://github.com/tethys-json/tethys","commit_stats":null,"previous_names":[],"tags_count":28,"template":false,"template_full_name":null,"purl":"pkg:github/tethys-json/tethys","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tethys-json%2Ftethys","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tethys-json%2Ftethys/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tethys-json%2Ftethys/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tethys-json%2Ftethys/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tethys-json","download_url":"https://codeload.github.com/tethys-json/tethys/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tethys-json%2Ftethys/sbom","scorecard":{"id":875372,"data":{"date":"2025-08-11","repo":{"name":"github.com/tethys-json/tethys","commit":"533d37636bacf3b6ba795caf37215f2a00008ce0"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":6,"checks":[{"name":"Code-Review","score":10,"reason":"all changesets reviewed","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":10,"reason":"16 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/label-verifier.yml:1","Warn: no topLevel permission defined: .github/workflows/release-drafter.yml:1","Warn: no topLevel permission defined: .github/workflows/scala.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Branch-Protection","score":5,"reason":"branch protection is not maximal on development and all release branches","details":["Info: 'allow deletion' disabled on branch 'master'","Info: 'force pushes' disabled on branch 'master'","Warn: 'branch protection settings apply to administrators' is disabled on branch 'master'","Warn: 'stale review dismissal' is disabled on branch 'master'","Warn: required approving review count is 1 on branch 'master'","Warn: codeowners review is not required on branch 'master'","Warn: 'last push approval' is disabled on branch 'master'","Warn: no status checks found to merge onto branch 'master'","Info: PRs are required in order to make changes on branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: third-party GitHubAction not pinned by hash: .github/workflows/label-verifier.yml:11: update your workflow using https://app.stepsecurity.io/secureworkflow/tethys-json/tethys/label-verifier.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/release-drafter.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/tethys-json/tethys/release-drafter.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/scala.yml:20: update your workflow using https://app.stepsecurity.io/secureworkflow/tethys-json/tethys/scala.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/scala.yml:21: update your workflow using https://app.stepsecurity.io/secureworkflow/tethys-json/tethys/scala.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/scala.yml:25: update your workflow using https://app.stepsecurity.io/secureworkflow/tethys-json/tethys/scala.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/scala.yml:33: update your workflow using https://app.stepsecurity.io/secureworkflow/tethys-json/tethys/scala.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/scala.yml:34: update your workflow using https://app.stepsecurity.io/secureworkflow/tethys-json/tethys/scala.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/scala.yml:38: update your workflow using https://app.stepsecurity.io/secureworkflow/tethys-json/tethys/scala.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/scala.yml:48: update your workflow using https://app.stepsecurity.io/secureworkflow/tethys-json/tethys/scala.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/scala.yml:51: update your workflow using https://app.stepsecurity.io/secureworkflow/tethys-json/tethys/scala.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/scala.yml:55: update your workflow using https://app.stepsecurity.io/secureworkflow/tethys-json/tethys/scala.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/scala.yml:57: update your workflow using https://app.stepsecurity.io/secureworkflow/tethys-json/tethys/scala.yml/master?enable=pin","Info:   0 out of   6 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   6 third-party GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Packaging","score":10,"reason":"packaging workflow detected","details":["Info: Project packages its releases by way of GitHub Actions.: .github/workflows/scala.yml:42"],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 30 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-24T05:46:01.776Z","repository_id":26476409,"created_at":"2025-08-24T05:46:01.776Z","updated_at":"2025-08-24T05:46:01.776Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28408692,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T00:40:43.272Z","status":"ssl_error","status_checked_at":"2026-01-14T00:40:42.636Z","response_time":56,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["jackson","json","json-derivation","json-parsing","json-serialization","scala","scala-json","scala-json-library","tethys","tethys-json"],"created_at":"2026-01-14T01:54:19.798Z","updated_at":"2026-01-14T01:54:20.470Z","avatar_url":"https://github.com/tethys-json.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"| CI | Release | \n| --- | --- |\n| [![Build Status](https://github.com/tethys-json/tethys/workflows/Scala%20CI/badge.svg)](https://github.com/tethys-json/tethys/actions) | [![Maven Central](https://img.shields.io/maven-central/v/com.tethys-json/tethys-core_2.13.svg)](https://search.maven.org/search?q=com.tethys-json.tethys-json) | \n\n# tethys\ntethys is AST free json library for Scala\n\nIt's advantages:\n1. Performant\n    * Built in bridge to jackson [(benchmarks)](./modules/benchmarks)\n   \n\n2. User friendly\n    * Build reader/writer by hand for product and sum types\n    * Configurable recursive semiauto derivation\n    * Discriminator support for sum types derivation\n\nRead more about library usage bellow:\n- [Scala 3](#scala-3)\n- [Scala 2](#scala-2)\n\n\n# Scala 3\n\n## Quick start\n\n```scala\nval tethysVersion = \"latest version in badge\"\nlibraryDependencies ++= Seq(\n  \"com.tethys-json\" %% \"tethys-core\" % tethysVersion,\n  \"com.tethys-json\" %% \"tethys-jackson213\" % tethysVersion\n)\n```\n\n## Read/Write JSON API\ntethys provides extension methods allowing you to read and write JSON\n\nThey look something like this:\n```scala\npackage tethys\n\nextension [A](value: A)\n  def asJson(using \n    jw: JsonWriter[A],\n    twp: TokenWriterProducer\n  ): String = ???\n\nextension (value: String)\n  def readJson[A](using \n    jr: JsonReader[A],\n    tip: TokenIteratorProducer\n  ): Either[ReaderError, A] = ???\n```\n\nTethys provides **TokenWriterProducer** and **TokenIteratorProducer** automatically,\nso in most cases you only need to provide **JsonReader** or **JsonWriter**.\nLet's see how can we get one.\n\n\n## Basic instances\ntethys provides **JsonReader** and **JsonWriter** instances for a bunch of basic types\n\nCheck links below to see exact ones:\n\n[JsonReader instances](./modules/core/src/main/scala/tethys/readers/instances/AllJsonReaders.scala)\n\n[JsonWriters instances](./modules/core/src/main/scala/tethys/writers/instances/AllJsonWriters.scala)\n\n## Build instances by hand\n\n### map and contramap\n\nYou can create new instances for your types using:\n1. **contramap** on already existing writer\n2. **map** on already existing reader\n\n```scala\nimport tethys.*\n\ncase class StringWrapper(value: String) extends AnyVal\n\ngiven JsonWriter[StringWrapper] =\n   JsonWriter[String].contramap(_.value)\n   \ngiven JsonReader[StringWrapper] =\n   JsonReader[String].map(StringWrapper(_))\n```\n\n### JsonWriter\n\nTo build JsonWriter for case class you can use `obj` method on its companion object.  \n\n```scala\nimport tethys.*\n\n  case class MobileSession(\n    id: Long, \n    deviceId: String, \n    userId: java.lang.UUID\n  ) extends Session\n  \nobject MobileSession:\n  given JsonObjectWriter[MobileSession] = JsonWriter.obj[MobileSession]\n    .addField(\"id\")(_.id)\n    .addField(\"deviceId\")(_.deviceId)\n    .addField(\"userId\")(_.userId)\n```\n\nYou can concat multiple **JsonObjectWriter**.  \nCombining concatenation with derivation allows to create **JsonWriter** for sealed trait.\nTo derive JsonWriter for sealed trait you need to have **JsonObjectWriter** instances for all subtypes in scope\n\n```scala\ngiven JsonWriter[Session] =\n   JsonWriter.obj[Session].addField(\"typ\")(_.typ) ++ JsonObjectWriter.derived[Session]\n```\n\n\n### JsonReader\n\nTo build JsonReader for case class you can use `builder` method on its companion object.\n\n```scala\nimport tethys.*\n\n  case class MobileSession(\n    id: Long, \n    deviceId: String, \n    userId: java.lang.UUID\n  ) extends Session(\"mobile\")\n  \n  object Mobile:\n    given JsonReader[MobileSession] = JsonReader.builder\n      .addField[Long](\"id\")\n      .addField[String](\"deviceId\")\n      .addField[java.lang.UUID](\"userId\")\n      .buildReader(MobileSession(_, _, _))\n```   \n\nTo build JsonReader for sealed trait you can use `selectReader` after adding some field:\n\n```scala\nimport tethys.*\n  \n  object Session:\n    given webReader: JsonReader[WebSession] = ???\n    given mobileReader: JsonReader[MobileSession] = ???\n    \n    given JsonReader[Session] = JsonReader.builder\n      .addField[String](\"typ\")\n      .selectReader {\n         case \"web\" =\u003e webReader\n         case \"mobile\" =\u003e mobileReader\n      }\n```  \n\n\n## Derivation\n\nAll examples consider you made this imports:\n```scala\nimport tethys.*\nimport tethys.jackson.* // or tethys.jackson.pretty.* for pretty printing\n```\n\n\n### Basic enums\n1. **StringEnumJsonWriter** and **StringEnumJsonReader**\n\n```scala\nenum SessionType derives StringEnumJsonWriter, StringEnumJsonReader:\n  case Mobile, Web\n \ncase class Session(typ: SessionType) derives JsonReader, JsonObjectWriter\n\nval session = Session(typ = SessionType.Mobile)\nval json = \"\"\"{\"typ\": \"Mobile\"}\"\"\"\n\njson.jsonAs[Session] == Right(session)\nsession.asJson == json\n```\n2. **OrdinalEnumJsonWriter** and **OrdinalEnumJsonReader**\n\n```scala\nenum SessionType derives OrdinalEnumJsonWriter, OrdinalEnumJsonReader:\n  case Mobile, Web\n \ncase class Session(typ: SessionType) derives JsonReader, JsonObjectWriter\n\nval session = Session(typ = SessionType.Web)\nval json = \"\"\"{\"typ\": \"1\"}\"\"\"\n\njson.jsonAs[Session] == Right(session)\nsession.asJson == json\n```\n\n### Case classes\n\n```scala\ncase class Session(\n    id: Long, \n    userId: String\n) derives JsonReader, JsonObjectWriter\n\nval session = Session(id = 123, userId = \"3-X56812\")\nval json = \"\"\"{\"id\": 123, \"userId\": \"3-X56812\"}\"\"\"\n\njson.jsonAs[Session] == Right(session)\nsession.asJson == json\n```\n\n### Sealed traits and enums\nTo derive **JsonReader** you **must** provide a discriminator.\nThis can be done via **selector** annotation.    \nDiscriminator for **JsonWriter** is optional.\n\nIf you don't need readers/writers for subtypes, you can omit them,\nthey will be derived recursively for your trait/enum.\n\n```scala\nimport tethys.selector\n\nsealed trait UserAccount(@selector val typ: String) derives JsonReader, JsonObjectWriter\n\nobject UserAccount:\n   case class Customer(\n        id: Long,\n        phone: String\n   ) extends UserAccount(\"Customer\")\n   \n   case class Employee(\n        id: Long,\n        phone: String,\n        position: String\n   ) extends UserAccount(\"Employee\")\n\nval account: UserAccount = UserAccount.Customer(id = 123, phone = \"+12394283293\"\nval json = \"\"\"{\"typ\": \"Customer\", \"id\": 123, \"userId\": \"+12394283293\"}\"\"\"\n\njson.jsonAs[UserAccount] == Right(account)\naccount.asJson == json\n```\n\n## Configuration\n\n\n### Configuration via **ReaderBuilder** and **WriterBuilder**\n1. You can configure only case class derivation\n2. To configure **JsonReader** use **ReaderBuilder**\n3. To configure **JsonWriter** use **WriterBuilder**\n4. Configuration can be provided:\n   * **directly to derived method**\n   ```scala\n      given JsonWriter[UserAccount.Customer] = \n        JsonObjectWriter.derived {\n          WriterBuilder[UserAccount.Customer]\n        }\n   ```\n   * **as an inline given to derives**\n   ```scala\n      object Customer:\n        inline given WriterBuilder[UserAccount.Customer] =\n          WriterBuilder[UserAccount.Customer]\n   ```\n   P.S. There are empty **WriterBuilder** in the examples to simplify demonstration of two approaches. You shouldn't use empty one\n5. **WriterBuilder** features\n```scala\ncase class Foo(a: Int, b: String, c: Any, d: Boolean, e: Double)\n\ninline given WriterBuilder[Foo] =\n   WriterBuilder[Foo]\n     // choose field style\n     .fieldStyle(FieldStyle.UpperSnakeCase)\n     // remove field\n     .remove(_.b)\n     // add new field\n     .add(\"d\")(_.b.trim)\n     // rename field\n     .rename(_.e)(\"z\")\n     // update field (also you can rename it using withRename after choosing field)\n     .update(_.a)(_ + 1)\n     // update field from root (same as update, but function is from root element)\n     .update(_.d).fromRoot(foo =\u003e if (foo.d) foo.a else foo.a / 2)\n     // possibility to semiauto derive any\n     .update(_.c) {\n        case s: String =\u003e s\n        case i: Int if i % 2 == 0 =\u003e i / 2\n        case i: Int =\u003e i + 1\n        case other =\u003e other.toString\n     }\n```\n6. **ReaderBuilder** features\n```scala\n\ninline given ReaderBuilder[Foo] =\n  ReaderBuilder[Foo]\n    // extract field from a value of a specific type\n    .extract(_.e).as[Option[Double]](_.getOrElse(1.0))\n  \n    // extract field as combination of model fields and some other fields from json\n    .extract(_.a).from(_.b).and[Int](\"otherField2\")((b, other) =\u003e d.toInt + other)\n  \n    // provide reader for Any field\n    .extractReader(_.c).from(_.a) {\n       case 1 =\u003e JsonReader[String]\n       case 2 =\u003e JsonReader[Int]\n       case _ =\u003e JsonReader[Option[Boolean]]\n    }\n\n    // ensure that json contains only fields that JsonReader knows about, otherwise throw ReaderError\n    .strict\n```\n\n### Configuration via **JsonConfiguration**\n1. To configure both **JsonWriter** and **JsonReader** you can use **JsonConfiguration**\n2. **JsonConfiguration** can be provided as an inline given to derives\n```scala\ninline given JsonConfiguration = JsonConfiguration.default\n```\n3. **JsonConfiguration** will be applied recursively to all nested readers/writers\n  * Product types\n  ```scala\n  import tethys.*\n  import tethys.jackson.*\n\n  inline given JsonConfiguration =\n    JsonConfiguration.default.fieldStyle(FieldStyle.LowerSnakeCase)\n\n  case class Inner(innerField: String)\n  case class Outer(outerField: Inner) derives JsonWriter, JsonReader\n\n  val outer = Outer(Inner(\"fooBar\"))\n  val json = \"\"\"{\"outer_field\": {\"inner_field\": \"fooBar\"}}\"\"\"\n\n  json.jsonAs[Outer] == Right(outer)\n  outer.asJson == json\n  ```\n  * Sum types\n  ```scala\n  import tethys.*\n  import tethys.jackson.*\n\n  inline given JsonConfiguration =\n    JsonConfiguration.default.fieldStyle(FieldStyle.LowerSnakeCase)\n\n  enum Choice(@selector val select: Int) derives JsonReader, JsonWriter:\n    case First(firstField: Int) extends Choice(0)\n    case Second(secondField: String) extends Choice(1)\n\n  val first = Choice.First(1)\n  val second = Choice.Second(\"foo\")\n  val firstJson = \"\"\"{\"select\": 0, \"first_field\": 1}\"\"\"\n  val secondJson = \"\"\"{\"select\": 1, \"second_field\": \"foo\"}\"\"\"\n\n  first.asJson == firstJson\n  second.asJson == secondJson\n\n  firstJson.jsonAs[Choice] == first\n  secondJson.jsonAs[Choice] == second\n  ```\n4. **WriterBuilder** and **ReaderBuilder** settings have higher priority than **JsonConfiguration** settings\n```scala\nimport tethys.*\nimport tethys.jackson.*\n\ncase class Customer(\n    id: Long,\n    phoneNumber: String\n) derives JsonWriter, JsonReader\n\ninline given JsonConfiguration =\n  JsonConfiguration.default.fieldStyle(FieldStyle.LowerSnakeCase)\n\ninline given WriterBuilder[Customer] =\n  // has higher priority than JsonConfiguration's fieldStyle\n  WriterBuilder[Customer].fieldStyle(FieldStyle.UpperCase)\n\ninline given ReaderBuilder[Customer] =\n  // has higher priority than JsonConfiguration's fieldStyle\n  ReaderBuilder[Customer].fieldStyle(FieldStyle.UpperCase)\n\nval customer = Customer(id = 5L, phoneNumber = \"+123\")\nval json = \"\"\"{\"ID\": 5, \"PHONENUMBER\": \"+123\"}\"\"\"\n\njson.jsonAs[Customer] == Right(customer)\ncustomer.asJson == json\n```\n5. **JsonConfiguration** features\n```scala\ninline given JsonConfiguration =\n  JsonConfiguration\n    // default config, entrypoint for configuration\n    .default\n\n    // choose field style\n    .fieldStyle(FieldStyle.UpperSnakeCase)\n\n    // ensure that json contains only fields that JsonReader knows about, otherwise throw ReaderError\n    // applicable only for JsonReader\n    .strict\n```\n\n## integrations\nIn some cases, you may need to work with raw AST,\nso tethys can offer you **circe** and **json4s** AST support\n\n#### Circe\n[see project page](https://github.com/circe/circe)\n```scala\nlibraryDependencies += \"com.tethys-json\" %% \"tethys-circe\" % tethysVersion\n```\n\n```scala\nimport tethys.*\nimport tethys.jackson.*\nimport tethys.circe.*\n\nimport io.circe.Json\n\ncase class Foo(bar: Int, baz: Json) derives JsonReader\n\nval json = \"\"\"{\"bar\": 1, \"baz\": [\"some\", {\"arbitrary\": \"json\"}]}\"\"\"\nval foo = json.jsonAs[Foo].fold(throw _, identity)\n\nfoo.bar // 1: Int\nfoo.baz // [ \"some\", { \"arbitrary\" : \"json\" } ]: io.circe.Json\n```\n\n#### Json4s\n[see project page](https://github.com/json4s/json4s)\n```scala\nlibraryDependencies += \"com.tethys-json\" %% \"tethys-json4s\" % tethysVersion\n```\n\n```scala\nimport tethys.*\nimport tethys.jackson.*\nimport tethys.json4s.*\n\nimport org.json4s.JsonAST.*\n\ncase class Foo(bar: Int, baz: JValue) derives JsonReader\n\nval json = \"\"\"{\"bar\": 1, \"baz\": [\"some\", {\"arbitrary\": \"json\"}]\"\"\"\nval foo = json.jsonAs[Foo].fold(throw _, identity)\n\nfoo.bar // 1\nfoo.baz // JArray(List(JString(\"some\"), JObject(\"arbitrary\" -\u003e JString(\"json\"))))\n```\n#### Enumeratum\n[see project page](https://github.com/lloydmeta/enumeratum)\n```scala\nlibraryDependencies += \"com.tethys-json\" %% \"tethys-enumeratum\" % tethysVersion\n```\n\nenumeratum module provides a bunch of mixins for your Enum classes.\n```scala\nimport enumeratum.{Enum, EnumEntry}\nimport tethys.enumeratum.*\n\nsealed trait Direction extends EnumEntry\ncase object Direction extends Enum[Direction] \n  with TethysEnum[Direction] // provides JsonReader and JsonWriter instances \n  with TethysKeyEnum[Direction] { // provides KeyReader and KeyWriter instances\n  \n  \n  case object Up extends    Direction\n  case object Down extends  Direction\n  case object Left extends  Direction\n  case object Right extends Direction\n\n  val values = findValues\n}\n```\n\n### Migration notes\n\nWhen migrating to **scala 3** you should use **0.29.0** version.\n\nScala 3 derivation API in **0.29.0** has a lot of deprecations and is not fully compatible in compile time with **0.28.4**, including:\n\n1. **WriterDescription** and **ReaderDescription** are deprecated along with **describe** macro.\n   Use **WriterBuilder** and **ReaderBuilder** directly instead\n\n\n2. **DependentField** model for **ReaderBuilder** has changed.\n   Now `extract field from` feature works like this:\n    * exactly one **from** call\n    * chain of **and** calls (until compiler lets you)\n    * both methods **from/and** has two forms\n        * select some field from your model\n        * provide type to method and name of field as string parameter\n\n```scala\n   ReaderBuilder[SimpleType]\n     .extract(_.i).from(_.d).and[Double](\"e\")((d, e) =\u003e (d + e).toInt)\n```\n\n3. **0.28.4 scala 3 enum support** was changed. [See more](https://github.com/tethys-json/tethys?tab=readme-ov-file#basic-enums)\n\n\n4. `updatePartial` for **WriterBuilder** is deprecated. Use ```update``` instead\n\n5. all derivation api were moved directly into core module in **tethys** package, including\n    * FieldStyle\n    * WriterBuilder\n    * ReaderBuilder\n\n6. **auto** derivation is deprecated. Use derives on toplevel type instead\n\n# Scala 2\n\n## Quick start\nAdd dependencies to your `build.sbt`\n\n```scala\nval tethysVersion = \"latest version in badge\"\nlibraryDependencies ++= Seq(\n  \"com.tethys-json\" %% \"tethys-core\" % tethysVersion,\n  \"com.tethys-json\" %% \"tethys-jackson213\" % tethysVersion,\n  \"com.tethys-json\" %% \"tethys-derivation\" % tethysVersion\n)\n```\n\n```scala\nlibraryDependencies ++= Seq(\n  \"com.tethys-json\" %% \"tethys\" % \"latest version in badge\"\n)\n```\n\n# core\n\ncore module contains all type classes for parsing/writing JSON.\nJSON string parsing/writing and derivation are separated to `tethys-jackson` and `tethys-derivation`\n\n## JsonWriter\n\nJsonWriter writes json tokens to `TokenWriter`\n\n```scala\nimport tethys._\nimport tethys.jackson._\n\nList(1, 2, 3, 4).asJson\n\n//or write directly to TokenWriter\n\nval tokenWriter = YourWriter\n\ntokenWriter.writeJson(List(1, 2, 3, 4))\n```\n\nNew writers can be created with an object builder or with a combination of a few writers\n\n```scala\nimport tethys._\nimport tethys.jackson._\nimport scala.reflect.ClassTag\n\ncase class Foo(bar: Int)\n\ndef classWriter[A](implicit ct: ClassTag[A]): JsonObjectWriter[A] = {\n    JsonWriter.obj[A].addField(\"clazz\")(_ =\u003e ct.toString())\n}\n\nimplicit val fooWriter: JsonObjectWriter[Foo] = {\n  classWriter[Foo] ++ JsonWriter.obj[Foo].addField(\"bar\")(_.bar)\n}\n\nFoo(1).asJson\n```\n\nor just using another JsonWriter\n\n```scala\nimport tethys._\n\ncase class Foo(bar: Int)\n\nJsonWriter.stringWriter.contramap[Foo](_.bar.toString)\n```\n\n## JsonReader\n\nJsonReader converts a json token from `TokenIterator` to its value\n```scala\nimport tethys._\nimport tethys.jackson._\n\n\"[1, 2, 3, 4]\".jsonAs[List[Int]]\n```\n\nNew readers can be created with a builder\n\n```scala\nimport tethys._\nimport tethys.jackson._\n\ncase class Foo(bar: Int)\n\nimplicit val fooReader: JsonReader[Foo] = JsonReader.builder\n    .addField[Int](\"bar\")\n    .buildReader(Foo.apply)\n    \n\"\"\"{\"bar\":1}\"\"\".jsonAs[Foo]\n```\n\nAlso you can select an existing reader that depends on other json fields\n\n```scala\nimport tethys._\nimport tethys.jackson._\n\ntrait FooBar\ncase class Foo(foo: Int) extends FooBar\ncase class Bar(bar: String)  extends FooBar\n\nval fooReader: JsonReader[Foo] = JsonReader.builder\n    .addField[Int](\"foo\")\n    .buildReader(Foo.apply)\n    \nval barReader: JsonReader[Bar] = JsonReader.builder\n    .addField[String](\"bar\")\n    .buildReader(Bar.apply)\n    \nimplicit val fooBarReader: JsonReader[FooBar] = JsonReader.builder\n    .addField[String](\"clazz\")\n    .selectReader[FooBar] {\n      case \"Foo\" =\u003e fooReader\n      case _ =\u003e barReader \n    }    \n    \n\"\"\"{\"clazz\":\"Foo\",\"foo\":1}\"\"\".jsonAs[FooBar]\n```\n\nPlease check out `tethys` package object for all available syntax Ops classes\n\n# derivation\n\n`tethys-derivation` provides semiauto and auto macro derivation JsonReader and JsonWriter instances.  \nIn most cases you should prefer semiauto derivation because it's more precise, faster in compilation and flexible.\n\n```scala\nimport tethys._\nimport tethys.jackson._\nimport tethys.derivation.auto._\nimport tethys.derivation.semiauto._\n\ncase class Foo(bar: Bar)\ncase class Bar(seq: Seq[Int])\n\nimplicit val barWriter: JsonObjectWriter[Bar] = jsonWriter[Bar] //semiauto\nimplicit val barReader: JsonReader[Bar] = jsonReader[Bar]\n\n\"\"\"{\"bar\":{\"seq\":[1,2,3]}}\"\"\".jsonAs[Foo] //Foo reader auto derived\n``` \n\nIn complex cases you can provide some additional information to `jsonWriter` and `jsonReader` functions\n\n```scala\nimport tethys._\nimport tethys.derivation.builder._\nimport tethys.derivation.semiauto._\n\ncase class Foo(a: Int, b: String, c: Any, d: Boolean, e: Double)\n\nimplicit val fooWriter = jsonWriter[Foo] {\n  describe {\n    //Any functions are allowed in lambdas\n    WriterBuilder[Foo]\n      .remove(_.b)\n      .add(\"d\")(_.b.trim)\n      .update(_.a)(_ + 1)\n      // the only way to semiauto derive Any\n      // this partial function will be replaced with match in the final writer\n      .updatePartial(_.c) {  \n        case s: String =\u003e s\n        case i: Int if i % 2 == 0 =\u003e i / 2\n        case i: Int =\u003e i + 1\n        case other =\u003e other.toString \n      }\n      .update(_.d).fromRoot(foo =\u003e if(foo.d) foo.a else foo.a / 2) //same as update but function accepts root element\n      .updatePartial(_.e).fromRoot { //same as updatePartial but function accepts root element\n        case Foo(1, _, _, _, e) =\u003e e\n        case Foo(2, _, _, _, e) =\u003e e % 2\n        case foo =\u003e e.toString\n      }\n  }\n}\n\nimplicit val fooReader = jsonReader[Foo] {\n    //Any functions are allowed in lambdas\n    ReaderBuilder[Foo]\n      .extractReader(_.c).from(_.a)('otherField.as[String]) { // provide reader for Any field\n        case (1, \"str\") =\u003e JsonReader[String]\n        case (_, \"int\") =\u003e JsonReader[Int]\n        case _ =\u003e JsonReader[Option[Boolean]]\n      }\n      .extract(_.a).from(_.b).and(\"otherField2\".as[Int])((b, other) =\u003e d.toInt + other) // calculate a field that depends on other fields\n      .extract(_.e).as[Option[Double]](_.getOrElse(1.0)) // extract a field from a value of a specific type\n}\n```\n\n\n# jackson\n\n`tethys-jackson` module provides bridge instances for jackson streaming api\n\n```scala\nimport tethys.jackson._\n//import tethys.jackson.pretty._ //pretty writing\n\n//that's it. welcome to use jackson\n```\n\n# complex case\n```scala\nimport tethys._\nimport tethys.jackson._\nimport tethys.derivation.auto._\n\ncase class Foo(bar: Bar)\ncase class Bar(seq: Seq[Int])\n\nval foo = \"\"\"{\"bar\":{\"seq\":[1,2,3]}}\"\"\".jsonAs[Foo].fold(throw _, identity)\nval json = foo.asJson\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftethys-json%2Ftethys","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftethys-json%2Ftethys","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftethys-json%2Ftethys/lists"}