diff --git a/build.sbt b/build.sbt index 7a3f6a18a..469dfed31 100644 --- a/build.sbt +++ b/build.sbt @@ -82,7 +82,12 @@ lazy val json = .settings( description := "JSON Library", Test / parallelExecution := false, - libraryDependencies ++= Seq(scalap(scalaVersion.value), paranamer, scala_xml) + libraryDependencies ++= Seq(paranamer, scala_xml) ++ { + CrossVersion.partialVersion(scalaVersion.value) match { + case Some((2, _)) => Seq(scalap(scalaVersion.value)) + case _ => Seq.empty + } + } ) lazy val documentationHelpers = diff --git a/core/json/src/main/scala-2.13/net/liftweb/json/JsonASTExtensions.scala b/core/json/src/main/scala-2.13/net/liftweb/json/JsonASTExtensions.scala new file mode 100644 index 000000000..bf3180785 --- /dev/null +++ b/core/json/src/main/scala-2.13/net/liftweb/json/JsonASTExtensions.scala @@ -0,0 +1,111 @@ +/* + * Copyright 2009-2010 WorldWide Conferencing, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.liftweb +package json + +/** Scala 2.13-specific extensions for JsonAST - supports path-dependent types */ +trait JsonASTExtensions { + self: JsonAST.JValue => + + /** + * Find immediate children of this `[[JValue]]` that match a specific `JValue` subclass. + * + * This methid will search a `[[JObject]]` or `[[JArray]]` for values of a specific type and + * return a `List` of those values if they any are found. + * + * So given some JSON like so: + * + * {{{ + * [ + * { + * "thinga":1, + * "thingb":"bacon" + * },{ + * "thingc":3, + * "thingd":"Wakka" + * },{ + * "thinge":{ + * "thingf":4 + * }, + * "thingg":true + * } + * ] + * }}} + * + * You would use this method like so: + * + * {{{ + * scala> json \ classOf[JInt] + * res0: List[net.liftweb.json.JInt#Values] = List(1, 3) + * }}} + * + * This method does require that whatever type you're searching for is subtype of `JValue`. + */ + def \[A <: JsonAST.JValue](clazz: Class[A]): List[A#Values] = + findDirect(children, typePredicate(clazz) _).asInstanceOf[List[A]] map { _.values } + + /** + * Find all descendants of this `JValue` that match a specific `JValue` subclass. + * + * Unlike its cousin `\`, this method will recurse down into all children looking for + * type matches searching a `[[JObject]]` or `[[JArray]]` for values of a specific type and + * return a `List` of those values if they are found. + * + * So given some JSON like so: + * + * {{{ + * [ + * { + * "thinga":1, + * "thingb":"bacon" + * },{ + * "thingc":3, + * "thingd":"Wakka" + * },{ + * "thinge":{ + * "thingf":4 + * }, + * "thingg":true + * } + * ] + * }}} + * + * You would use this method like so: + * + * {{{ + * scala> json \\ classOf[JInt] + * res0: List[net.liftweb.json.JInt#Values] = List(1, 3, 4) + * }}} + */ + def \\[A <: JsonAST.JValue](clazz: Class[A]): List[A#Values] = + (this filter typePredicate(clazz) _).asInstanceOf[List[A]] map { _.values } + + private def findDirect(xs: List[JsonAST.JValue], p: JsonAST.JValue => Boolean): List[JsonAST.JValue] = xs.flatMap { + case JsonAST.JObject(l) => + l.collect { + case JsonAST.JField(n, x) if p(x) => x + } + case JsonAST.JArray(l) => findDirect(l, p) + case x if p(x) => x :: Nil + case _ => Nil + } + + private def typePredicate[A <: JsonAST.JValue](clazz: Class[A])(json: JsonAST.JValue) = json match { + case x if x.getClass == clazz => true + case _ => false + } +} diff --git a/core/json/src/main/scala/net/liftweb/json/ScalaSig.scala b/core/json/src/main/scala-2.13/net/liftweb/json/ScalaSig.scala similarity index 100% rename from core/json/src/main/scala/net/liftweb/json/ScalaSig.scala rename to core/json/src/main/scala-2.13/net/liftweb/json/ScalaSig.scala diff --git a/core/json/src/main/scala-2.13/net/liftweb/json/TypeExtractor.scala b/core/json/src/main/scala-2.13/net/liftweb/json/TypeExtractor.scala new file mode 100644 index 000000000..e3f427dca --- /dev/null +++ b/core/json/src/main/scala-2.13/net/liftweb/json/TypeExtractor.scala @@ -0,0 +1,29 @@ +package net.liftweb.json + +import scala.reflect.Manifest + +/** Abstraction for runtime class and type args (Scala 2.13 variant). */ +trait TypeExtractor[A] { + def runtimeClass: Class[_] + def typeArguments: List[TypeExtractor[_]] +} + + +/** Scala 2.13 implementation backed by Manifest. */ +object TypeExtractor extends LowPriorityTypeExtractor { + implicit def fromManifest[A](implicit mf: Manifest[A]): TypeExtractor[A] = new TypeExtractor[A] { + def runtimeClass: Class[_] = mf.runtimeClass + def typeArguments: List[TypeExtractor[_]] = mf.typeArguments.map(arg => fromManifest(arg)) + } +} + +/** Low-priority fallback kept for compatibility; ClassTag has no type args. */ +trait LowPriorityTypeExtractor { + import scala.reflect.ClassTag + implicit def fromClassTag[A](implicit ct: ClassTag[A]): TypeExtractor[A] = new TypeExtractor[A] { + def runtimeClass: Class[_] = ct.runtimeClass + def typeArguments: List[TypeExtractor[_]] = Nil + } +} + + diff --git a/core/json/src/main/scala-3/net/liftweb/json/JsonASTExtensions.scala b/core/json/src/main/scala-3/net/liftweb/json/JsonASTExtensions.scala new file mode 100644 index 000000000..35f2e4735 --- /dev/null +++ b/core/json/src/main/scala-3/net/liftweb/json/JsonASTExtensions.scala @@ -0,0 +1,135 @@ +/* + * Copyright 2009-2010 WorldWide Conferencing, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.liftweb +package json + +/** Scala 3-specific extensions for JsonAST - avoids path-dependent type issues */ +trait JsonASTExtensions { + self: JsonAST.JValue => + + /** + * Find immediate children of this `[[JValue]]` that match a specific `JValue` subclass. + * + * This method will search a `[[JObject]]` or `[[JArray]]` for values of a specific type and + * return a `List` of those values if they any are found. + * + * So given some JSON like so: + * + * {{{ + * [ + * { + * "thinga":1, + * "thingb":"bacon" + * },{ + * "thingc":3, + * "thingd":"Wakka" + * },{ + * "thinge":{ + * "thingf":4 + * }, + * "thingg":true + * } + * ] + * }}} + * + * You would use this method like so: + * + * {{{ + * scala> json \ classOf[JInt] + * res0: List[Any] = List(1, 3) + * }}} + * + * This method does require that whatever type you're searching for is subtype of `JValue`. + * Note: In Scala 3, returns List[Any] instead of List[A#Values] for type safety. + */ + def \[A <: JsonAST.JValue](clazz: Class[A]): List[Any] = { + val results = findDirect(children, typePredicate(clazz)) + results.asInstanceOf[List[A]].map { jvalue => + jvalue match { + case JsonAST.JInt(value) => value + case JsonAST.JDouble(value) => value + case JsonAST.JString(value) => value + case JsonAST.JBool(value) => value + case JsonAST.JNull => null + case other => other.values + } + } + } + + /** + * Find all descendants of this `JValue` that match a specific `JValue` subclass. + * + * Unlike its cousin `\`, this method will recurse down into all children looking for + * type matches searching a `[[JObject]]` or `[[JArray]]` for values of a specific type and + * return a `List` of those values if they are found. + * + * So given some JSON like so: + * + * {{{ + * [ + * { + * "thinga":1, + * "thingb":"bacon" + * },{ + * "thingc":3, + * "thingd":"Wakka" + * },{ + * "thinge":{ + * "thingf":4 + * }, + * "thingg":true + * } + * ] + * }}} + * + * You would use this method like so: + * + * {{{ + * scala> json \\ classOf[JInt] + * res0: List[Any] = List(1, 3, 4) + * }}} + * + * Note: In Scala 3, returns List[Any] instead of List[A#Values] for type safety. + */ + def \\[A <: JsonAST.JValue](clazz: Class[A]): List[Any] = { + val filtered = this.filter(typePredicate(clazz)) + filtered.asInstanceOf[List[A]].map { jvalue => + jvalue match { + case JsonAST.JInt(value) => value + case JsonAST.JDouble(value) => value + case JsonAST.JString(value) => value + case JsonAST.JBool(value) => value + case JsonAST.JNull => null + case other => other.values + } + } + } + + private def findDirect(xs: List[JsonAST.JValue], p: JsonAST.JValue => Boolean): List[JsonAST.JValue] = xs.flatMap { + case JsonAST.JObject(l) => + l.collect { + case JsonAST.JField(n, x) if p(x) => x + } + case JsonAST.JArray(l) => findDirect(l, p) + case x if p(x) => x :: Nil + case _ => Nil + } + + private def typePredicate[A <: JsonAST.JValue](clazz: Class[A]): JsonAST.JValue => Boolean = { json => + json.getClass == clazz + } +} \ No newline at end of file diff --git a/core/json/src/main/scala-3/net/liftweb/json/ScalaSig.scala b/core/json/src/main/scala-3/net/liftweb/json/ScalaSig.scala new file mode 100644 index 000000000..d113605c5 --- /dev/null +++ b/core/json/src/main/scala-3/net/liftweb/json/ScalaSig.scala @@ -0,0 +1,90 @@ +/* + * Copyright 2009-2010 WorldWide Conferencing, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.liftweb +package json + +import java.util.concurrent.ConcurrentHashMap +import scala.util.{Try, Success, Failure} + +private[json] object ScalaSigReader { + + def readConstructor(argName: String, clazz: Class[_], typeArgIndex: Int, argNames: List[String]): Class[_] = { + // In Scala 3, we fall back to runtime inspection since compile-time type information + // is not available in the same way as Scala 2's signature parsing. + // For most use cases, this will return AnyRef and the JSON extraction will handle + // type conversion at runtime based on the JSON structure. + + try { + // Try to use standard Java reflection to get constructor parameter types + val constructors = clazz.getDeclaredConstructors + val matchingConstructor = constructors.find(_.getParameterCount == argNames.length) + + matchingConstructor match { + case Some(constructor) => + val paramTypes = constructor.getGenericParameterTypes + val argIndex = argNames.indexOf(argName) + if (argIndex >= 0 && argIndex < paramTypes.length) { + paramTypes(argIndex) match { + case cls: Class[_] => + // Handle generic type arguments - for now, return the raw type + if (typeArgIndex > 0) classOf[AnyRef] else cls + case _ => classOf[AnyRef] + } + } else { + classOf[AnyRef] + } + case None => classOf[AnyRef] + } + } catch { + case _: Exception => classOf[AnyRef] + } + } + + def readField(name: String, clazz: Class[_], typeArgIndex: Int): Class[_] = { + try { + // Try to find the field using Java reflection + val field = Try(clazz.getDeclaredField(name)).orElse( + // If direct field lookup fails, try looking in superclasses + Try(findFieldInHierarchy(clazz, name)) + ) + + field match { + case Success(f) => + f.getGenericType match { + case cls: Class[_] => + // Handle generic type arguments - for now, return the raw type + if (typeArgIndex > 0) classOf[AnyRef] else cls + case _ => classOf[AnyRef] + } + case Failure(_) => classOf[AnyRef] + } + } catch { + case _: Exception => classOf[AnyRef] + } + } + + private def findFieldInHierarchy(clazz: Class[_], name: String): java.lang.reflect.Field = { + if (clazz == null) throw new NoSuchFieldException(name) + + try { + clazz.getDeclaredField(name) + } catch { + case _: NoSuchFieldException => + findFieldInHierarchy(clazz.getSuperclass, name) + } + } +} \ No newline at end of file diff --git a/core/json/src/main/scala-3/net/liftweb/json/TypeExtractor.scala b/core/json/src/main/scala-3/net/liftweb/json/TypeExtractor.scala new file mode 100644 index 000000000..609d0109a --- /dev/null +++ b/core/json/src/main/scala-3/net/liftweb/json/TypeExtractor.scala @@ -0,0 +1,38 @@ +package net.liftweb.json + +import scala.quoted.* +import scala.reflect.ClassTag + +/** Abstraction for runtime class and type args (Scala 3 variant). */ +trait TypeExtractor[A] { + def runtimeClass: Class[_] + def typeArguments: List[TypeExtractor[_]] +} + +/** Scala 3 implementation - hybrid ClassTag + macro. */ +object TypeExtractor extends LowPriorityTypeExtractor { + // High priority hybrid implementation + inline given hybridTypeExtractor[A](using ct: ClassTag[A]): TypeExtractor[A] = { + // Always use ClassTag for runtime class, try macro for type arguments + val typeArgs = try { + val derived = derivedFor[A] + derived.typeArguments + } catch { + case _: Exception => Nil + } + + new TypeExtractor[A] { + def runtimeClass: Class[_] = ct.runtimeClass + def typeArguments: List[TypeExtractor[_]] = typeArgs + } + } +} + +trait LowPriorityTypeExtractor { + // This should rarely be used due to higher priority macro version + given fromClassTag[A](using ct: ClassTag[A]): TypeExtractor[A] = new TypeExtractor[A] { + def runtimeClass: Class[_] = ct.runtimeClass + def typeArguments: List[TypeExtractor[_]] = Nil + } +} + diff --git a/core/json/src/main/scala-3/net/liftweb/json/TypeExtractorDeriver.scala b/core/json/src/main/scala-3/net/liftweb/json/TypeExtractorDeriver.scala new file mode 100644 index 000000000..f0b087294 --- /dev/null +++ b/core/json/src/main/scala-3/net/liftweb/json/TypeExtractorDeriver.scala @@ -0,0 +1,65 @@ +package net.liftweb.json + +import scala.quoted.* + +inline def derivedFor[A]: TypeExtractor[A] = ${ TypeExtractorDeriver.deriveImpl[A] } + +private object TypeExtractorDeriver { + def deriveImpl[A: Type](using Quotes): Expr[TypeExtractor[A]] = { + import quotes.reflect.* + + def normalize(tpe: TypeRepr): TypeRepr = tpe.dealias.simplified + + def splitArgs(tpe: TypeRepr): List[TypeRepr] = normalize(tpe) match { + case AppliedType(_, args) => args.map(normalize) + case _ => Nil + } + + def runtimeClassExpr(tpe: TypeRepr): Expr[Class[_]] = { + // Use TypeRepr to get class name and use Class.forName + val normalized = normalize(tpe) + val symbol = normalized.typeSymbol + + if (symbol.isClassDef) { + val className = symbol.fullName + // Handle some known problematic cases + className match { + case "scala.Int" => '{ classOf[Int] } + case "scala.Long" => '{ classOf[Long] } + case "scala.Double" => '{ classOf[Double] } + case "scala.Float" => '{ classOf[Float] } + case "scala.Boolean" => '{ classOf[Boolean] } + case "scala.Byte" => '{ classOf[Byte] } + case "scala.Short" => '{ classOf[Short] } + case "scala.Char" => '{ classOf[Char] } + case "java.lang.String" | "scala.Predef.String" => '{ classOf[String] } + case "scala.Unit" => '{ classOf[scala.runtime.BoxedUnit] } + case _ => + try { + '{ Class.forName(${ Expr(className) }) } + } catch { + case _ => '{ classOf[Object] } + } + } + } else { + '{ classOf[Object] } + } + } + + def deriveArg(tpe: TypeRepr): Expr[TypeExtractor[?]] = tpe.asType match { + case '[x] => deriveImpl[x].asExprOf[TypeExtractor[?]] + } + + val tpeA = TypeRepr.of[A] + val args = splitArgs(tpeA) + val argExtractors: Expr[List[TypeExtractor[?]]] = Expr.ofList(args.map(deriveArg)) + + '{ + new TypeExtractor[A] { + def runtimeClass: Class[_] = ${ runtimeClassExpr(TypeRepr.of[A]) } + def typeArguments: List[TypeExtractor[_]] = + $argExtractors.asInstanceOf[List[TypeExtractor[_]]] + } + } + } +} diff --git a/core/json/src/main/scala/net/liftweb/json/Extraction.scala b/core/json/src/main/scala/net/liftweb/json/Extraction.scala index 7f55becdf..9a61c7a68 100644 --- a/core/json/src/main/scala/net/liftweb/json/Extraction.scala +++ b/core/json/src/main/scala/net/liftweb/json/Extraction.scala @@ -28,49 +28,9 @@ import scala.reflect.{ClassTag, Manifest} * See: ExtractionExamples.scala */ object Extraction { - - /** Type information extractor that abstracts over Manifest (Scala 2.13) and future Scala 3 implementations. - * - * This trait provides a clean abstraction that allows the JSON extraction system to work - * with different type information sources: - * - Scala 2.13: Uses Manifest for full type information including generics - * - Scala 3: Can be implemented with inline macros using the new metaprogramming system - * - * Usage example: - * {{{ - * case class Person(name: String) - * val json: JValue = ... - * val person = json.extract[Person] // TypeExtractor[Person] is implicitly derived - * }}} - */ - trait TypeExtractor[A] { - def runtimeClass: Class[_] - def typeArguments: List[TypeExtractor[_]] - } - - object TypeExtractor { - /** Scala 2.13 implementation using Manifest - provides full type information including generics. */ - implicit def fromManifest[A](implicit mf: Manifest[A]): TypeExtractor[A] = new TypeExtractor[A] { - def runtimeClass: Class[_] = mf.runtimeClass - def typeArguments: List[TypeExtractor[_]] = mf.typeArguments.map(arg => fromManifest(arg)) - } - - /** Future Scala 3 macro-based implementation. - * This would replace the Manifest-based approach in Scala 3. - * - * Example implementation for Scala 3: - * {{{ - * inline implicit def derive[A]: TypeExtractor[A] = ${ deriveImpl[A] } - * - * def deriveImpl[A: Type](using Quotes): Expr[TypeExtractor[A]] = { - * import quotes.reflect._ - * val tpe = TypeRepr.of[A] - * // Extract type information using Scala 3 reflection - * // Create TypeExtractor with full generic type information - * } - * }}} - */ - } + // Back-compat: re-expose TypeExtractor under Extraction for existing call sites + type TypeExtractor[A] = net.liftweb.json.TypeExtractor[A] + val TypeExtractor: net.liftweb.json.TypeExtractor.type = net.liftweb.json.TypeExtractor import Meta._ import Meta.Reflection._ diff --git a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala index 53abebbf1..09e4df7bc 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonAST.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonAST.scala @@ -74,7 +74,7 @@ object JsonAST { * not extend this class because it really ''can't'' properly exist as a * first-class citizen of JSON. */ - sealed abstract class JValue extends Diff.Diffable { + sealed abstract class JValue extends Diff.Diffable with JsonASTExtensions { type Values /** @@ -193,83 +193,9 @@ object JsonAST { JObject(find(this)) } - /** - * Find immediate children of this `[[JValue]]` that match a specific `JValue` subclass. - * - * This methid will search a `[[JObject]]` or `[[JArray]]` for values of a specific type and - * return a `List` of those values if they any are found. - * - * So given some JSON like so: - * - * {{{ - * [ - * { - * "thinga":1, - * "thingb":"bacon" - * },{ - * "thingc":3, - * "thingd":"Wakka" - * },{ - * "thinge":{ - * "thingf":4 - * }, - * "thingg":true - * } - * ] - * }}} - * - * You would use this method like so: - * - * {{{ - * scala> json \ classOf[JInt] - * res0: List[net.liftweb.json.JInt#Values] = List(1, 3) - * }}} - * - * This method does require that whatever type you're searching for is subtype of `JValue`. - */ - def \[A <: JValue](clazz: Class[A]): List[A#Values] = - findDirect(children, typePredicate(clazz) _).asInstanceOf[List[A]] map { _.values } - - /** - * Find all descendants of this `JValue` that match a specific `JValue` subclass. - * - * Unlike its cousin `\`, this method will recurse down into all children looking for - * type matches searching a `[[JObject]]` or `[[JArray]]` for values of a specific type and - * return a `List` of those values if they are found. - * - * So given some JSON like so: - * - * {{{ - * [ - * { - * "thinga":1, - * "thingb":"bacon" - * },{ - * "thingc":3, - * "thingd":"Wakka" - * },{ - * "thinge":{ - * "thingf":4 - * }, - * "thingg":true - * } - * ] - * }}} - * - * You would use this method like so: - * - * {{{ - * scala> json \\ classOf[JInt] - * res0: List[net.liftweb.json.JInt#Values] = List(1, 3, 4) - * }}} - */ - def \\[A <: JValue](clazz: Class[A]): List[A#Values] = - (this filter typePredicate(clazz) _).asInstanceOf[List[A]] map { _.values } - - private def typePredicate[A <: JValue](clazz: Class[A])(json: JValue) = json match { - case x if x.getClass == clazz => true - case _ => false - } + // Type-based search methods (\ and \\) are provided by version-specific extensions: + // - scala-2.13/JsonASTExtensions.scala (uses path-dependent types A#Values) + // - scala-3/JsonASTExtensions.scala (uses alternative approach for type safety) /** * Return the element in the `i`-th position from a `[[JArray]]`. @@ -1138,31 +1064,31 @@ trait Implicits { object JsonDSL extends JsonDSL trait JsonDSL extends Implicits { implicit def seq2jvalue[A](s: Iterable[A])(implicit ev: A => JValue) : JArray = - JArray(s.toList.map { a => val v: JValue = a; v }) + JArray(s.toList.map { a => ev(a) }) implicit def map2jvalue[A](m: Map[String, A])(implicit ev: A => JValue): JObject = - JObject(m.toList.map { case (k, v) => JField(k, v) }) + JObject(m.toList.map { case (k, v) => JField(k, ev(v)) }) implicit def option2jvalue[A](opt: Option[A])(implicit ev: A => JValue): JValue = opt match { - case Some(x) => x + case Some(x) => ev(x) case None => JNothing } implicit def symbol2jvalue(x: Symbol) :JString = JString(x.name) - implicit def pair2jvalue[A](t: (String, A))(implicit ev: A => JValue) : JObject = JObject(List(JField(t._1, t._2))) + implicit def pair2jvalue[A](t: (String, A))(implicit ev: A => JValue) : JObject = JObject(List(JField(t._1, ev(t._2)))) implicit def list2jvalue(l: List[JField]) : JObject = JObject(l) implicit def jobject2assoc(o: JObject) : JsonListAssoc = new JsonListAssoc(o.obj) implicit def pair2Assoc[A](t: (String, A))(implicit ev: A => JValue): JsonAssoc[A] = new JsonAssoc(t) class JsonAssoc[A](left: (String, A))(implicit ev1: A => JValue) { def ~[B](right: (String, B))(implicit ev: B => JValue) = { - val l: JValue = left._2 - val r: JValue = right._2 + val l: JValue = ev1(left._2) + val r: JValue = ev(right._2) JObject(JField(left._1, l) :: JField(right._1, r) :: Nil) } def ~(right: JObject) = { - val l: JValue = left._2 + val l: JValue = ev1(left._2) JObject(JField(left._1, l) :: right.obj) } } diff --git a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala index ec2179351..3b106492b 100644 --- a/core/json/src/main/scala/net/liftweb/json/JsonParser.scala +++ b/core/json/src/main/scala/net/liftweb/json/JsonParser.scala @@ -223,7 +223,9 @@ object JsonParser { } } - do { + // Scala 3 compatible version of do-while loop + var continue = true + while (continue) { token = p.nextToken token match { case OpenObj => vals.push(IntermediateJObject(scala.collection.mutable.ListBuffer())) @@ -236,9 +238,9 @@ object JsonParser { case CloseObj => closeBlock(vals.popAny) case OpenArr => vals.push(IntermediateJArray(scala.collection.mutable.ListBuffer())) case CloseArr => closeBlock(vals.popAny) - case End => + case End => continue = false } - } while (token != End) + } root getOrElse JNothing } diff --git a/core/json/src/test/scala/net/liftweb/json/Examples.scala b/core/json/src/test/scala/net/liftweb/json/Examples.scala index 55dea4dab..adf71d397 100644 --- a/core/json/src/test/scala/net/liftweb/json/Examples.scala +++ b/core/json/src/test/scala/net/liftweb/json/Examples.scala @@ -81,9 +81,10 @@ trait AbstractExamples extends Specification { } "Unbox values using XPath-like type expression" in { - (parse(objArray) \ "children" \\ classOf[JInt] mustEqual List(5, 3)) and - (parse(lotto) \ "lotto" \ "winning-numbers" \ classOf[JInt] mustEqual List(2, 45, 34, 23, 7, 5, 3)) and - (parse(lotto) \\ "winning-numbers" \ classOf[JInt] mustEqual List(2, 45, 34, 23, 7, 5, 3)) + // In Scala 3, the type-based extractor returns List[Any]; widen expected types accordingly + (parse(objArray) \ "children" \\ classOf[JInt] mustEqual List[Any](5, 3)) and + (parse(lotto) \ "lotto" \ "winning-numbers" \ classOf[JInt] mustEqual List[Any](2, 45, 34, 23, 7, 5, 3)) and + (parse(lotto) \\ "winning-numbers" \ classOf[JInt] mustEqual List[Any](2, 45, 34, 23, 7, 5, 3)) } "Quoted example" in { diff --git a/core/json/src/test/scala/net/liftweb/json/ExtractionBugs.scala b/core/json/src/test/scala/net/liftweb/json/ExtractionBugs.scala index e22c56d66..88527a821 100644 --- a/core/json/src/test/scala/net/liftweb/json/ExtractionBugs.scala +++ b/core/json/src/test/scala/net/liftweb/json/ExtractionBugs.scala @@ -22,7 +22,7 @@ import org.specs2.mutable.Specification object ExtractionBugs extends Specification { "Extraction bugs Specification".title - implicit val formats: DefaultFormats.type = DefaultFormats + implicit val formats: Formats = DefaultFormats case class Response(data: List[Map[String, Int]]) diff --git a/core/json/src/test/scala/net/liftweb/json/ExtractionExamplesSpec.scala b/core/json/src/test/scala/net/liftweb/json/ExtractionExamplesSpec.scala index 3af404762..5b87a99ea 100644 --- a/core/json/src/test/scala/net/liftweb/json/ExtractionExamplesSpec.scala +++ b/core/json/src/test/scala/net/liftweb/json/ExtractionExamplesSpec.scala @@ -23,7 +23,7 @@ import org.specs2.mutable.Specification class ExtractionExamples extends Specification { "Extraction Examples Specification".title - implicit val formats: DefaultFormats.type = DefaultFormats + implicit val formats: Formats = DefaultFormats "Extraction example" in { val json = parse(testJson) diff --git a/core/json/src/test/scala/net/liftweb/json/FieldSerializerBugs.scala b/core/json/src/test/scala/net/liftweb/json/FieldSerializerBugs.scala index 0d7e92010..5699508f5 100644 --- a/core/json/src/test/scala/net/liftweb/json/FieldSerializerBugs.scala +++ b/core/json/src/test/scala/net/liftweb/json/FieldSerializerBugs.scala @@ -35,7 +35,7 @@ object FieldSerializerBugs extends Specification { */ "Name with symbols is correctly serialized" in { - implicit val formats = DefaultFormats + FieldSerializer[AnyRef]() + implicit val formats: Formats = DefaultFormats + FieldSerializer[AnyRef]() val s = WithSymbol(5) val str = Serialization.write(s) @@ -44,7 +44,7 @@ object FieldSerializerBugs extends Specification { } "FieldSerialization should work with Options" in { - implicit val formats = DefaultFormats + FieldSerializer[ClassWithOption]() + implicit val formats: Formats = DefaultFormats + FieldSerializer[ClassWithOption]() val t = new ClassWithOption t.field = Some(5) diff --git a/core/json/src/test/scala/net/liftweb/json/FieldSerializerExamples.scala b/core/json/src/test/scala/net/liftweb/json/FieldSerializerExamples.scala index e603d0275..884fbf390 100644 --- a/core/json/src/test/scala/net/liftweb/json/FieldSerializerExamples.scala +++ b/core/json/src/test/scala/net/liftweb/json/FieldSerializerExamples.scala @@ -31,7 +31,7 @@ object FieldSerializerExamples extends Specification { cat.name = "tommy" "All fields are serialized by default" in { - implicit val formats = DefaultFormats + FieldSerializer[WildDog]() + implicit val formats: Formats = DefaultFormats + FieldSerializer[WildDog]() val ser = swrite(dog) val dog2 = read[WildDog](ser) (dog2.name mustEqual dog.name) and @@ -46,20 +46,20 @@ object FieldSerializerExamples extends Specification { renameFrom("animalname", "name") ) - implicit val formats = DefaultFormats + dogSerializer + implicit val formats: Formats = DefaultFormats + dogSerializer val ser = swrite(dog) val dog2 = read[WildDog](ser) - (dog2.name mustEqual dog.name) - (dog2.color mustEqual dog.color) - (dog2.owner must beNull) - (dog2.size mustEqual dog.size) + (dog2.name mustEqual dog.name) and + (dog2.color mustEqual dog.color) and + (dog2.owner must beNull) and + (dog2.size mustEqual dog.size) and ((parse(ser) \ "animalname") mustEqual JString("pluto")) } "Selects best matching serializer" in { val dogSerializer = FieldSerializer[WildDog](ignore("name")) - implicit val formats = DefaultFormats + FieldSerializer[AnyRef]() + dogSerializer + implicit val formats: Formats = DefaultFormats + FieldSerializer[AnyRef]() + dogSerializer val dog2 = read[WildDog](swrite(dog)) val cat2 = read[WildCat](swrite(cat)) diff --git a/core/json/src/test/scala/net/liftweb/json/JsonAstSpec.scala b/core/json/src/test/scala/net/liftweb/json/JsonAstSpec.scala index e67276c27..b733e035b 100644 --- a/core/json/src/test/scala/net/liftweb/json/JsonAstSpec.scala +++ b/core/json/src/test/scala/net/liftweb/json/JsonAstSpec.scala @@ -182,12 +182,12 @@ class JsonAstSpec extends Specification with JValueGen with ScalaCheck { .find() must beFalse } - "equals hashCode" in prop({ x: JObject => + "equals hashCode" in prop { (x: JObject) => val y = JObject(scala.util.Random.shuffle(x.obj)) x must_== y x.## must_== y.## - }) + } "find all children" in { val subject = JObject( diff --git a/core/json/src/test/scala/net/liftweb/json/LottoExample.scala b/core/json/src/test/scala/net/liftweb/json/LottoExample.scala index d2d1846fe..8edfe4c7f 100644 --- a/core/json/src/test/scala/net/liftweb/json/LottoExample.scala +++ b/core/json/src/test/scala/net/liftweb/json/LottoExample.scala @@ -23,7 +23,7 @@ import org.specs2.mutable.Specification object LottoExample extends Specification { import JsonDSL._ - implicit val formats: DefaultFormats.type = DefaultFormats + implicit val formats: Formats = DefaultFormats case class Winner(`winner-id`: Long, numbers: List[Int]) case class Lotto(id: Long, `winning-numbers`: List[Int], winners: List[Winner], diff --git a/core/json/src/test/scala/net/liftweb/json/ParserBugs.scala b/core/json/src/test/scala/net/liftweb/json/ParserBugs.scala index e213aa0d5..0dd8e8fed 100644 --- a/core/json/src/test/scala/net/liftweb/json/ParserBugs.scala +++ b/core/json/src/test/scala/net/liftweb/json/ParserBugs.scala @@ -57,8 +57,10 @@ object ParserBugs extends Specification { private val discardParser = (p : JsonParser.Parser) => { var token: JsonParser.Token = null - do { + var continue = true + while (continue) { token = p.nextToken - } while (token != JsonParser.End) + if (token == JsonParser.End) continue = false + } } }