alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Lift Framework example source code file (Extraction.scala)

This example Lift Framework source code file (Extraction.scala) is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Java - Lift Framework tags/keywords

any, formats, jdouble, jfield, jint, jint, jnothing, jobject, jobject, jvalue, jvalue, map, map, reflection, string, util

The Lift Framework Extraction.scala source code

/*
 * 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.lang.reflect.{Constructor => JConstructor, Type}
import java.lang.{Integer => JavaInteger, Long => JavaLong, Short => JavaShort, Byte => JavaByte, Boolean => JavaBoolean, Double => JavaDouble, Float => JavaFloat}
import java.util.Date
import scala.reflect.Manifest

/** 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 net.liftweb.json.JsonAST.JValue#extract
   * @throws MappingException is thrown if extraction fails
   */
  def extract[A](json: JValue)(implicit formats: Formats, mf: Manifest[A]): A = {
    def allTypes(mf: Manifest[_]): List[Class[_]] = mf.erasure :: (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 net.liftweb.json.JsonAST.JValue#extract
   */
  def extractOpt[A](json: JValue)(implicit formats: Formats, mf: Manifest[A]): Option[A] = 
    try { Some(extract(json)(formats, mf)) } catch { case _: MappingException => None }

  /** Decompose a case class into JSON.
   * <p>
   * Example:<pre>
   * case class Person(name: String, age: Int)
   * implicit val formats = net.liftweb.json.DefaultFormats
   * Extraction.decompose(Person("joe", 25)) == JObject(JField("age",JInt(25)) :: JField("name",JString("joe")) :: Nil)
   * </pre>
   */
  def decompose(a: Any)(implicit formats: Formats): JValue = {
    def prependTypeHint(clazz: Class[_], o: JObject) = 
      JField(formats.typeHintFieldName, JString(formats.typeHints.hintFor(clazz))) ++ o

    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: Collection[_] => JArray(x.toList map decompose)
        case x if (x.getClass.isArray) => JArray(x.asInstanceOf[Array[_]].toList map decompose)
        case x: Option[_] => x.flatMap[JValue] { y => Some(decompose(y)) }.getOrElse(JNothing)
        case x => 
          val constructorArgs = primaryConstructorArgs(x.getClass)
          constructorArgs.collect { case (name, _) if Reflection.hasDeclaredField(x.getClass, name) =>
            val f = x.getClass.getDeclaredField(name)
            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 JField(name, value) => flatten0(path + escapePath(name), value)
        case JObject(obj)        => obj.foldLeft(Map[String, String]()) { (map, field) => map ++ flatten0(path + ".", field) }
        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] = 
      Map(
        map.filter(t => 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, f, i) => set + p
          case OtherProp(p, f)    => set + p    
          case ArrayElem(p, i)    => 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(p, f, i) => JObject(List(JField(f, unflatten(submap(key)))))
        case ArrayElem(p, i)    => JArray(List(unflatten(submap(key))))
        case OtherProp(p, f)    => JObject(List(JField(f, unflatten(submap(key)))))
        case ""                 => extractValue(map(key))
      })
    }
  }

  private def extract0(json: JValue, clazz: Class[_], typeArgs: Seq[Class[_]])
                      (implicit formats: Formats): Any = {
    def mkMapping(clazz: Class[_], typeArgs: Seq[Class[_]])(implicit formats: Formats): Meta.Mapping = {
      if (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 mappingOf(clazz, typeArgs)
    }
    extract0(json, mkMapping(clazz, typeArgs))
  }

  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 JField(name, _) => List(name)
            case x => Nil
          }
          constructor.bestMatching(argNames)
            .getOrElse(fail("No constructor for type " + constructor.targetType.clazz + ", " + json))
        }
      }

      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) =>
                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")

          val instance = jconstructor.newInstance(args.map(_.asInstanceOf[AnyRef]).toArray: _*)
          setFields(instance.asInstanceOf[AnyRef], json, jconstructor)
        } catch {
          case e @ (_: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)
        }
      }

      def mkWithTypeHint(typeHint: String, fields: List[JField], typeInfo: TypeInfo) = {
        val obj = JObject(fields)
        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)
      val JsonClass = formats.typeHintFieldName
      if (custom.isDefinedAt(constructor.targetType, json)) custom(constructor.targetType, json)
      else json match {
        case JNull => null
        case JObject(JField(JsonClass, JString(t)) :: xs) => mkWithTypeHint(t, xs, constructor.targetType)
        case JField(_, JObject(JField(JsonClass, JString(t)) :: xs)) => mkWithTypeHint(t, xs, constructor.targetType)
        case _ => instantiate
      }
    }

    def newPrimitive(elementType: Class[_], elem: JValue) = convert(elem, elementType, formats)
    
    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 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(fieldValue(root), m, path, optional)
      case Col(targetType, m) =>
        val custom = formats.customDeserializer(formats)
        val c = targetType.clazz
        if (custom.isDefinedAt(targetType, root)) custom(targetType, root)
        else if (c == classOf[List[_]]) newCollection(root, m, a => List(a: _*))
        else if (c == classOf[Set[_]]) newCollection(root, m, a => Set(a: _*))
        else if (c.isArray) newCollection(root, m, mkTypedArray(c))
        else if (classOf[Seq[_]].isAssignableFrom(c)) newCollection(root, m, a => List(a: _*))
        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)
      }
    }
    
    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 mkList(root: JValue, m: Mapping) = root match {
      case JArray(arr) => arr.map(build(_, m))
      case JNothing | JNull => Nil
      case x => fail("Expected array but got " + x)
    }

    def mkValue(root: JValue, mapping: Mapping, path: String, optional: Boolean) = 
      if (optional && root == JNothing) None
      else {
        try {
          val x = build(root, mapping)
          if (optional) {
            if (x == null) None else Some(x) 
          } else x
        } catch { 
          case e @ MappingException(msg, _) =>
            if (optional) None else fail("No usable value for " + path + "\n" + msg, e)
        }
      }

    def fieldValue(json: JValue): JValue = json match {
      case JField(_, value) => value
      case x => x
    }

    build(json, mapping)
  }

  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]) => new JavaInteger(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]) => new JavaLong(x.longValue)
    case JInt(x) if (targetType == classOf[Double]) => x.doubleValue
    case JInt(x) if (targetType == classOf[JavaDouble]) => new JavaDouble(x.doubleValue)
    case JInt(x) if (targetType == classOf[Float]) => x.floatValue
    case JInt(x) if (targetType == classOf[JavaFloat]) => new JavaFloat(x.floatValue)
    case JInt(x) if (targetType == classOf[Short]) => x.shortValue
    case JInt(x) if (targetType == classOf[JavaShort]) => new JavaShort(x.shortValue)
    case JInt(x) if (targetType == classOf[Byte]) => x.byteValue
    case JInt(x) if (targetType == classOf[JavaByte]) => new JavaByte(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]) => new JavaDouble(x)
    case JDouble(x) if (targetType == classOf[Float]) => x.floatValue
    case JDouble(x) if (targetType == classOf[JavaFloat]) => new JavaFloat(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 JBool(x) if (targetType == classOf[Boolean]) => x
    case JBool(x) if (targetType == classOf[JavaBoolean]) => new JavaBoolean(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 JField(_, x) => convert(x, targetType, formats)
    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)
  }
}

Other Lift Framework examples (source code examples)

Here is a short list of links related to this Lift Framework Extraction.scala source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.