Skip to content

Commit

Permalink
config ability added
Browse files Browse the repository at this point in the history
  • Loading branch information
Greg Zoller committed Oct 17, 2023
1 parent c740e7f commit 4d7bb3c
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 79 deletions.
18 changes: 9 additions & 9 deletions src/main/scala/co.blocke.scalajack/Codec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import json.*

object Codec:

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

def writeImpl[T:Type](t: Expr[T])(using q: Quotes): Expr[String] =
import quotes.reflect.*
def writeImpl[T: Type](t: Expr[T], cfg: Expr[JsonConfig])(using q: Quotes): Expr[String] =
import quotes.reflect.*

val rtRef = ReflectOnType[T](q)(TypeRepr.of[T])(using scala.collection.mutable.Map.empty[TypedName, Boolean])
val fn = JsonWriter.writeJsonFn[T](rtRef)
'{
val sb = new StringBuilder()
$fn($t, sb).toString
}
val rtRef = ReflectOnType[T](q)(TypeRepr.of[T])(using scala.collection.mutable.Map.empty[TypedName, Boolean])
val fn = JsonWriter.writeJsonFn[T](rtRef)
'{
val sb = new StringBuilder()
$fn($t, sb, $cfg).toString
}
6 changes: 6 additions & 0 deletions src/main/scala/co.blocke.scalajack/json/JsonConfig.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package co.blocke.scalajack
package json

case class JsonConfig(
enumsAsIds: Char | List[String] = Nil // Char is '*' for all enums as ids
)
4 changes: 4 additions & 0 deletions src/main/scala/co.blocke.scalajack/json/JsonError.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package co.blocke.scalajack
package json

class JsonError(msg: String) extends Throwable
143 changes: 80 additions & 63 deletions src/main/scala/co.blocke.scalajack/json/JsonWriter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,74 +3,91 @@ package json

import co.blocke.scala_reflection.RTypeRef
import co.blocke.scala_reflection.reflect.rtypeRefs.*
import scala.quoted.*
import co.blocke.scala_reflection.rtypes.*
import scala.quoted.*

object JsonWriter:

def writeJsonFn[T](rtRef: RTypeRef[T])(using Type[T])(using q: Quotes): Expr[(T,StringBuilder) => StringBuilder] =
import quotes.reflect.*
rtRef match
case rt: PrimitiveRef[?] if rt.isStringish =>
'{(a:T, sb:StringBuilder) =>
sb.append('"')
sb.append(a.toString)
sb.append('"')
}
case rt: PrimitiveRef[?] =>
'{(a:T, sb:StringBuilder) => sb.append(a.toString) }
def writeJsonFn[T](rtRef: RTypeRef[T])(using tt: Type[T], q: Quotes): Expr[(T, StringBuilder, JsonConfig) => StringBuilder] =
import quotes.reflect.*
rtRef match
case rt: PrimitiveRef[?] if rt.isStringish =>
'{ (a: T, sb: StringBuilder, cfg: JsonConfig) =>
sb.append('"')
sb.append(a.toString)
sb.append('"')
}
case rt: PrimitiveRef[?] =>
'{ (a: T, sb: StringBuilder, cfg: JsonConfig) => sb.append(a.toString) }

case rt: AliasRef[?] =>
writeJsonFn[rt.T](rt.unwrappedType.asInstanceOf[RTypeRef[rt.T]])(using Type.of[rt.T])
case rt: AliasRef[?] =>
writeJsonFn[rt.T](rt.unwrappedType.asInstanceOf[RTypeRef[rt.T]])(using Type.of[rt.T])

case rt: ArrayRef[?] =>
rt.elementRef.refType match
case '[t] =>
val elementFn = writeJsonFn[t](rt.elementRef.asInstanceOf[RTypeRef[t]])
'{(a:T, sb:StringBuilder) =>
sb.append('[')
a.asInstanceOf[Array[?]].map{ e =>
$elementFn(e.asInstanceOf[t],sb)
sb.append(',')
}
sb.setCharAt(sb.length()-1,']')
}
case rt: ArrayRef[?] =>
rt.elementRef.refType match
case '[t] =>
val elementFn = writeJsonFn[t](rt.elementRef.asInstanceOf[RTypeRef[t]])
'{ (a: T, sb: StringBuilder, cfg: JsonConfig) =>
sb.append('[')
a.asInstanceOf[Array[?]].map { e =>
$elementFn(e.asInstanceOf[t], sb, cfg: JsonConfig)
sb.append(',')
}
sb.setCharAt(sb.length() - 1, ']')
}

