![JAR search and dependency download from the Maven repository](/logo.png)
net.liftweb.json.Formats.scala Maven / Gradle / Ivy
The newest version!
/*
* 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.{Date, TimeZone}
import java.util.concurrent.ConcurrentHashMap
import scala.collection.concurrent.{Map=>ConcurrentScalaMap}
import scala.jdk.CollectionConverters._
/** Formats to use when converting JSON.
* Formats are usually configured by using an implicit parameter:
*
* implicit val formats = net.liftweb.json.DefaultFormats
*
*/
trait Formats { self: Formats =>
val dateFormat: DateFormat
val typeHints: TypeHints = NoTypeHints
val customSerializers: List[Serializer[_]] = Nil
val fieldSerializers: List[(Class[_], FieldSerializer[_])] = Nil
/**
* Support for the tuple decomposition/extraction that represents tuples as JSON
* arrays. This provides better support for heterogenous arrays in JSON, but enable it at your
* own risk as it does change the behavior of serialization/deserialization and comes
* with some caveats (such as Scala primitives not being recognized reliably during extraction).
*/
val tuplesAsArrays = false
/**
* The name of the field in JSON where type hints are added (jsonClass by default)
*/
val typeHintFieldName = "jsonClass"
/**
* Parameter name reading strategy. By deafult 'paranamer' is used.
*/
val parameterNameReader: ParameterNameReader = Meta.ParanamerReader
/**
* Adds the specified type hints to this formats.
*/
def + (extraHints: TypeHints): Formats = new Formats {
val dateFormat = Formats.this.dateFormat
override val typeHintFieldName = self.typeHintFieldName
override val parameterNameReader = self.parameterNameReader
override val typeHints = self.typeHints + extraHints
override val customSerializers = self.customSerializers
override val fieldSerializers = self.fieldSerializers
}
/**
* Adds the specified custom serializer to this formats.
*/
def + (newSerializer: Serializer[_]): Formats = new Formats {
val dateFormat = Formats.this.dateFormat
override val typeHintFieldName = self.typeHintFieldName
override val parameterNameReader = self.parameterNameReader
override val typeHints = self.typeHints
override val customSerializers = newSerializer :: self.customSerializers
override val fieldSerializers = self.fieldSerializers
}
/**
* Adds the specified custom serializers to this formats.
*/
def ++ (newSerializers: Iterable[Serializer[_]]): Formats =
newSerializers.foldLeft(this)(_ + _)
/**
* Adds a field serializer for a given type to this formats.
*/
def + [A](newSerializer: FieldSerializer[A])(implicit mf: Manifest[A]): Formats = new Formats {
val dateFormat = Formats.this.dateFormat
override val typeHintFieldName = self.typeHintFieldName
override val parameterNameReader = self.parameterNameReader
override val typeHints = self.typeHints
override val customSerializers = self.customSerializers
// The type inferencer infers an existential type below if we use
// value :: list instead of list.::(value), and we get a feature
// warning.
override val fieldSerializers: List[(Class[_], FieldSerializer[_])] = self.fieldSerializers.::((mf.runtimeClass: Class[_], newSerializer))
}
private[json] def fieldSerializer(clazz: Class[_]): Option[FieldSerializer[_]] = {
import ClassDelta._
val ord = Ordering[Int].on[(Class[_], FieldSerializer[_])](x => delta(x._1, clazz))
fieldSerializers filter (_._1.isAssignableFrom(clazz)) match {
case Nil => None
case xs => Some((xs min ord)._2)
}
}
def customSerializer(implicit format: Formats) =
customSerializers.foldLeft(Map(): PartialFunction[Any, JValue]) { (acc, x) =>
acc.orElse(x.serialize)
}
def customDeserializer(implicit format: Formats) =
customSerializers.foldLeft(Map(): PartialFunction[(TypeInfo, JValue), Any]) { (acc, x) =>
acc.orElse(x.deserialize)
}
}
/** Conversions between String and Date.
*/
trait DateFormat {
def parse(s: String): Option[Date]
def format(d: Date): String
}
trait Serializer[A] {
def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), A]
def serialize(implicit format: Formats): PartialFunction[Any, JValue]
}
/** Type hints can be used to alter the default conversion rules when converting
* Scala instances into JSON and vice versa. Type hints must be used when converting
* class which is not supported by default (for instance when class is not a case class).
*
* Example:
* class DateTime(val time: Long)
*
* val hints = new ShortTypeHints(classOf[DateTime] :: Nil) {
* override def serialize: PartialFunction[Any, JObject] = {
* case t: DateTime => JObject(JField("t", JInt(t.time)) :: Nil)
* }
*
* override def deserialize: PartialFunction[(String, JObject), Any] = {
* case ("DateTime", JObject(JField("t", JInt(t)) :: Nil)) => new DateTime(t.longValue)
* }
* }
* implicit val formats = DefaultFormats.withHints(hints)
*
*/
trait TypeHints {
import ClassDelta._
val hints: List[Class[_]]
/** Return hint for given type.
*/
def hintFor(clazz: Class[_]): String
/** Return type for given hint.
*/
def classFor(hint: String): Option[Class[_]]
def containsHint_?(clazz: Class[_]) = hints exists (_ isAssignableFrom clazz)
def deserialize: PartialFunction[(String, JObject), Any] = Map()
def serialize: PartialFunction[Any, JObject] = Map()
def components: List[TypeHints] = List(this)
/**
* Adds the specified type hints to this type hints.
*/
def + (hints: TypeHints): TypeHints = CompositeTypeHints(components ::: hints.components)
private[TypeHints] case class CompositeTypeHints(override val components: List[TypeHints]) extends TypeHints {
val hints: List[Class[_]] = components.flatMap(_.hints)
/**
* Chooses most specific class.
*/
def hintFor(clazz: Class[_]): String = components.filter(_.containsHint_?(clazz))
.map(th => (th.hintFor(clazz), th.classFor(th.hintFor(clazz)).getOrElse(sys.error("hintFor/classFor not invertible for " + th))))
.sortWith((x, y) => (delta(x._2, clazz) - delta(y._2, clazz)) < 0).head._1
def classFor(hint: String): Option[Class[_]] = {
def hasClass(h: TypeHints) =
scala.util.control.Exception.allCatch opt (h.classFor(hint)) map (_.isDefined) getOrElse(false)
components find (hasClass) flatMap (_.classFor(hint))
}
override def deserialize: PartialFunction[(String, JObject), Any] = components.foldLeft[PartialFunction[(String, JObject),Any]](Map()) {
(result, cur) => result.orElse(cur.deserialize)
}
override def serialize: PartialFunction[Any, JObject] = components.foldLeft[PartialFunction[Any, JObject]](Map()) {
(result, cur) => result.orElse(cur.serialize)
}
}
}
private[json] object ClassDelta {
def delta(class1: Class[_], class2: Class[_]): Int = {
if (class1 == class2) 0
else if (class1.getInterfaces.contains(class2)) 0
else if (class2.getInterfaces.contains(class1)) 0
else if (class1.isAssignableFrom(class2)) {
1 + delta(class1, class2.getSuperclass)
}
else if (class2.isAssignableFrom(class1)) {
1 + delta(class1.getSuperclass, class2)
}
else sys.error("Don't call delta unless one class is assignable from the other")
}
}
/** Do not use any type hints.
*/
case object NoTypeHints extends TypeHints {
val hints = Nil
def hintFor(clazz: Class[_]) = sys.error("NoTypeHints does not provide any type hints.")
def classFor(hint: String) = None
}
/** Use short class name as a type hint.
*/
case class ShortTypeHints(hints: List[Class[_]]) extends TypeHints {
def hintFor(clazz: Class[_]) = clazz.getName.substring(clazz.getName.lastIndexOf(".")+1)
def classFor(hint: String) = hints find (hintFor(_) == hint)
}
/** Use full class name as a type hint.
*/
case class FullTypeHints(hints: List[Class[_]]) extends TypeHints {
private val hintsToClass: ConcurrentScalaMap[String, Class[_]] =
new ConcurrentHashMap[String, Class[_]]().asScala ++= hints.map(clazz => hintFor(clazz) -> clazz)
def hintFor(clazz: Class[_]) = clazz.getName
def classFor(hint: String): Option[Class[_]] = {
hintsToClass.get(hint).orElse {
val clazz = Thread.currentThread.getContextClassLoader.loadClass(hint)
hintsToClass.putIfAbsent(hint, clazz).orElse(Some(clazz))
}
}
}
/** Default date format is UTC time.
*/
object DefaultFormats extends DefaultFormats {
val losslessDate = new ThreadLocal(new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"))
val UTC = TimeZone.getTimeZone("UTC")
}
trait DefaultFormats extends Formats {
import java.text.{ParseException, SimpleDateFormat}
val dateFormat = new DateFormat {
def parse(s: String) = try {
Some(formatter.parse(s))
} catch {
case e: ParseException => None
}
def format(d: Date) = formatter.format(d)
private def formatter = {
val f = dateFormatter
f.setTimeZone(DefaultFormats.UTC)
f
}
}
protected def dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
/** Lossless date format includes milliseconds too.
*/
def lossless = new DefaultFormats {
override def dateFormatter = DefaultFormats.losslessDate()
}
/** Default formats with given TypeHint
s.
*/
def withHints(hints: TypeHints) = new DefaultFormats {
override val typeHints = hints
}
}
private[json] class ThreadLocal[A](init: => A) extends java.lang.ThreadLocal[A] with (() => A) {
override def initialValue = init
def apply() = get
}
class CustomSerializer[A: Manifest](
ser: Formats => (PartialFunction[JValue, A], PartialFunction[Any, JValue])) extends Serializer[A] {
val Class = implicitly[Manifest[A]].runtimeClass
def deserialize(implicit format: Formats) = {
case (TypeInfo(Class, _), json) =>
if (ser(format)._1.isDefinedAt(json)) ser(format)._1(json)
else throw new MappingException("Can't convert " + json + " to " + Class)
}
def serialize(implicit format: Formats) = ser(format)._2
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy