• Stars
    star
    110
  • Rank 316,770 (Top 7 %)
  • Language
    Scala
  • License
    Apache License 2.0
  • Created about 7 years ago
  • Updated 3 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

AST free JSON library for Scala
CI Release
Build Status Maven Central

tethys

tethys is a JSON parsing/writing library for Scala

benchmarks

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:

Circe

see project page

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

Json4s

see project page

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

Enumeratum

see project page

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

core

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

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")(_.bar)
}

Foo(1).asJson

or just using another JsonWriter

import tethys._

case class Foo(bar: Int)

JsonWriter.stringWriter.contramap[Foo](_.bar.toString)

JsonReader

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
    .addField[Int]("bar")
    .buildReader(Foo.apply)
    
"""{"bar":1}""".jsonAs[Foo]

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
    .addField[Int]("foo")
    .buildReader(Foo.apply)
    
val barReader: JsonReader[Bar] = JsonReader.builder
    .addField[String]("bar")
    .buildReader(Bar.apply)
    
implicit val fooBarReader: JsonReader[FooBar] = JsonReader.builder
    .addField[String]("clazz")
    .selectReader[FooBar] {
      case "Foo" => fooReader
      case _ => barReader 
    }    
    
"""{"clazz":"Foo","foo":1}""".jsonAs[FooBar]

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

derivation

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.auto._
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
    WriterBuilder[Foo]
      .remove(_.b)
      .add("d")(_.b.trim)
      .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
    ReaderBuilder[Foo]
      .extractReader(_.c).from(_.a)('otherField.as[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
}

jackson

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._
import tethys.derivation.auto._

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.derivation.auto._
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)

foo.bar // 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)

foo.bar // 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
}