Skip to content
This repository
  • 8 commits
  • 7 files changed
  • 0 comments
  • 1 contributor
Mar 19, 2011
Joni Freeman Add FieldSerializer b1be4cc
Joni Freeman Rename 411fca4
Joni Freeman Rename 94f8aaf
Joni Freeman Add FieldSerializer a3581ee
Joni Freeman Skip constructor args 144d4ea
Mar 20, 2011
Joni Freeman Fix List + Map support e00bbf0
Mar 21, 2011
Joni Freeman Improve search of FieldSerializers: examine the class hierarchy 5d2c87f
Joni Freeman Add docs 0312ed4
25 core/json/README.md
Source Rendered
@@ -513,6 +513,7 @@ Serialization supports:
513 513 * java.util.Date
514 514 * Polymorphic Lists (see below)
515 515 * Recursive types
  516 +* Serialization of fields of a class (see below)
516 517 * Custom serializer functions for types which are not supported (see below)
517 518
518 519 Serializing polymorphic Lists
@@ -535,6 +536,30 @@ will get an extra field named 'jsonClass' (the name can be changed by overriding
535 536 ShortTypeHints outputs short classname for all instances of configured objects. FullTypeHints outputs full
536 537 classname. Other strategies can be implemented by extending TypeHints trait.
537 538
  539 +Serializing fields of a class
  540 +-----------------------------
  541 +
  542 +To enable serialization of fields, a FieldSerializer can be added for some type:
  543 +
  544 + implicit val formats = DefaultFormats + FieldSerializer[WildDog]()
  545 +
  546 +Now the type WildDog (and all subtypes) gets serialized with all its fields (+ constructor parameters).
  547 +FieldSerializer takes two optional parameters which can be used to intercept the field serialization:
  548 +
  549 + case class FieldSerializer[A: Manifest](
  550 + serializer: PartialFunction[(String, Any), Option[(String, Any)]] = Map(),
  551 + deserializer: PartialFunction[JField, JField] = Map()
  552 + )
  553 +
  554 +Those PartialFunctions are called just before a field is serialized or deserialized. Some useful PFs to
  555 +rename and ignore fields are provided:
  556 +
  557 + val dogSerializer = FieldSerializer[WildDog](
  558 + renameTo("name", "animalname") orElse ignore("owner"),
  559 + renameFrom("animalname", "name"))
  560 +
  561 + implicit val formats = DefaultFormats + dogSerializer
  562 +
538 563 Serializing non-supported types
539 564 -------------------------------
540 565
63 core/json/src/main/scala/net/liftweb/json/Extraction.scala
@@ -36,7 +36,7 @@ object Extraction {
36 36 */
37 37 def extract[A](json: JValue)(implicit formats: Formats, mf: Manifest[A]): A =
38 38 try {
39   - extract0(json, mf)
  39 + extract0(json, mf.erasure, mf.typeArguments.map(_.erasure)).asInstanceOf[A]
40 40 } catch {
41 41 case e: MappingException => throw e
42 42 case e: Exception => throw new MappingException("unknown error", e)
@@ -83,7 +83,18 @@ object Extraction {
83 83 f.setAccessible(true)
84 84 JField(unmangleName(name), decompose(f get x))
85 85 } match {
86   - case fields => mkObject(x.getClass, fields)
  86 + case args =>
  87 + val fields = formats.fieldSerializer(x.getClass).map { serializer =>
  88 + Reflection.fields(x.getClass).map {
  89 + case (n, _) =>
  90 + val fieldVal = Reflection.getField(x, n)
  91 + val s = serializer.serializer orElse Map((n, fieldVal) -> Some(n, fieldVal))
  92 + s((n, fieldVal)).map { case (name, value) => JField(name, decompose(value)) }
  93 + .getOrElse(JField(n, JNothing))
  94 + }
  95 + } getOrElse Nil
  96 + val uniqueFields = fields filterNot (f => args.find(_.name == f.name).isDefined)
  97 + mkObject(x.getClass, uniqueFields ++ args)
87 98 }
88 99 }
89 100 } else prependTypeHint(any.getClass, serializer(any))
@@ -165,14 +176,15 @@ object Extraction {
165 176 }
166 177 }
167 178
168   - private def extract0[A](json: JValue, mf: Manifest[A])(implicit formats: Formats): A = {
  179 + private def extract0(json: JValue, clazz: Class[_], typeArgs: Seq[Class[_]])
  180 + (implicit formats: Formats): Any = {
169 181 val mapping =
170   - if (mf.erasure == classOf[List[_]] || mf.erasure == classOf[Set[_]] || mf.erasure == classOf[Array[_]])
171   - Col(mf.erasure, mappingOf(mf.typeArguments(0).erasure))
172   - else if (mf.erasure == classOf[Map[_, _]])
173   - Dict(mappingOf(mf.typeArguments(1).erasure))
174   - else mappingOf(mf.erasure)
175   - extract0(json, mapping).asInstanceOf[A]
  182 + if (clazz == classOf[List[_]] || clazz == classOf[Set[_]] || clazz == classOf[Array[_]])
  183 + Col(clazz, mappingOf(typeArgs(0)))
  184 + else if (clazz == classOf[Map[_, _]])
  185 + Dict(mappingOf(typeArgs(1)))
  186 + else mappingOf(clazz)
  187 + extract0(json, mapping)
176 188 }
177 189
178 190 def extract(json: JValue, target: TypeInfo)(implicit formats: Formats): Any =
@@ -192,13 +204,42 @@ object Extraction {
192 204 }
193 205 }
194 206
  207 + def setFields(a: AnyRef, json: JValue, constructor: JConstructor[_]) = json match {
  208 + case o: JObject =>
  209 + formats.fieldSerializer(a.getClass).map { serializer =>
  210 + val constructorArgNames =
  211 + Reflection.constructorArgs(constructor, formats.parameterNameReader).map(_._1).toSet
  212 + val jsonFields = o.obj.map { f =>
  213 + val JField(n, v) = (serializer.deserializer orElse Map(f -> f))(f)
  214 + (n, (n, v))
  215 + }.toMap
  216 +
  217 + val fieldsToSet =
  218 + Reflection.fields(a.getClass).filterNot(f => constructorArgNames.contains(f._1))
  219 +
  220 + fieldsToSet.foreach { case (name, typeInfo) =>
  221 + jsonFields.get(name).foreach { case (n, v) =>
  222 + val typeArgs = typeInfo.parameterizedType
  223 + .map(_.getActualTypeArguments.map(_.asInstanceOf[Class[_]]).toList)
  224 + val value = extract0(v, typeInfo.clazz, typeArgs.getOrElse(Nil))
  225 + Reflection.setField(a, n, value)
  226 + }
  227 + }
  228 + }
  229 + a
  230 + case _ => a
  231 + }
  232 +