case rt: ClassRef[?] =>
val fieldFns = rt.fields.map{ f =>
f.fieldRef.refType match
case '[t] => (writeJsonFn[t](f.fieldRef.asInstanceOf[RTypeRef[t]]), f)
}
'{(a:T, sb:StringBuilder) =>
sb.append('{')
${
val stmts = fieldFns.map{ case (fn, field) =>
field.fieldRef.refType match
case '[t] =>
'{
sb.append(${Expr(field.name)})
sb.append(':')
val fieldValue = ${
Select.unique('{ a }.asTerm, field.name).asExpr
}
val fn2 = $fn.asInstanceOf[(t, StringBuilder) => StringBuilder]
fn2(fieldValue.asInstanceOf[t], sb)
sb.append(',')
}
}
Expr.ofList(stmts)
case rt: ClassRef[?] =>
val fieldFns = rt.fields.map { f =>
f.fieldRef.refType match
case '[t] => (writeJsonFn[t](f.fieldRef.asInstanceOf[RTypeRef[t]]), f)
}
'{ (a: T, sb: StringBuilder, cfg: JsonConfig) =>
sb.append('{')
${
val stmts = fieldFns.map { case (fn, field) =>
field.fieldRef.refType match
case '[t] =>
'{
sb.append(${ Expr(field.name) })
sb.append(':')
val fieldValue = ${
Select.unique('{ a }.asTerm, field.name).asExpr
}
sb.setCharAt(sb.length()-1,'}')
}
val fn2 = $fn.asInstanceOf[(t, StringBuilder, JsonConfig) => StringBuilder]
fn2(fieldValue.asInstanceOf[t], sb, cfg: JsonConfig)
sb.append(',')
}
}
Expr.ofList(stmts)
}
sb.setCharAt(sb.length() - 1, '}')
}

case rt: SeqRef[?] =>
rt.elementRef.refType match
case '[t] =>
val elementFn = writeJsonFn[t](rt.elementRef.asInstanceOf[RTypeRef[t]])
'{(a:T, sb:StringBuilder) =>
sb.append('[')
a.asInstanceOf[Seq[?]].map{ e =>
$elementFn(e.asInstanceOf[t],sb)
sb.append(',')
}
sb.setCharAt(sb.length()-1,']')
}
case rt: SeqRef[?] =>
rt.elementRef.refType match
case '[t] =>
val elementFn = writeJsonFn[t](rt.elementRef.asInstanceOf[RTypeRef[t]])
'{ (a: T, sb: StringBuilder, cfg: JsonConfig) =>
sb.append('[')
a.asInstanceOf[Seq[?]].map { e =>
$elementFn(e.asInstanceOf[t], sb, cfg: JsonConfig)
sb.append(',')
}
sb.setCharAt(sb.length() - 1, ']')
}

case rt: EnumRef[?] =>
val expr = rt.expr
'{
val rtype = $expr.asInstanceOf[EnumRType[?]]
(a: T, sb: StringBuilder, cfg: JsonConfig) =>
val enumAsId = cfg.enumsAsIds match
case '*' => true
case aList: List[String] if aList.contains(rtype.name) => true
case _ => false
if enumAsId then sb.append(rtype.ordinal(a.toString).getOrElse(throw new JsonError(s"Value $a is not a valid enum value for ${rtype.name}")))
else
sb.append('"')
sb.append(a.toString)
sb.append('"')
}
12 changes: 6 additions & 6 deletions src/main/scala/co.blocke.scalajack/run/Play.scala
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package co.blocke.scalajack
package run

import co.blocke.scala_reflection.*
import co.blocke.scala_reflection.*

object RunMe extends App:

val p = Person("Greg", 57, List(false,true,true))
val p = Person("Greg", 57, List(false, true, true), Colors.Blue)

val i: Array[Int] = Array(1,2,3)
val i: Array[Int] = Array(1, 2, 3)

println(Codec.write(p))
println(Codec.write(p)(using json.JsonConfig(List("co.blocke.scalajack.run.ColorsX"))))

// println(RType.of[Person].pretty)
// println(RType.of[Person].pretty)
/*
Expand All @@ -31,4 +31,4 @@ def writeImpl[T:Type](t: Expr[T])(using quotes: Quotes): Expr[String] =
import quotes.reflect.*
*/
*/
5 changes: 4 additions & 1 deletion src/main/scala/co.blocke.scalajack/run/Sample.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
package co.blocke.scalajack.run

case class Person(name: String, age: Int, isOk: List[Boolean])
case class Person(name: String, age: Int, isOk: List[Boolean], favColor: Colors)

enum Colors:
case Red, Blue, Green

0 comments on commit 4d7bb3c

Please sign in to comment.