From 7c91856f17920625d41267ed4cb323f3adb29b5c Mon Sep 17 00:00:00 2001 From: Joni Freeman Date: Wed, 2 Nov 2011 17:47:44 +0200 Subject: [PATCH] Helper class to create custom JSON serializers --- .../liftweb/json/ext/JodaTimeSerializer.scala | 120 ++++++++---------- core/json/README.md | 26 ++-- .../main/scala/net/liftweb/json/Formats.scala | 14 ++ .../liftweb/json/SerializationExamples.scala | 66 ++++------ 4 files changed, 99 insertions(+), 127 deletions(-) diff --git a/core/json-ext/src/main/scala/net/liftweb/json/ext/JodaTimeSerializer.scala b/core/json-ext/src/main/scala/net/liftweb/json/ext/JodaTimeSerializer.scala index 1b238295bb..d4e61cef68 100644 --- a/core/json-ext/src/main/scala/net/liftweb/json/ext/JodaTimeSerializer.scala +++ b/core/json-ext/src/main/scala/net/liftweb/json/ext/JodaTimeSerializer.scala @@ -27,52 +27,60 @@ object JodaTimeSerializers { LocalTimeSerializer(), PeriodSerializer()) } -object PeriodSerializer { - def apply() = new SimpleTypeSerializer(new JStringType[Period]() { - def targetClass = classOf[Period] - def unwrap(json: JString)(implicit format: Formats) = new Period(json.s) - def wrap(a: Period)(implicit format: Formats) = JString(a.toString) - }) -} - -object DurationSerializer { - def apply() = new SimpleTypeSerializer(new JIntType[Duration]() { - def targetClass = classOf[Duration] - def unwrap(json: JInt)(implicit format: Formats) = new Duration(json.num.longValue) - def wrap(a: Duration)(implicit format: Formats) = JInt(a.getMillis) - }) -} - -object InstantSerializer { - def apply() = new SimpleTypeSerializer(new JIntType[Instant]() { - def targetClass = classOf[Instant] - def unwrap(json: JInt)(implicit format: Formats) = new Instant(json.num.longValue) - def wrap(a: Instant)(implicit format: Formats) = JInt(a.getMillis) - }) -} - -object DateTimeSerializer { - def apply() = new SimpleTypeSerializer(new JStringType[DateTime]() { - def targetClass = classOf[DateTime] - def unwrap(json: JString)(implicit format: Formats) = new DateTime(parse(json)) - def wrap(a: DateTime)(implicit format: Formats) = JString(format.dateFormat.format(a.toDate)) - }) +case class PeriodSerializer extends CustomSerializer[Period](format => ( + { + case JString(p) => new Period(p) + case JNull => null + }, + { + case p: Period => JString(p.toString) + } +)) + +case class DurationSerializer extends CustomSerializer[Duration](format => ( + { + case JInt(d) => new Duration(d.longValue) + case JNull => null + }, + { + case d: Duration => JInt(d.getMillis) + } +)) + +case class InstantSerializer extends CustomSerializer[Instant](format => ( + { + case JInt(i) => new Instant(i.longValue) + case JNull => null + }, + { + case i: Instant => JInt(i.getMillis) + } +)) - private[ext] def parse(json: JString)(implicit format: Formats) = - format.dateFormat.parse(json.s).map(_.getTime).getOrElse { - throw new MappingException("Invalid date format " + json.s) - } +object DateParser { + def parse(s: String, format: Formats) = + format.dateFormat.parse(s).map(_.getTime).getOrElse(throw new MappingException("Invalid date format " + s)) } -object DateMidnightSerializer { - def apply() = new SimpleTypeSerializer(new JStringType[DateMidnight]() { - def targetClass = classOf[DateMidnight] - def unwrap(json: JString)(implicit format: Formats) = - new DateMidnight(DateTimeSerializer.parse(json)) - def wrap(a: DateMidnight)(implicit format: Formats) = - JString(format.dateFormat.format(a.toDate)) - }) -} +case class DateTimeSerializer extends CustomSerializer[DateTime](format => ( + { + case JString(s) => new DateTime(DateParser.parse(s, format)) + case JNull => null + }, + { + case d: DateTime => JString(format.dateFormat.format(d.toDate)) + } +)) + +case class DateMidnightSerializer extends CustomSerializer[DateMidnight](format => ( + { + case JString(s) => new DateMidnight(DateParser.parse(s, format)) + case JNull => null + }, + { + case d: DateMidnight => JString(format.dateFormat.format(d.toDate)) + } +)) private[ext] case class _Interval(start: Long, end: Long) object IntervalSerializer { @@ -101,7 +109,6 @@ object LocalTimeSerializer { }) } -// FIXME consider moving these utilities to lift-json in some form private[ext] trait ClassType[A, B] { def unwrap(b: B)(implicit format: Formats): A def wrap(a: A)(implicit format: Formats): B @@ -122,28 +129,3 @@ case class ClassSerializer[A : Manifest, B : Manifest](t: ClassType[A, B]) exten case a: A if a.asInstanceOf[AnyRef].getClass == Class => Extraction.decompose(t.wrap(a)) } } - -private[ext] trait SimpleType[A, JS <: JValue] { - def targetClass: Class[A] - def unwrap(json: JS)(implicit format: Formats): A - def wrap(a: A)(implicit format: Formats): JS -} - -private[ext] trait JIntType[A] extends SimpleType[A, JInt] -private[ext] trait JStringType[A] extends SimpleType[A, JString] - -private[ext] class SimpleTypeSerializer[A, JS <: JValue](t: SimpleType[A, JS]) extends Serializer[A] { - private val Class = t.targetClass - - def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), A] = { - case (TypeInfo(Class, _), json) => json match { - case JNull => null.asInstanceOf[A] - case json: JS => t.unwrap(json) - case value => throw new MappingException("Can't convert " + value + " to " + Class) - } - } - - def serialize(implicit format: Formats): PartialFunction[Any, JValue] = { - case d: A if d.asInstanceOf[AnyRef].getClass == t.targetClass => t.wrap(d) - } -} diff --git a/core/json/README.md b/core/json/README.md index 73bb5f9a7b..6999e48be5 100644 --- a/core/json/README.md +++ b/core/json/README.md @@ -573,28 +573,22 @@ by providing following serializer. val endTime = end } - scala> class IntervalSerializer extends Serializer[Interval] { - private val IntervalClass = classOf[Interval] - - def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), Interval] = { - case (TypeInfo(IntervalClass, _), json) => json match { - case JObject(JField("start", JInt(s)) :: JField("end", JInt(e)) :: Nil) => - new Interval(s.longValue, e.longValue) - case x => throw new MappingException("Can't convert " + x + " to Interval") - } - } - - def serialize(implicit format: Formats): PartialFunction[Any, JValue] = { + scala> class IntervalSerializer extends CustomSerializer[Interval](format => ( + { + case JObject(JField("start", JInt(s)) :: JField("end", JInt(e)) :: Nil) => + new Interval(s.longValue, e.longValue) + }, + { case x: Interval => JObject(JField("start", JInt(BigInt(x.startTime))) :: - JField("end", JInt(BigInt(x.endTime))) :: Nil) + JField("end", JInt(BigInt(x.endTime))) :: Nil) } - } + )) scala> implicit val formats = Serialization.formats(NoTypeHints) + new IntervalSerializer -Function 'serialize' creates a JSON object to hold serialized data. Function 'deserialize' knows how -to construct serialized object by pattern matching against type info and data. +Custom serializer is created by providing two partial functions. The first evaluates to a value +if it can unpack the data from JSON. The second creates the desired JSON if the type matches. Extensions ---------- diff --git a/core/json/src/main/scala/net/liftweb/json/Formats.scala b/core/json/src/main/scala/net/liftweb/json/Formats.scala index 4c99688494..c484da34cf 100644 --- a/core/json/src/main/scala/net/liftweb/json/Formats.scala +++ b/core/json/src/main/scala/net/liftweb/json/Formats.scala @@ -268,3 +268,17 @@ private[json] class ThreadLocal[A](init: => A) extends java.lang.ThreadLocal[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]].erasure + + 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 +} diff --git a/core/json/src/test/scala/net/liftweb/json/SerializationExamples.scala b/core/json/src/test/scala/net/liftweb/json/SerializationExamples.scala index 45eb5fd5d2..b58b6108e1 100644 --- a/core/json/src/test/scala/net/liftweb/json/SerializationExamples.scala +++ b/core/json/src/test/scala/net/liftweb/json/SerializationExamples.scala @@ -232,54 +232,36 @@ object CustomSerializerExamples extends Specification { import JsonAST._ import java.util.regex.Pattern - class IntervalSerializer extends Serializer[Interval] { - private val IntervalClass = classOf[Interval] - - def deserialize(implicit format: Formats) = { - case (TypeInfo(IntervalClass, _), json) => json match { - case JObject(JField("start", JInt(s)) :: JField("end", JInt(e)) :: Nil) => - new Interval(s.longValue, e.longValue) - case x => throw new MappingException("Can't convert " + x + " to Interval") - } - } - - def serialize(implicit format: Formats) = { + class IntervalSerializer extends CustomSerializer[Interval](format => ( + { + case JObject(JField("start", JInt(s)) :: JField("end", JInt(e)) :: Nil) => + new Interval(s.longValue, e.longValue) + }, + { case x: Interval => JObject(JField("start", JInt(BigInt(x.startTime))) :: - JField("end", JInt(BigInt(x.endTime))) :: Nil) + JField("end", JInt(BigInt(x.endTime))) :: Nil) } - } - - class PatternSerializer extends Serializer[Pattern] { - private val PatternClass = classOf[Pattern] - - def deserialize(implicit format: Formats) = { - case (TypeInfo(PatternClass, _), json) => json match { - case JObject(JField("$pattern", JString(s)) :: Nil) => Pattern.compile(s) - case x => throw new MappingException("Can't convert " + x + " to Pattern") - } + )) + + class PatternSerializer extends CustomSerializer[Pattern](format => ( + { + case JObject(JField("$pattern", JString(s)) :: Nil) => Pattern.compile(s) + }, + { + case x: Pattern => JObject(JField("$pattern", JString(x.pattern)) :: Nil) } - - def serialize(implicit format: Formats) = { - case x: Pattern => JObject(JField("$pattern", JString(x.pattern)) :: Nil) - } - } - - class DateSerializer extends Serializer[Date] { - private val DateClass = classOf[Date] - - def deserialize(implicit format: Formats) = { - case (TypeInfo(DateClass, _), json) => json match { - case JObject(List(JField("$dt", JString(s)))) => - format.dateFormat.parse(s).getOrElse(throw new MappingException("Can't parse "+ s + " to Date")) - case x => throw new MappingException("Can't convert " + x + " to Date") - } - } - - def serialize(implicit format: Formats) = { + )) + + class DateSerializer extends CustomSerializer[Date](format => ( + { + case JObject(List(JField("$dt", JString(s)))) => + format.dateFormat.parse(s).getOrElse(throw new MappingException("Can't parse "+ s + " to Date")) + }, + { case x: Date => JObject(JField("$dt", JString(format.dateFormat.format(x))) :: Nil) } - } + )) class IndexedSeqSerializer extends Serializer[IndexedSeq[_]] { def deserialize(implicit format: Formats) = {