Skip to content


Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time
CI Release
Build Status Maven Central


tethys is a JSON parsing/writing library for Scala


see here

Quick start

Add dependencies to your build.sbt

val tethysVersion = "latest version in badge"
libraryDependencies ++= Seq(
  "com.tethys-json" %% "tethys-core" % tethysVersion,
  "com.tethys-json" %% "tethys-jackson" % tethysVersion,
  "com.tethys-json" %% "tethys-derivation" % tethysVersion

or just

libraryDependecies ++= Seq(
  "com.tethys-json" %% "tethys" % "latest version in badge"

Also, tethys has the following integrations:


see project page

libraryDependencies += "com.tethys-json" %% "tethys-circe" % tethysVersion


see project page

libraryDependencies += "com.tethys-json" %% "tethys-json4s" % tethysVersion


see project page

libraryDependencies += "com.tethys-json" %% "tethys-enumeratum" % tethysVersion


core module contains all type classes for parsing/writing JSON. JSON string parsing/writing and derivation are separated to tethys-jackson and tethys-derivation


JsonWriter writes json tokens to TokenWriter

import tethys._
import tethys.jackson._

List(1, 2, 3, 4).asJson

//or write directly to TokenWriter

val tokenWriter = YourWriter

tokenWriter.writeJson(List(1, 2, 3, 4))

New writers can be created with an object builder or with a combination of a few writers

import tethys._
import tethys.jackson._
import scala.reflect.ClassTag

case class Foo(bar: Int)

def classWriter[A](implicit ct: ClassTag[A]): JsonObjectWriter[A] = {
    JsonWriter.obj[A].addField("clazz")(_ => ct.toString())

implicit val fooWriter: JsonObjectWriter[Foo] = {
  classWriter[Foo] ++ JsonWriter.obj[Foo].addField("bar")(


or just using another JsonWriter

import tethys._

case class Foo(bar: Int)



JsonReader converts a json token from TokenIterator to its value

import tethys._
import tethys.jackson._

"[1, 2, 3, 4]".jsonAs[List[Int]]

New readers can be created with a builder

import tethys._
import tethys.jackson._

case class Foo(bar: Int)

implicit val fooReader: JsonReader[Foo] = JsonReader.builder

Also you can select an existing reader that depends on other json fields

import tethys._
import tethys.jackson._

trait FooBar
case class Foo(foo: Int) extends FooBar
case class Bar(bar: String)  extends FooBar

val fooReader: JsonReader[Foo] = JsonReader.builder
val barReader: JsonReader[Bar] = JsonReader.builder
implicit val fooBarReader: JsonReader[FooBar] = JsonReader.builder
    .selectReader[FooBar] {
      case "Foo" => fooReader
      case _ => barReader 

Please check out tethys package object for all available syntax Ops classes


tethys-derivation provides semiauto and auto macro derivation JsonReader and JsonWriter instances.
In most cases you should prefer semiauto derivation because it's more precise, faster in compilation and flexible.

import tethys._
import tethys.jackson._
import tethys.derivation.semiauto._

case class Foo(bar: Bar)
case class Bar(seq: Seq[Int])

implicit val barWriter: JsonObjectWriter[Bar] = jsonWriter[Bar] //semiauto
implicit val barReader: JsonReader[Bar] = jsonReader[Bar]

"""{"bar":{"seq":[1,2,3]}}""".jsonAs[Foo] //Foo reader auto derived

In complex cases you can provide some additional information to jsonWriter and jsonReader functions

import tethys._
import tethys.derivation.builder._
import tethys.derivation.semiauto._

case class Foo(a: Int, b: String, c: Any, d: Boolean, e: Double)

implicit val fooWriter = jsonWriter[Foo] {
  describe {
    //Any functions are allowed in lambdas
      .update(_.a)(_ + 1)
      // the only way to semiauto derive Any
      // this partial function will be replaced with match in the final writer
      .updatePartial(_.c) {  
        case s: String => s
        case i: Int if i % 2 == 0 => i / 2
        case i: Int => i + 1
        case other => other.toString 
      .update(_.d).fromRoot(foo => if(foo.d) foo.a else foo.a / 2) //same as update but function accepts root element
      .updatePartial(_.e).fromRoot { //same as updatePartial but function accepts root element
        case Foo(1, _, _, _, e) => e
        case Foo(2, _, _, _, e) => e % 2
        case foo => e.toString

implicit val fooReader = jsonReader[Foo] {
    //Any functions are allowed in lambdas
      .extractReader(_.c).from(_.a)('[String]) { // provide reader for Any field
        case (1, "str") => JsonReader[String]
        case (_, "int") => JsonReader[Int]
        case _ => JsonReader[Option[Boolean]]
      .extract(_.a).from(_.b).and("otherField2".as[Int])((b, other) => d.toInt + other) // calculate a field that depends on other fields
      .extract(_.e).as[Option[Double]](_.getOrElse(1.0)) // extract a field from a value of a specific type


tethys-jackson module provides bridge instances for jackson streaming api

import tethys.jackson._
//import tethys.jackson.pretty._ //pretty writing

//that's it. welcome to use jackson

complex case

import tethys._
import tethys.jackson._

case class Foo(bar: Bar)
case class Bar(seq: Seq[Int])

val foo = """{"bar":{"seq":[1,2,3]}}""".jsonAs[Foo].fold(throw _, identity)
val json = foo.asJson

AST support

In some cases, you may need to work with raw AST, so tethys can offer you circe and json4s AST support:

circe AST support

import tethys._
import tethys.jackson._
import tethys.circe._

import io.circe.Json

case class Foo(bar: Int, baz: Json)

val json = """{"bar": 1, "baz": ["some", {"arbitrary": "json"}]}"""
val foo = json.jsonAs[Foo].fold(throw _, identity) // 1: Int
foo.baz // [ "some", { "arbitrary" : "json" } ]: io.circe.Json

json4s AST support

import tethys._
import tethys.jackson._
import tethys.derivation.semiauto._
import tethys.json4s._

import org.json4s.JsonAST._

case class Foo(bar: Int, baz: JValue)

val json = """{"bar": 1, "baz": ["some", {"arbitrary": "json"}]"""
val foo = json.jsonAs[Foo].fold(throw _, identity) // 1
foo.baz // JArray(List(JString("some"), JObject("arbitrary" -> JString("json"))))

enumeratum support

enumeratum module provides bunch of mixins for your Enum classes.

import enumeratum.{Enum, EnumEntry}
import tethys.enumeratum._

sealed trait Direction extends EnumEntry
case object Direction extends Enum[Direction] 
  with TethysEnum[Direction] // provides JsonReader and JsonWriter instances 
  with TethysKeyEnum[Direction] { // provides KeyReader and KeyWriter instances
  case object Up extends    Direction
  case object Down extends  Direction
  case object Left extends  Direction
  case object Right extends Direction

  val values = findValues