195 233 def instantiate = {
196 234 val c = findBestConstructor
197 235 val jconstructor = c.constructor
198 236 val args = c.args.map(a => build(json \ a.path, a))
199 237 try {
200   - if (jconstructor.getDeclaringClass == classOf[java.lang.Object]) fail("No information known about type")
201   - jconstructor.newInstance(args.map(_.asInstanceOf[AnyRef]).toArray: _*)
  238 + if (jconstructor.getDeclaringClass == classOf[java.lang.Object])
  239 + fail("No information known about type")
  240 +
  241 + val instance = jconstructor.newInstance(args.map(_.asInstanceOf[AnyRef]).toArray: _*)
  242 + setFields(instance.asInstanceOf[AnyRef], json, jconstructor)
202 243 } catch {
203 244 case e @ (_:IllegalArgumentException | _:InstantiationException) =>
204 245 fail("Parsed JSON values do not match with class constructor\nargs=" +
50 core/json/src/main/scala/net/liftweb/json/FieldSerializer.scala
... ... @@ -0,0 +1,50 @@
  1 +/*
  2 + * Copyright 2009-2011 WorldWide Conferencing, LLC
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package net.liftweb
  18 +package json
  19 +
  20 +/**
  21 + * Serializer which serializes all fields of a class too.
  22 + *
  23 + * Serialization can be intercepted by giving two optional PartialFunctions as
  24 + * constructor parameters:
  25 + * <p>
  26 + * <pre>
  27 + * FieldSerializer[WildDog](
  28 + * renameTo("name", "animalname") orElse ignore("owner"),
  29 + * renameFrom("animalname", "name")
  30 + * )
  31 + * </pre>
  32 + */
  33 +case class FieldSerializer[A: Manifest](
  34 + serializer: PartialFunction[(String, Any), Option[(String, Any)]] = Map(),
  35 + deserializer: PartialFunction[JField, JField] = Map()
  36 +)
  37 +
  38 +object FieldSerializer {
  39 + def renameFrom(name: String, newName: String): PartialFunction[JField, JField] = {
  40 + case JField(`name`, x) => JField(newName, x)
  41 + }
  42 +
  43 + def ignore(name: String): PartialFunction[(String, Any), Option[(String, Any)]] = {
  44 + case (`name`, _) => None
  45 + }
  46 +
  47 + def renameTo(name: String, newName: String): PartialFunction[(String, Any), Option[(String, Any)]] = {
  48 + case (`name`, x) => Some(newName, x)
  49 + }
  50 +}
53 core/json/src/main/scala/net/liftweb/json/Formats.scala
@@ -29,6 +29,7 @@ trait Formats { self: Formats =>
29 29 val dateFormat: DateFormat
30 30 val typeHints: TypeHints = NoTypeHints
31 31 val customSerializers: List[Serializer[_]] = Nil
  32 + val fieldSerializers: List[(Class[_], FieldSerializer[_])] = Nil
32 33
33 34 /**
34 35 * The name of the field in JSON where type hints are added (jsonClass by default)
@@ -49,6 +50,7 @@ trait Formats { self: Formats =>
49 50 override val parameterNameReader = self.parameterNameReader
50 51 override val typeHints = self.typeHints + extraHints
51 52 override val customSerializers = self.customSerializers
  53 + override val fieldSerializers = self.fieldSerializers
52 54 }
53 55
54 56 /**
@@ -60,6 +62,7 @@ trait Formats { self: Formats =>
60 62 override val parameterNameReader = self.parameterNameReader
61 63 override val typeHints = self.typeHints
62 64 override val customSerializers = newSerializer :: self.customSerializers
  65 + override val fieldSerializers = self.fieldSerializers
63 66 }
64 67
65 68 /**
@@ -68,6 +71,28 @@ trait Formats { self: Formats =>
68 71 def ++ (newSerializers: Traversable[Serializer[_]]): Formats =
69 72 newSerializers.foldLeft(this)(_ + _)
70 73
  74 + /**
  75 + * Adds a field serializer for a given type to this formats.
  76 + */
  77 + def + [A](newSerializer: FieldSerializer[A])(implicit mf: Manifest[A]): Formats = new Formats {
  78 + val dateFormat = Formats.this.dateFormat
  79 + override val typeHintFieldName = self.typeHintFieldName
  80 + override val parameterNameReader = self.parameterNameReader
  81 + override val typeHints = self.typeHints
  82 + override val customSerializers = self.customSerializers
  83 + override val fieldSerializers = (mf.erasure, newSerializer) :: self.fieldSerializers
  84 + }
  85 +
  86 + private[json] def fieldSerializer(clazz: Class[_]): Option[FieldSerializer[_]] = {
  87 + import ClassDelta._
  88 +
  89 + val ord = Ordering[Int].on[(Class[_], FieldSerializer[_])](x => delta(x._1, clazz))
  90 + fieldSerializers filter (_._1.isAssignableFrom(clazz)) match {
  91 + case Nil => None
  92 + case xs => Some((xs min ord)._2)
  93 + }
  94 + }
  95 +
71 96 def customSerializer(implicit format: Formats) =
72 97 customSerializers.foldLeft(Map(): PartialFunction[Any, JValue]) { (acc, x) =>
73 98 acc.orElse(x.serialize)
@@ -111,6 +136,8 @@ trait Serializer[A] {
111 136 * </pre>
112 137 */
113 138 trait TypeHints {
  139 + import ClassDelta._
  140 +
114 141 val hints: List[Class[_]]
115 142
116 143 /** Return hint for given type.
@@ -151,19 +178,21 @@ trait TypeHints {
151 178 override def serialize: PartialFunction[Any, JObject] = components.foldLeft[PartialFunction[Any, JObject]](Map()) {
152 179 (result, cur) => result.orElse(cur.serialize)
153 180 }
154   -
155   - private def delta(class1: Class[_], class2: Class[_]): Int = {
156   - if (class1 == class2) 0
157   - else if (class1.getInterfaces.contains(class2)) 0
158   - else if (class2.getInterfaces.contains(class1)) 0
159   - else if (class1.isAssignableFrom(class2)) {
160   - 1 + delta(class1, class2.getSuperclass)
161   - }
162   - else if (class2.isAssignableFrom(class1)) {
163   - 1 + delta(class1.getSuperclass, class2)
164   - }
165   - else error("Don't call delta unless one class is assignable from the other")
  181 + }
  182 +}
  183 +
  184 +private[json] object ClassDelta {
  185 + def delta(class1: Class[_], class2: Class[_]): Int = {
  186 + if (class1 == class2) 0
  187 + else if (class1.getInterfaces.contains(class2)) 0
  188 + else if (class2.getInterfaces.contains(class1)) 0
  189 + else if (class1.isAssignableFrom(class2)) {
  190 + 1 + delta(class1, class2.getSuperclass)
  191 + }
  192 + else if (class2.isAssignableFrom(class1)) {
  193 + 1 + delta(class1.getSuperclass, class2)
166 194 }
  195 + else error("Don't call delta unless one class is assignable from the other")
167 196 }
168 197 }
169 198
2  core/json/src/main/scala/net/liftweb/json/JsonAST.scala
@@ -337,6 +337,8 @@ object JsonAST {
337 337 case o: JObject => Set(obj.toArray: _*) == Set(o.obj.toArray: _*)
338 338 case _ => false
339 339 }
  340 +
  341 + def field(name: String): Option[JField] = obj.find(_.name == name)
340 342 }
341 343 case class JArray(arr: List[JValue]) extends JValue {
342 344 type Values = List[Any]
29 core/json/src/main/scala/net/liftweb/json/Meta.scala
@@ -251,6 +251,35 @@ private[json] object Meta {
251 251
252 252 def array_?(x: Any) = x != null && classOf[scala.Array[_]].isAssignableFrom(x.asInstanceOf[AnyRef].getClass)
253 253
  254 + def fields(clazz: Class[_]): List[(String, TypeInfo)] = {
  255 + val fs = clazz.getDeclaredFields.toList
  256 + .map(f => (f.getName, TypeInfo(f.getType, f.getGenericType match {
  257 + case p: ParameterizedType => Some(p)
  258 + case _ => None
  259 + })))
  260 + fs ::: (if (clazz.getSuperclass == null) Nil else fields(clazz.getSuperclass))
  261 + }
  262 +
  263 + def setField(a: AnyRef, name: String, value: Any) = {
  264 + val f = findField(a.getClass, name)
  265 + f.setAccessible(true)
  266 + f.set(a, value)
  267 + }
  268 +
  269 + def getField(a: AnyRef, name: String) = {
  270 + val f = findField(a.getClass, name)
  271 + f.setAccessible(true)
  272 + f.get(a)
  273 + }
  274 +
  275 + def findField(clazz: Class[_], name: String): Field = try {
  276 + clazz.getDeclaredField(name)
  277 + } catch {
  278 + case e: NoSuchFieldException =>
  279 + if (clazz.getSuperclass == null) throw e
  280 + else findField(clazz.getSuperclass, name)
  281 + }
  282 +
254 283 def mkJavaArray(x: Any, componentType: Class[_]) = {
255 284 val arr = x.asInstanceOf[scala.Array[_]]
256 285 val a = java.lang.reflect.Array.newInstance(componentType, arr.size)
81 core/json/src/test/scala/net/liftweb/json/FieldSerializerExamples.scala
... ... @@ -0,0 +1,81 @@
  1 +/*
  2 + * Copyright 2009-2011 WorldWide Conferencing, LLC
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package net.liftweb
  18 +package json
  19 +
  20 +import org.specs.Specification
  21 +
  22 +object FieldSerializerExamples extends Specification {
  23 + import Serialization.{read, write => swrite}
  24 + import FieldSerializer._
  25 +
  26 + val dog = new WildDog("black")
  27 + dog.name = "pluto"
  28 + dog.owner = Owner("joe", 35)
  29 +
  30 + val cat = new WildCat(100)
  31 + cat.name = "tommy"
  32 +
  33 + "All fields are serialized by default" in {
  34 + implicit val formats = DefaultFormats + FieldSerializer[WildDog]()
  35 + val ser = swrite(dog)
  36 + val dog2 = read[WildDog](ser)
  37 + dog2.name mustEqual dog.name
  38 + dog2.color mustEqual dog.color
  39 + dog2.owner mustEqual dog.owner
  40 + dog2.size mustEqual dog.size
  41 + }
  42 +
  43 + "Fields can be ignored and renamed" in {
  44 + val dogSerializer = FieldSerializer[WildDog](
  45 + renameTo("name", "animalname") orElse ignore("owner"),
  46 + renameFrom("animalname", "name")
  47 + )
  48 +
  49 + implicit val formats = DefaultFormats + dogSerializer
  50 +
  51 + val ser = swrite(dog)
  52 + val dog2 = read[WildDog](ser)
  53 + dog2.name mustEqual dog.name
  54 + dog2.color mustEqual dog.color
  55 + dog2.owner must beNull
  56 + dog2.size mustEqual dog.size
  57 + (parse(ser) \ "animalname") mustEqual JString("pluto")
  58 + }
  59 +
  60 + "Selects best matching serializer" in {
  61 + val dogSerializer = FieldSerializer[WildDog](ignore("name"))
  62 + implicit val formats = DefaultFormats + FieldSerializer[AnyRef]() + dogSerializer
  63 +
  64 + val dog2 = read[WildDog](swrite(dog))
  65 + val cat2 = read[WildCat](swrite(cat))
  66 +
  67 + dog2.name mustEqual ""
  68 + cat2.name mustEqual "tommy"
  69 + }
  70 +}
  71 +
  72 +abstract class Mammal {
  73 + var name: String = ""
  74 + var owner: Owner = null
  75 + val size = List(10, 15)
  76 +}
  77 +
  78 +class WildDog(val color: String) extends Mammal
  79 +class WildCat(val cuteness: Int) extends Mammal
  80 +
  81 +case class Owner(name: String, age: Int)

No commit comments for this range

Something went wrong with that request. Please try again.