Skip to content

Commit

Permalink
writing json is working
Browse files Browse the repository at this point in the history
  • Loading branch information
Greg Zoller committed Oct 20, 2023
1 parent eed5e6d commit d11003e
Show file tree
Hide file tree
Showing 6 changed files with 40 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import scala.quoted.*
import quoted.Quotes
import json.*

object Codec:
object ScalaJack:

inline def write[T](t: T)(using cfg: JsonConfig = JsonConfig()): String = ${ writeImpl[T]('t, 'cfg) }

Expand Down
9 changes: 7 additions & 2 deletions src/main/scala/co.blocke.scalajack/json/JsonConfig.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ package json
case class JsonConfig(
noneAsNull: Boolean = false,
tryFailureHandling: TryOption = TryOption.NO_WRITE,
typeHint: String = "_hint",
enumsAsIds: Char | List[String] = Nil // Char is '*' for all enums as ids
// --------------------------
typeHintLabel: String = "_hint",
typeHintLabelByTrait: Map[String, String] = Map.empty[String, String], // Trait name -> type hint label
typeHintDefaultTransformer: String => String = (v: String) => v, // in case you want something different than class name (simple name re-mapping)
typeHintTransformer: Map[String, Any => String] = Map.empty[String, Any => String], // if you want class-specific control (instance value => String)
// --------------------------
enumsAsIds: Char | List[String] = Nil // Char is '*' for all enums as ids, or a list of fully-qualified class names
)

enum TryOption:
Expand Down
15 changes: 7 additions & 8 deletions src/main/scala/co.blocke.scalajack/json/JsonWriter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,6 @@ object JsonWriter:
def writeJsonFn[T](rtRef: RTypeRef[T])(using tt: Type[T], q: Quotes): Expr[(T, StringBuilder, JsonConfig) => StringBuilder] =
import quotes.reflect.*

// def liftFunction[T: Type, U: Type](f: T => U): Expr[T => U] = {
// val fnExpr: Expr[T => U] = '{ (x: T) => ${ f('x) } }
// fnExpr
// }

rtRef match
case rt: PrimitiveRef[?] if rt.isStringish =>
'{ (a: T, sb: StringBuilder, cfg: JsonConfig) =>
Expand Down Expand Up @@ -75,14 +70,18 @@ object JsonWriter:
val sbLen = sb.length
${
val hintStmt = (rt match
case s: ScalaClassRef[?] if s.renderHint =>
case s: ScalaClassRef[?] if s.renderTrait.isDefined =>
val traitName = Expr(s.renderTrait.get)
'{
sb.append('"')
sb.append(cfg.typeHint)
sb.append(cfg.typeHintLabelByTrait.getOrElse($traitName, cfg.typeHintLabel))
sb.append('"')
sb.append(':')
sb.append('"')
sb.append(a.getClass.getName)
val hint = cfg.typeHintTransformer.get(a.getClass.getName) match
case Some(xform) => xform(a)
case None => cfg.typeHintDefaultTransformer(a.getClass.getName)
sb.append(hint)
sb.append('"')
sb.append(',')
()
Expand Down
5 changes: 2 additions & 3 deletions src/main/scala/co.blocke.scalajack/json/ReflectUtil.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import scala.quoted.staging.*

object ReflectUtil:

/**
* This function takes the RType of a trait and an instance of T and does two things.
/** This function takes the RType of a trait and an instance of T and does two things.
* First it expresses the instance's class *in terms of* the trait's concrete type parameters (if any).
* Then it generates a writer function for the now correctly-typed class.
*
Expand Down Expand Up @@ -46,7 +45,7 @@ object ReflectUtil:

inTermsOfRef.refType match
case '[t] =>
val asClassRef = inTermsOfRef.asInstanceOf[ScalaClassRef[t]].copy(renderHint = true)
val asClassRef = inTermsOfRef.asInstanceOf[ScalaClassRef[t]].copy(renderTrait = Some(traitType.name))
JsonWriter.writeJsonFn[t](asClassRef.asInstanceOf[RTypeRef[t]])
}
val writeFn = run(fn)
Expand Down
21 changes: 17 additions & 4 deletions src/main/scala/co.blocke.scalajack/run/Play.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,25 @@ object RunMe extends App:
val d = Dog("Fido", 4, 2, Some(Dog("Mindy", 4, 0, None)))
val d2 = Dog("Spot", 4, 3, Some(Dog("Floppy", 3, 1, None)))

val cfg = json.JsonConfig()
println(Codec.write(d)(using cfg))
val mapper = (a: Any) =>
a.getClass.getPackage.getName match
case p if p.startsWith("co.blocke") => "Blocke"
case x => "Something " + x.getClass.getName

given json.JsonConfig = json
.JsonConfig()
.copy(
typeHintDefaultTransformer = (s: String) => s.split("\\.").last,
typeHintLabelByTrait = Map("co.blocke.scalajack.run.Animal" -> "kind"),
typeHintTransformer = Map("co.blocke.scalajack.run.Dog" -> mapper)
)

println(ScalaJack.write(d))

val t0 = System.currentTimeMillis()
for i <- 0 to 10000 do
Codec.write(d)(using cfg)
if i % 100 == 0 then println(i)
ScalaJack.write(d)
// if i % 100 == 0 then println(i)
// if i == 10000 then println(i)

// println(Codec.write(d)(using cfg))
Expand Down
7 changes: 6 additions & 1 deletion src/main/scala/co.blocke.scalajack/run/Sample.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ trait Animal:
val numLegs: Int
val friend: Option[Animal]

case class Dog(name: String, numLegs: Int, carsChased: Int, friend: Option[Animal]) extends Animal
trait Animal2:
val name: String
val numLegs: Int
val friend: Option[Animal2]

case class Dog(name: String, numLegs: Int, carsChased: Int, friend: Option[Animal2]) extends Animal2

enum Colors:
case Red, Blue, Green

0 comments on commit d11003e

Please sign in to comment.