All Downloads are FREE. Search and download functionalities are using the official Maven repository.

olon.json.Extraction.scala Maven / Gradle / Ivy

The newest version!
package olon
package json

import izumi.reflect.Tag

import java.lang.reflect.InvocationTargetException
import java.lang.reflect.{Constructor => JConstructor}
import java.lang.{Boolean => JavaBoolean}
import java.lang.{Byte => JavaByte}
import java.lang.{Double => JavaDouble}
import java.lang.{Float => JavaFloat}
import java.lang.{Integer => JavaInteger}
import java.lang.{Long => JavaLong}
import java.lang.{Short => JavaShort}
import java.sql.Timestamp
import java.util.Date
import scala.collection.immutable.ArraySeq

/** Function to extract values from JSON AST using case classes.
  *
  * See: ExtractionExamples.scala
  */
object Extraction {
  import Meta._
  import Meta.Reflection._

  /** Extract a case class from JSON.
    * @see
    *   olon.json.JsonAST.JValue#extract
    * @throws MappingException
    *   is thrown if extraction fails
    */
  // SCALA3 Using `ClassTag` instead of `Manifest`
  def extract[A](
      json: JValue
  )(implicit formats: Formats, mf: Tag[A]): A = {
    // SCALA3 using `?` instead of `_`

    // SCALA3 replaced Manifests with izumi.reflect.Tag
    def allTypes(mf: Tag[?]): Seq[Class[?]] = {
      import izumi.reflect.macrortti.LightTypeTag
      def getAllArgs(tpe: LightTypeTag): Seq[LightTypeTag] =
        Seq(tpe) ++ tpe.typeArgs.flatMap(getAllArgs(_))
      def getClassOf(name: String) = {
        name match {
          case "scala.Long"    => classOf[scala.Long]
          case "scala.Double"  => classOf[scala.Double]
          case "scala.Float"   => classOf[scala.Float]
          case "scala.Byte"    => classOf[scala.Byte]
          case "scala.BigInt"  => classOf[scala.BigInt]
          case "scala.Boolean" => classOf[scala.Boolean]
          case "scala.Short"   => classOf[scala.Short]
          case "scala.Int"     => classOf[scala.Int]
          case _               => Class.forName(name)
        }
      }
      val res = getAllArgs(mf.tag).map(_.repr).map { (scalaRepr: String) =>
        // olon.json.SerializationExamples::Project -> olon.json.SerializationExamples$Project
        val javaName = scalaRepr.replace("::", "$").split('[')(0)
        getClassOf(javaName)
      }
      res
    }

    // SCALA3 ORIGINAL
    // allTypes(mf)

    // SCALA 3 the above replaces the below code, which was used in scala 2 thanks to scala.reflect.Manifest
    // mf.runtimeClass :: (mf.typeArguments flatMap allTypes)

    try {
      val types = allTypes(mf)
      extract0(json, types.head, types.tail).asInstanceOf[A]
    } catch {
      case e: MappingException => throw e
      case e: Exception        => throw new MappingException("unknown error", e)
    }
  }

  /** Extract a case class from JSON.
    * @see
    *   olon.json.JsonAST.JValue#extract
    */
  // SCALA3 Using `ClassTag` instead of `Manifest`
  def extractOpt[A](
      json: JValue
  )(implicit formats: Formats, mf: Tag[A]): Option[A] =
    try { Some(extract(json)(formats, mf)) }
    catch { case _: MappingException => None }

