From 4d7bb3ccf184ec60a66a305a0cbba7a4da3e9f90 Mon Sep 17 00:00:00 2001 From: Greg Zoller Date: Tue, 17 Oct 2023 13:25:37 -0500 Subject: [PATCH] config ability added --- .../scala/co.blocke.scalajack/Codec.scala | 18 +-- .../co.blocke.scalajack/json/JsonConfig.scala | 6 + .../co.blocke.scalajack/json/JsonError.scala | 4 + .../co.blocke.scalajack/json/JsonWriter.scala | 143 ++++++++++-------- .../scala/co.blocke.scalajack/run/Play.scala | 12 +- .../co.blocke.scalajack/run/Sample.scala | 5 +- 6 files changed, 109 insertions(+), 79 deletions(-) create mode 100644 src/main/scala/co.blocke.scalajack/json/JsonConfig.scala create mode 100644 src/main/scala/co.blocke.scalajack/json/JsonError.scala diff --git a/src/main/scala/co.blocke.scalajack/Codec.scala b/src/main/scala/co.blocke.scalajack/Codec.scala index 5e789029..1044f551 100644 --- a/src/main/scala/co.blocke.scalajack/Codec.scala +++ b/src/main/scala/co.blocke.scalajack/Codec.scala @@ -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 + } diff --git a/src/main/scala/co.blocke.scalajack/json/JsonConfig.scala b/src/main/scala/co.blocke.scalajack/json/JsonConfig.scala new file mode 100644 index 00000000..37ca8ee5 --- /dev/null +++ b/src/main/scala/co.blocke.scalajack/json/JsonConfig.scala @@ -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 +) \ No newline at end of file diff --git a/src/main/scala/co.blocke.scalajack/json/JsonError.scala b/src/main/scala/co.blocke.scalajack/json/JsonError.scala new file mode 100644 index 00000000..a2ab9a13 --- /dev/null +++ b/src/main/scala/co.blocke.scalajack/json/JsonError.scala @@ -0,0 +1,4 @@ +package co.blocke.scalajack +package json + +class JsonError(msg: String) extends Throwable \ No newline at end of file diff --git a/src/main/scala/co.blocke.scalajack/json/JsonWriter.scala b/src/main/scala/co.blocke.scalajack/json/JsonWriter.scala index 4224c61d..f2ce08d5 100644 --- a/src/main/scala/co.blocke.scalajack/json/JsonWriter.scala +++ b/src/main/scala/co.blocke.scalajack/json/JsonWriter.scala @@ -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,']') - } \ No newline at end of file + 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('"') + } diff --git a/src/main/scala/co.blocke.scalajack/run/Play.scala b/src/main/scala/co.blocke.scalajack/run/Play.scala index 1c73d974..bf43558a 100644 --- a/src/main/scala/co.blocke.scalajack/run/Play.scala +++ b/src/main/scala/co.blocke.scalajack/run/Play.scala @@ -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) /* @@ -31,4 +31,4 @@ def writeImpl[T:Type](t: Expr[T])(using quotes: Quotes): Expr[String] = import quotes.reflect.* -*/ \ No newline at end of file + */ diff --git a/src/main/scala/co.blocke.scalajack/run/Sample.scala b/src/main/scala/co.blocke.scalajack/run/Sample.scala index 74860ac5..b9623f93 100644 --- a/src/main/scala/co.blocke.scalajack/run/Sample.scala +++ b/src/main/scala/co.blocke.scalajack/run/Sample.scala @@ -1,3 +1,6 @@ package co.blocke.scalajack.run -case class Person(name: String, age: Int, isOk: List[Boolean]) \ No newline at end of file +case class Person(name: String, age: Int, isOk: List[Boolean], favColor: Colors) + +enum Colors: + case Red, Blue, Green