Created
August 13, 2019 20:56
-
-
Save scf37/9b7bcd20eb212b5c8c1faaca9a0fc147 to your computer and use it in GitHub Desktop.
Tethys Json via reflection
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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