Skip to content

Commit

Permalink
Helper class to create custom JSON serializers
Browse files Browse the repository at this point in the history
  • Loading branch information
Joni Freeman committed Nov 2, 2011
1 parent 28c0dea commit 7c91856
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 127 deletions.
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -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)
}
}
26 changes: 10 additions & 16 deletions core/json/README.md
Expand Up @@ -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
----------
Expand Down
14 changes: 14 additions & 0 deletions core/json/src/main/scala/net/liftweb/json/Formats.scala
Expand Up @@ -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
}
Expand Up @@ -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) = {
Expand Down

0 comments on commit 7c91856

Please sign in to comment.