  /** Decompose a case class into JSON. 

Example:

 case class
    * Person(name: String, age: Int) implicit val formats =
    * olon.json.DefaultFormats Extraction.decompose(Person("joe", 25)) ==
    * JObject(JField("age",JInt(25)) :: JField("name",JString("joe")) :: Nil)
    * 
*/ def decompose(a: Any)(implicit formats: Formats): JValue = { // SCALA3 using `?` instead of `_` def prependTypeHint(clazz: Class[?], o: JObject) = JObject( JField( formats.typeHintFieldName, JString(formats.typeHints.hintFor(clazz)) ) :: o.obj ) // SCALA3 using `?` instead of `_` def mkObject(clazz: Class[?], fields: List[JField]) = formats.typeHints.containsHint_?(clazz) match { case true => prependTypeHint(clazz, JObject(fields)) case false => JObject(fields) } val serializer = formats.typeHints.serialize val any = a.asInstanceOf[AnyRef] if (formats.customSerializer(formats).isDefinedAt(a)) { formats.customSerializer(formats)(a) } else if (!serializer.isDefinedAt(a)) { any match { case null => JNull case x: JValue => x case x if primitive_?(x.getClass) => primitive2jvalue(x)(formats) case x: Map[_, _] => JObject( (x map { case (k: String, v) => JField(k, decompose(v)) }).toList ) case x: Iterable[_] => JArray(x.toList map decompose) case x if (x.getClass.isArray) => // SCALA3 using `?` instead of `_` JArray(x.asInstanceOf[Array[?]].toList map decompose) case x: Option[_] => x.flatMap[JValue] { y => Some(decompose(y)) }.getOrElse(JNothing) case x: Product if formats.tuplesAsArrays && tuple_?(x.getClass) => JArray(x.productIterator.toList.map(decompose)) case x => val fields = getDeclaredFields(x.getClass) val constructorArgs = primaryConstructorArgs(x.getClass).map { case (name, _) => (name, fields.get(name)) } constructorArgs.collect { case (name, Some(f)) => f.setAccessible(true) JField(unmangleName(name), decompose(f.get(x))) } match { case args => val fields = formats.fieldSerializer(x.getClass).map { serializer => Reflection.fields(x.getClass).map { case (mangledName, _) => val n = Meta.unmangleName(mangledName) val fieldVal = Reflection.getField(x, mangledName) val s = serializer.serializer orElse Map( (n, fieldVal) -> Some(n, fieldVal) ) s((n, fieldVal)) .map { case (name, value) => JField(name, decompose(value)) } .getOrElse(JField(n, JNothing)) } } getOrElse Nil val uniqueFields = fields filterNot (f => args.find(_.name == f.name).isDefined) mkObject(x.getClass, uniqueFields ++ args) } } } else prependTypeHint(any.getClass, serializer(any)) } /** Flattens the JSON to a key/value map. */ def flatten(json: JValue): Map[String, String] = { def escapePath(str: String) = str def flatten0(path: String, json: JValue): Map[String, String] = { json match { case JNothing | JNull => Map() case JString(s) => Map(path -> ("\"" + JsonAST.quote(s) + "\"")) case JDouble(num) => Map(path -> num.toString) case JInt(num) => Map(path -> num.toString) case JBool(value) => Map(path -> value.toString) case JObject(obj) => obj.foldLeft(Map[String, String]()) { case (map, JField(name, value)) => map ++ flatten0(path + "." + escapePath(name), value) } case JArray(arr) => arr.length match { case 0 => Map(path -> "[]") case _ => arr .foldLeft((Map[String, String](), 0)) { (tuple, value) => ( tuple._1 ++ flatten0(path + "[" + tuple._2 + "]", value), tuple._2 + 1 ) } ._1 } } } flatten0("", json) } /** Unflattens a key/value map to a JSON object. */ def unflatten(map: Map[String, String]): JValue = { import scala.util.matching.Regex def extractValue(value: String): JValue = value.toLowerCase match { case "" => JNothing case "null" => JNull case "true" => JBool(true) case "false" => JBool(false) case "[]" => JArray(Nil) case x @ _ => if (value.charAt(0).isDigit) { if (value.indexOf('.') == -1) JInt(BigInt(value)) else JDouble(JsonParser.parseDouble(value)) } else JString(JsonParser.unquote(value.substring(1))) } def submap(prefix: String): Map[String, String] = // SCALA3 using `x*` instead of `_*` Map( ArraySeq.unsafeWrapArray( map .filter(t => t._1 == prefix || t._1.startsWith(prefix + ".") || t._1 .startsWith(prefix + "[") ) .map(t => (t._1.substring(prefix.length), t._2)) .toList .toArray )* ) val ArrayProp = new Regex("""^(\.([^\.\[]+))\[(\d+)\].*$""") val ArrayElem = new Regex("""^(\[(\d+)\]).*$""") val OtherProp = new Regex("""^(\.([^\.\[]+)).*$""") val uniquePaths = map.keys .foldLeft[Set[String]](Set()) { (set, key) => key match { case ArrayProp(p, _, _) => set + p case OtherProp(p, _) => set + p case ArrayElem(p, _) => set + p case x @ _ => set + x } } .toList .sortWith(_ < _) // Sort is necessary to get array order right uniquePaths.foldLeft[JValue](JNothing) { (jvalue, key) => jvalue.merge(key match { case ArrayProp(_, f, _) => JObject(List(JField(f, unflatten(submap(key))))) case ArrayElem(_, _) => JArray(List(unflatten(submap(key)))) case OtherProp(_, f) => JObject(List(JField(f, unflatten(submap(key))))) case "" => extractValue(map(key)) }) } } // SCALA3 using `?` instead of `_` private def mkMapping(clazz: Class[?], typeArgs: Seq[Class[?]])(implicit formats: Formats ): Meta.Mapping = { // SCALA3 using `?` instead of `_` if ( clazz == classOf[Option[?]] || clazz == classOf[ List[?] ] || clazz == classOf[Set[?]] || clazz.isArray ) { Col(TypeInfo(clazz, None), mkMapping(typeArgs.head, typeArgs.tail)) } else if (clazz == classOf[Map[?, ?]]) { Dict(mkMapping(typeArgs.tail.head, typeArgs.tail.tail)) } else if (formats.tuplesAsArrays && tuple_?(clazz)) { val childMappings = typeArgs.map(c => mkMapping(c, Nil)).toList HCol(TypeInfo(clazz, None), childMappings) } else { mappingOf(clazz, typeArgs) } } // SCALA3 using `?` instead of `_` private def extract0(json: JValue, clazz: Class[?], typeArgs: Seq[Class[?]])( implicit formats: Formats ): Any = { val mapping = mkMapping(clazz, typeArgs) extract0(json, mapping) } def extract(json: JValue, target: TypeInfo)(implicit formats: Formats): Any = extract0(json, mappingOf(target.clazz)) private def extract0(json: JValue, mapping: Mapping)(implicit formats: Formats ): Any = { def newInstance(constructor: Constructor, json: JValue) = { def findBestConstructor = { if (constructor.choices.size == 1) { constructor.choices.head // optimized common case } else { val argNames = json match { case JObject(fs) => fs.map(_.name) case _ => Nil } constructor .bestMatching(argNames) .getOrElse( fail( "No constructor for type " + constructor.targetType.clazz + ", " + json ) ) } } // SCALA3 using `?` instead of `_` def setFields(a: AnyRef, json: JValue, constructor: JConstructor[?]) = json match { case o: JObject => formats.fieldSerializer(a.getClass).map { serializer => val constructorArgNames = Reflection .constructorArgs( a.getClass, constructor, formats.parameterNameReader, None ) .map(_._1) .toSet val jsonFields = o.obj.map { f => val JField(n, v) = (serializer.deserializer orElse Map(f -> f))(f) (n, (n, v)) }.toMap val fieldsToSet = Reflection .fields(a.getClass) .filterNot(f => constructorArgNames.contains(f._1)) fieldsToSet.foreach { case (name, typeInfo) => jsonFields.get(name).foreach { case (n, v) => // SCALA3 using `?` instead of `_` val typeArgs = typeInfo.parameterizedType .map( _.getActualTypeArguments .map(_.asInstanceOf[Class[?]]) .toList .zipWithIndex .map { case (t, idx) => if (t == classOf[java.lang.Object]) ScalaSigReader.readField(name, a.getClass, idx) else t } ) val value = extract0(v, typeInfo.clazz, typeArgs.getOrElse(Nil)) Reflection.setField(a, n, value) } } } a case _ => a } def instantiate = { val c = findBestConstructor val jconstructor = c.constructor val args = c.args.map(a => build(json \ a.path, a)) try { if (jconstructor.getDeclaringClass == classOf[java.lang.Object]) fail("No information known about type") // SCALA3 using `x*` instead of `_*` val instance = jconstructor.newInstance( args.map(_.asInstanceOf[AnyRef]).toArray* ) setFields(instance.asInstanceOf[AnyRef], json, jconstructor) } catch { case exception: Exception => exception match { case matchedException @ (_: IllegalArgumentException | _: InstantiationException) => fail( "Parsed JSON values do not match with class constructor\nargs=" + args.mkString(",") + "\narg types=" + args .map(a => if (a != null) a.asInstanceOf[AnyRef].getClass.getName else "null" ) .mkString(",") + "\nconstructor=" + jconstructor, matchedException ) case exceptionThrownInConstructor: InvocationTargetException => fail( "An exception was thrown in the class constructor during extraction", exceptionThrownInConstructor ) case unmatchedException => throw unmatchedException } } } def mkWithTypeHint( typeHint: String, fields: List[JField], typeInfo: TypeInfo ) = { val obj = JObject( fields filterNot (_.name == formats.typeHintFieldName) ) val deserializer = formats.typeHints.deserialize if (!deserializer.isDefinedAt(typeHint, obj)) { val concreteClass = formats.typeHints.classFor(typeHint) getOrElse fail( "Do not know how to deserialize '" + typeHint + "'" ) val typeArgs = typeInfo.parameterizedType .map(_.getActualTypeArguments.toList.map(Meta.rawClassOf)) .getOrElse(Nil) build(obj, mappingOf(concreteClass, typeArgs)) } else deserializer(typeHint, obj) } val custom = formats.customDeserializer(formats) if (custom.isDefinedAt(constructor.targetType, json)) { custom(constructor.targetType, json) } else { json match { case JNull => null case JObject(TypeHint(t, fs)) => mkWithTypeHint(t, fs, constructor.targetType) case _ => instantiate } } } object TypeHint { def unapply(fs: List[JField]): Option[(String, List[JField])] = if (formats.typeHints == NoTypeHints) None else { val grouped = fs groupBy (_.name == formats.typeHintFieldName) if (grouped.isDefinedAt(true)) Some( ( grouped(true).head.value.values.toString, grouped.get(false).getOrElse(Nil) ) ) else None } } // SCALA3 using `?` instead of `_` def newCollection( root: JValue, m: Mapping, constructor: Array[?] => Any ) = { val array: Array[?] = root match { case JArray(arr) => arr.map(build(_, m)).toArray case JNothing | JNull => Array[AnyRef]() case x => fail( "Expected collection but got " + x + " for root " + root + " and mapping " + m ) } constructor(array) } def newOption(root: JValue, m: Mapping) = { root match { case JNothing | JNull => None case x => Option(build(x, m)) } } def newTuple(root: JValue, mappings: List[Mapping]): Any = { root match { case JArray(items) if items.nonEmpty && items.length <= tuples.length => val builtItems: Seq[Object] = items .zip(mappings) .map({ case (item, mapping) => build(item, mapping).asInstanceOf[Object] }) val tupleIndex = items.length - 1 val typedTupleConstructor = tupleConstructors.get(tupleIndex).getOrElse { throw new IllegalArgumentException( s"Cannot instantiate a tuple of length ${items.length} even though that should be a valid tuple length." ) } // SCALA3 using `x*` instead of `_*` typedTupleConstructor.newInstance(builtItems*) case JArray(items) => throw new IllegalArgumentException( "Cannot create a tuple of length " + items.length ) case JObject(items) if items.forall(_.name.startsWith("_")) => val sortedItems = items.sortWith { (i1, i2) => val numerialName1 = i1.name.drop(1).toInt val numerialName2 = i2.name.drop(1).toInt numerialName1 < numerialName2 } newTuple(JArray(sortedItems.map(_.value)), mappings) case x => throw new IllegalArgumentException( "Got unexpected while attempting to create tuples: " + x ) } } def build(root: JValue, mapping: Mapping): Any = mapping match { case Value(targetType) => convert(root, targetType, formats) case c: Constructor => newInstance(c, root) case Cycle(targetType) => build(root, mappingOf(targetType)) case Arg(path, m, optional) => mkValue(root, m, path, optional) case HCol(targetType, mappings) if formats.tuplesAsArrays => val c = targetType.clazz if (tuples.find(_.isAssignableFrom(c)).isDefined) { newTuple(root, mappings) } else { fail("Expected tuple but found " + mappings) } case HCol(targetType, mappings) => fail("Invalid type found " + targetType + " " + mappings) case Col(targetType, m) => val custom = formats.customDeserializer(formats) val c = targetType.clazz // SCALA3 using `?` instead of `_` // SCALA3 using `x*` instead of `_*` if (custom.isDefinedAt(targetType, root)) custom(targetType, root) else if (c == classOf[List[?]]) newCollection(root, m, a => List(ArraySeq.unsafeWrapArray(a)*)) else if (c == classOf[Set[?]]) newCollection(root, m, a => Set(ArraySeq.unsafeWrapArray(a)*)) else if (c.isArray) newCollection(root, m, mkTypedArray(c)) else if (classOf[Seq[?]].isAssignableFrom(c)) newCollection(root, m, a => List(ArraySeq.unsafeWrapArray(a)*)) else if (c == classOf[Option[?]]) newOption(root, m) else fail("Expected collection but got " + m + " for class " + c) case Dict(m) => root match { case JObject(xs) => Map(xs.map(x => (x.name, build(x.value, m)))*) case x => fail("Expected object but got " + x) } } // SCALA3 using `?` instead of `_` def mkTypedArray(c: Class[?])(a: Array[?]) = { import java.lang.reflect.Array.{newInstance => newArray} a.foldLeft((newArray(c.getComponentType, a.length), 0)) { (tuple, e) => { java.lang.reflect.Array.set(tuple._1, tuple._2, e); (tuple._1, tuple._2 + 1) } }._1 } def mkValue( root: JValue, mapping: Mapping, path: String, optional: Boolean ) = { if (optional && root == JNothing) { None } else { try { val x = build(root, mapping) if (optional) Option(x) else x } catch { case e @ MappingException(msg, _) => if (optional && (root == JNothing || root == JNull)) { None } else { fail("No usable value for " + path + "\n" + msg, e) } } } } build(json, mapping) } // SCALA3 using `?` instead of `_` private def convert( json: JValue, targetType: Class[?], formats: Formats ): Any = json match { case JInt(x) if (targetType == classOf[Int]) => x.intValue case JInt(x) if (targetType == classOf[JavaInteger]) => JavaInteger.valueOf(x.intValue) case JInt(x) if (targetType == classOf[BigInt]) => x case JInt(x) if (targetType == classOf[Long]) => x.longValue case JInt(x) if (targetType == classOf[JavaLong]) => JavaLong.valueOf(x.longValue) case JInt(x) if (targetType == classOf[Double]) => x.doubleValue case JInt(x) if (targetType == classOf[JavaDouble]) => JavaDouble.valueOf(x.doubleValue) case JInt(x) if (targetType == classOf[Float]) => x.floatValue case JInt(x) if (targetType == classOf[JavaFloat]) => JavaFloat.valueOf(x.floatValue) case JInt(x) if (targetType == classOf[Short]) => x.shortValue case JInt(x) if (targetType == classOf[JavaShort]) => JavaShort.valueOf(x.shortValue) case JInt(x) if (targetType == classOf[Byte]) => x.byteValue case JInt(x) if (targetType == classOf[JavaByte]) => JavaByte.valueOf(x.byteValue) case JInt(x) if (targetType == classOf[String]) => x.toString case JInt(x) if (targetType == classOf[Number]) => x.longValue case JDouble(x) if (targetType == classOf[Double]) => x case JDouble(x) if (targetType == classOf[JavaDouble]) => JavaDouble.valueOf(x) case JDouble(x) if (targetType == classOf[Float]) => x.floatValue case JDouble(x) if (targetType == classOf[JavaFloat]) => JavaFloat.valueOf(x.floatValue) case JDouble(x) if (targetType == classOf[String]) => x.toString case JDouble(x) if (targetType == classOf[Int]) => x.intValue case JDouble(x) if (targetType == classOf[Long]) => x.longValue case JDouble(x) if (targetType == classOf[Number]) => x case JString(s) if (targetType == classOf[String]) => s case JString(s) if (targetType == classOf[Symbol]) => Symbol(s) case JString(s) if (targetType == classOf[Date]) => formats.dateFormat.parse(s).getOrElse(fail("Invalid date '" + s + "'")) case JString(s) if (targetType == classOf[Timestamp]) => new Timestamp( formats.dateFormat .parse(s) .getOrElse(fail("Invalid date '" + s + "'")) .getTime ) case JBool(x) if (targetType == classOf[Boolean]) => x case JBool(x) if (targetType == classOf[JavaBoolean]) => JavaBoolean.valueOf(x) case j: JValue if (targetType == classOf[JValue]) => j case j: JObject if (targetType == classOf[JObject]) => j case j: JArray if (targetType == classOf[JArray]) => j case JNull => null case JNothing => fail( "Did not find value which can be converted into " + targetType.getName ) case _ => val custom = formats.customDeserializer(formats) val typeInfo = TypeInfo(targetType, None) if (custom.isDefinedAt(typeInfo, json)) { custom(typeInfo, json) } else { fail("Do not know how to convert " + json + " into " + targetType) } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy