Skip to content

Instantly share code, notes, and snippets.

@scf37
Created August 13, 2019 20:56
Show Gist options
  • Save scf37/9b7bcd20eb212b5c8c1faaca9a0fc147 to your computer and use it in GitHub Desktop.
Save scf37/9b7bcd20eb212b5c8c1faaca9a0fc147 to your computer and use it in GitHub Desktop.
Tethys Json via reflection
package me.scf37.json
import java.lang.reflect.Field
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.util.concurrent.ConcurrentHashMap
import me.scf37.fine.exception.JsonParsingException
import tethys.JsonReader
import tethys.JsonWriter
import tethys._
import tethys.jackson._
import tethys.readers.FieldName
import tethys.readers.ReaderError
import tethys.readers.tokens.TokenIterator
import tethys.writers.tokens.TokenWriter
/**
* Tethys Json via reflection.
* Implementation looks for appropriate JsonReader/JsonWriter at companion object of provided type.
* In addition, writing of Map-s and Seq-s is supported
*/
trait TethysDyn {
def read(json: String, tpe: Class[_]): AnyRef
def write(value: Any): String
def reader(tpe: Class[_]): JsonReader[AnyRef]
def writer(tpe: Class[_]): JsonWriter[Any]
}
object TethysDyn extends TethysDyn {
private val readers = new ConcurrentHashMap[Class[_], JsonReader[_]]()
private val writers = new ConcurrentHashMap[Class[_], JsonWriter[_]]()
private val stdReaders = Map(
classOf[Int] -> implicitly[JsonReader[Int]],
classOf[Long] -> implicitly[JsonReader[Long]],
classOf[java.lang.Integer] -> implicitly[JsonReader[java.lang.Integer]],
classOf[java.lang.Long] -> implicitly[JsonReader[java.lang.Long]],
classOf[Boolean] -> implicitly[JsonReader[Boolean]],
classOf[java.lang.Boolean] -> implicitly[JsonReader[Boolean]].map(identity),
classOf[String] -> implicitly[JsonReader[String]]
)
private val stdWriters = Map(
classOf[Int] -> implicitly[JsonWriter[Int]],
classOf[Long] -> implicitly[JsonWriter[Long]],
classOf[java.lang.Integer] -> implicitly[JsonWriter[java.lang.Integer]],
classOf[java.lang.Long] -> implicitly[JsonWriter[java.lang.Long]],
classOf[Boolean] -> implicitly[JsonWriter[Boolean]],
classOf[java.lang.Boolean] -> implicitly[JsonWriter[java.lang.Boolean]],
classOf[String] -> implicitly[JsonWriter[String]],
)
stdReaders.foreach(kv => readers.put(kv._1, kv._2))
stdWriters.foreach(kv => writers.put(kv._1, kv._2))
override def reader(tpe: Class[_]): JsonReader[AnyRef] =
readers.computeIfAbsent(tpe, deriveReader).asInstanceOf[JsonReader[AnyRef]]
override def writer(tpe: Class[_]): JsonWriter[Any] =
writers.computeIfAbsent(tpe, deriveWriter).asInstanceOf[JsonWriter[Any]]
override def read(json: String, tpe: Class[_]): AnyRef = {
implicit val reader: JsonReader[AnyRef] = this.reader(tpe)
json.jsonAs[AnyRef] match {
case Left(ex) => throw JsonParsingException(ex)
case Right(v) => v
}
}
override def write(value: Any): String = {
implicit val writer: JsonWriter[Any] = this.writer(value.getClass)
value.asJson
}
private def deriveReader(cls: Class[_]): JsonReader[_] = {
if (classOf[Map[_, _]].isAssignableFrom(cls)) return mapReader
if (classOf[Seq[_]].isAssignableFrom(cls)) return arrayReader
try {
val o = companionObject(cls)
val instance = o.getField("MODULE$").get(null)
val f = findField(o, classOf[JsonReader[_]], cls)
f.setAccessible(true)
f.get(instance).asInstanceOf[JsonReader[_]]
} catch {
case e: Throwable => throw new RuntimeException(s"Failed to locate JsonReader for $cls", e)
}
}
private def deriveWriter(cls: Class[_]): JsonWriter[_] = {
if (classOf[Map[_, _]].isAssignableFrom(cls)) return mapWriter
if (classOf[Seq[_]].isAssignableFrom(cls)) return seqWriter
try {
val o = companionObject(cls)
val instance = o.getField("MODULE$").get(null)
val f = findField(o, classOf[JsonWriter[_]], cls)
f.setAccessible(true)
f.get(instance).asInstanceOf[JsonWriter[_]]
} catch {
case e: Throwable => throw new RuntimeException(s"Failed to locate JsonWriter for $cls", e)
}
}
private def findField(cls: Class[_], fieldType: Class[_], fieldTypeArgument: Class[_]): Field = {
def arg(tpe: Type): Option[Class[_]] = tpe match {
case t: ParameterizedType if t.getActualTypeArguments.length == 1 =>
t.getActualTypeArguments()(0) match {
case t: Class[_] => Some(t)
case _ => None
}
case _ => None
}
cls.getDeclaredFields.find { field =>
fieldType.isAssignableFrom(field.getType) &&
arg(field.getGenericType).exists(fieldTypeArgument.isAssignableFrom)
}.getOrElse(throw new RuntimeException(s"Failed to find $fieldType[$fieldTypeArgument] on $cls"))
}
private def companionObject(cls: Class[_]): Class[_] = {
cls.getClassLoader.loadClass(cls.getName + "$")
}
private val mapWriter = new JsonWriter[Map[String, Any]] {
override def write(map: Map[String, Any], tokenWriter: TokenWriter): Unit = {
tokenWriter.writeObjectStart()
map.foreach { case (key, value) =>
tokenWriter.writeFieldName(key)
if (value == null) {
tokenWriter.writeNull()
} else {
tokenWriter.writeRawJson(TethysDyn.write(value))
}
}
tokenWriter.writeObjectEnd()
}
}
private val seqWriter = new JsonWriter[Seq[Any]] {
override def write(seq: Seq[Any], tokenWriter: TokenWriter): Unit = {
tokenWriter.writeArrayStart()
seq.foreach { value =>
if (value == null) {
tokenWriter.writeNull()
} else {
tokenWriter.writeRawJson(TethysDyn.write(value))
}
}
tokenWriter.writeArrayEnd()
}
}
private val mapReader = new JsonReader[Map[String, Any]] {
override def read(it: TokenIterator)(implicit fieldName: FieldName): Map[String, Any] = {
if (!it.currentToken().isObjectStart) ReaderError.wrongJson(s"Expected object start but found: ${it.currentToken()}")
else {
it.next()
val builder = Map.newBuilder[String, Any]
while(!it.currentToken().isObjectEnd) {
val token = it.currentToken()
if (token.isFieldName) {
val name = it.fieldName()
val value: Any = valueReader.read(it.next())(fieldName.appendFieldName(name))
builder += name -> value
} else {
ReaderError.wrongJson(s"Expect end of object or field name but '$token' found")(fieldName)
}
}
it.next()
builder.result()
}
}
}
private val arrayReader: JsonReader[Seq[Any]] = new JsonReader[Seq[Any]] {
override def read(it: TokenIterator)(implicit fieldName: FieldName): scala.Seq[Any] = {
if (!it.currentToken().isArrayStart) ReaderError.wrongJson(s"Expected array start but found: ${it.currentToken()}")
else {
it.next()
val builder = Seq.newBuilder[Any]
var i = 0
while (!it.currentToken().isArrayEnd) {
builder += valueReader.read(it)(fieldName.appendArrayIndex(i))
i += 1
}
it.next()
builder.result()
}
}
}
private val valueReader: JsonReader[Any] = new JsonReader[Any] {
override def read(it: TokenIterator)(implicit fieldName: FieldName): Any = {
val token = it.currentToken()
if (token.isObjectStart) mapReader.read(it)
else if (token.isArrayStart) arrayReader.read(it)
else if (token.isStringValue) JsonReader[String].read(it)
else if (token.isBooleanValue) JsonReader[Boolean].read(it)
else if (token.isNumberValue) JsonReader[Number].read(it)
else if (token.isNullValue) {
it.next()
null
}
else ReaderError.wrongJson(s"Unexpected token found: $token")(fieldName)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment