From 211e72c22cf620c12835738b2a62c50867331fa6 Mon Sep 17 00:00:00 2001 From: Steffen Fritzsche Date: Tue, 11 Oct 2011 18:18:26 +0200 Subject: Implemented #8: Conversion of Double.NaN and Infinity to JsNull --- src/test/scala/cc/spray/json/BasicFormatsSpec.scala | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) (limited to 'src/test/scala/cc/spray') diff --git a/src/test/scala/cc/spray/json/BasicFormatsSpec.scala b/src/test/scala/cc/spray/json/BasicFormatsSpec.scala index fcae4bb..7f74e37 100644 --- a/src/test/scala/cc/spray/json/BasicFormatsSpec.scala +++ b/src/test/scala/cc/spray/json/BasicFormatsSpec.scala @@ -26,6 +26,15 @@ class BasicFormatsSpec extends Specification with DefaultJsonProtocol { "convert a Float to a JsNumber" in { 4.2f.toJson mustEqual JsNumber(4.2f) } + "convert a Float.NaN to a JsNull" in { + Float.NaN.toJson mustEqual JsNull + } + "convert a Float.PositiveInfinity to a JsNull" in { + Float.PositiveInfinity.toJson mustEqual JsNull + } + "convert a Float.NegativeInfinity to a JsNull" in { + Float.NegativeInfinity.toJson mustEqual JsNull + } "convert a JsNumber to a Float" in { JsNumber(4.2f).fromJson[Float] mustEqual 4.2f } @@ -35,6 +44,15 @@ class BasicFormatsSpec extends Specification with DefaultJsonProtocol { "convert a Double to a JsNumber" in { 4.2.toJson mustEqual JsNumber(4.2) } + "convert a Double.NaN to a JsNull" in { + Double.NaN.toJson mustEqual JsNull + } + "convert a Double.PositiveInfinity to a JsNull" in { + Double.PositiveInfinity.toJson mustEqual JsNull + } + "convert a Double.NegativeInfinity to a JsNull" in { + Double.NegativeInfinity.toJson mustEqual JsNull + } "convert a JsNumber to a Double" in { JsNumber(4.2).fromJson[Double] mustEqual 4.2 } @@ -119,4 +137,4 @@ class BasicFormatsSpec extends Specification with DefaultJsonProtocol { } } -} \ No newline at end of file +} -- cgit v1.2.3 From fe8fca4e69a21b5a795a000d4bd27026a06aa6a0 Mon Sep 17 00:00:00 2001 From: Steffen Fritzsche Date: Tue, 11 Oct 2011 18:33:59 +0200 Subject: Implemented deserialization support (Issue #8) --- src/main/scala/cc/spray/json/BasicFormats.scala | 4 +++- src/test/scala/cc/spray/json/BasicFormatsSpec.scala | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) (limited to 'src/test/scala/cc/spray') diff --git a/src/main/scala/cc/spray/json/BasicFormats.scala b/src/main/scala/cc/spray/json/BasicFormats.scala index e16b4ca..32ae91e 100644 --- a/src/main/scala/cc/spray/json/BasicFormats.scala +++ b/src/main/scala/cc/spray/json/BasicFormats.scala @@ -42,6 +42,7 @@ trait BasicFormats { def write(x: Float) = JsNumber(x) def read(value: JsValue) = value match { case JsNumber(x) => x.floatValue + case JsNull => Float.NaN case _ => throw new DeserializationException("Float expected") } } @@ -50,6 +51,7 @@ trait BasicFormats { def write(x: Double) = JsNumber(x) def read(value: JsValue) = value match { case JsNumber(x) => x.doubleValue + case JsNull => Double.NaN case _ => throw new DeserializationException("Double expected") } } @@ -124,4 +126,4 @@ trait BasicFormats { } } -} \ No newline at end of file +} diff --git a/src/test/scala/cc/spray/json/BasicFormatsSpec.scala b/src/test/scala/cc/spray/json/BasicFormatsSpec.scala index 7f74e37..11576a4 100644 --- a/src/test/scala/cc/spray/json/BasicFormatsSpec.scala +++ b/src/test/scala/cc/spray/json/BasicFormatsSpec.scala @@ -38,6 +38,9 @@ class BasicFormatsSpec extends Specification with DefaultJsonProtocol { "convert a JsNumber to a Float" in { JsNumber(4.2f).fromJson[Float] mustEqual 4.2f } + "convert a JsNull to a Float" in { + JsNull.fromJson[Float].isNaN mustEqual Float.NaN.isNaN + } } "The DoubleJsonFormat" should { @@ -56,6 +59,9 @@ class BasicFormatsSpec extends Specification with DefaultJsonProtocol { "convert a JsNumber to a Double" in { JsNumber(4.2).fromJson[Double] mustEqual 4.2 } + "convert a JsNull to a Double" in { + JsNull.fromJson[Double].isNaN mustEqual Double.NaN.isNaN + } } "The ByteJsonFormat" should { -- cgit v1.2.3 From 6cbaa4c1782bcb4060af9f8ef2fd72e737f7982a Mon Sep 17 00:00:00 2001 From: Mathias Date: Wed, 9 Nov 2011 11:42:25 +0100 Subject: Fix #6 (rename JsValue:fromJson to 'convertTo', add .prettyPrint and .compactPrint --- README.markdown | 10 ++++---- .../scala/cc/spray/json/AdditionalFormats.scala | 2 +- .../scala/cc/spray/json/CollectionFormats.scala | 8 +++---- src/main/scala/cc/spray/json/JsValue.scala | 9 +++++-- src/main/scala/cc/spray/json/StandardFormats.scala | 18 +++++++------- .../cc/spray/json/AdditionalFormatsSpec.scala | 2 +- .../scala/cc/spray/json/BasicFormatsSpec.scala | 28 +++++++++++----------- .../cc/spray/json/CollectionFormatsSpec.scala | 10 ++++---- .../scala/cc/spray/json/ProductFormatsSpec.scala | 14 +++++------ src/test/scala/cc/spray/json/ReadmeSpec.scala | 4 ++-- .../scala/cc/spray/json/StandardFormatsSpec.scala | 12 +++++----- 11 files changed, 61 insertions(+), 56 deletions(-) (limited to 'src/test/scala/cc/spray') diff --git a/README.markdown b/README.markdown index fcf4ba9..3550dd1 100644 --- a/README.markdown +++ b/README.markdown @@ -37,15 +37,15 @@ and do one or more of the following: 2. Print a JSON AST back to a String using either the `CompactPrinter` or the `PrettyPrinter` - val json = PrettyPrinter(jsonAst) + val json = jsonAst.prettyPrint // or .compactPrint 3. Convert any Scala object to a JSON AST using the pimped `toJson` method val jsonAst = List(1, 2, 3).toJson -4. Convert a JSON AST to a Scala object with the `fromJson` method +4. Convert a JSON AST to a Scala object with the `convertTo` method - val myObject = jsonAst.fromJson[MyObjectType] + val myObject = jsonAst.convertTo[MyObjectType] In order to make steps 3 and 4 work for an object of type `T` you need to bring implicit values in scope that provide `JsonFormat[T]` instances for `T` and all types used by `T` (directly or indirectly). @@ -90,7 +90,7 @@ If your custom type `T` is a case class then augmenting the `DefaultJsonProtocol import MyJsonProtocol._ val json = Color("CadetBlue", 95, 158, 160).toJson - val color = json.fromJson[Color] + val color = json.convertTo[Color] The `jsonFormat` method reduces the boilerplate to a minimum, just pass it the companion object of your case class as well as the field names (in order) and it will return a ready-to-use `JsonFormat` for your type. @@ -139,7 +139,7 @@ Here is one way to do it: import MyJsonProtocol._ val json = Color("CadetBlue", 95, 158, 160).toJson - val color = json.fromJson[Color] + val color = json.convertTo[Color] This serializes `Color` instances as a JSON array, which is compact but does not make the elements semantics explicit. You need to know that the color components are ordered "red, green, blue". diff --git a/src/main/scala/cc/spray/json/AdditionalFormats.scala b/src/main/scala/cc/spray/json/AdditionalFormats.scala index db9d3ba..dc78115 100644 --- a/src/main/scala/cc/spray/json/AdditionalFormats.scala +++ b/src/main/scala/cc/spray/json/AdditionalFormats.scala @@ -68,7 +68,7 @@ trait AdditionalFormats { def safeReader[A :JsonReader] = new JsonReader[Either[Exception, A]] { def read(json: JsValue) = { try { - Right(json.fromJson) + Right(json.convertTo) } catch { case e: Exception => Left(e) } diff --git a/src/main/scala/cc/spray/json/CollectionFormats.scala b/src/main/scala/cc/spray/json/CollectionFormats.scala index 07592a8..bcfa198 100644 --- a/src/main/scala/cc/spray/json/CollectionFormats.scala +++ b/src/main/scala/cc/spray/json/CollectionFormats.scala @@ -25,7 +25,7 @@ trait CollectionFormats { implicit def listFormat[T :JsonFormat] = new JsonFormat[List[T]] { def write(list: List[T]) = JsArray(list.map(_.toJson)) def read(value: JsValue) = value match { - case JsArray(elements) => elements.map(_.fromJson[T]) + case JsArray(elements) => elements.map(_.convertTo[T]) case _ => throw new DeserializationException("List expected") } } @@ -36,7 +36,7 @@ trait CollectionFormats { implicit def arrayFormat[T :JsonFormat :ClassManifest] = new JsonFormat[Array[T]] { def write(array: Array[T]) = JsArray(array.map(_.toJson).toList) def read(value: JsValue) = value match { - case JsArray(elements) => elements.map(_.fromJson[T]).toArray[T] + case JsArray(elements) => elements.map(_.convertTo[T]).toArray[T] case _ => throw new DeserializationException("Array expected") } } @@ -55,7 +55,7 @@ trait CollectionFormats { } } def read(value: JsValue) = value match { - case JsObject(fields) => fields.map(field => (JsString(field.name).fromJson[K], field.value.fromJson[V])).toMap + case JsObject(fields) => fields.map(field => (JsString(field.name).convertTo[K], field.value.convertTo[V])).toMap case _ => throw new DeserializationException("Map expected") } } @@ -84,7 +84,7 @@ trait CollectionFormats { def viaList[I <: Iterable[T], T :JsonFormat](f: List[T] => I): JsonFormat[I] = new JsonFormat[I] { def write(iterable: I) = JsArray(iterable.map(_.toJson).toList) def read(value: JsValue) = value match { - case JsArray(elements) => f(elements.map(_.fromJson[T])) + case JsArray(elements) => f(elements.map(_.convertTo[T])) case _ => throw new DeserializationException("Collection expected") } } diff --git a/src/main/scala/cc/spray/json/JsValue.scala b/src/main/scala/cc/spray/json/JsValue.scala index e4ab734..86d4101 100644 --- a/src/main/scala/cc/spray/json/JsValue.scala +++ b/src/main/scala/cc/spray/json/JsValue.scala @@ -24,9 +24,14 @@ import collection.mutable.ListBuffer * The general type of a JSON AST node. */ sealed trait JsValue { - override def toString = CompactPrinter(this) + override def toString = compactPrint def toString(printer: (JsValue => String)) = printer(this) - def fromJson[T :JsonReader]: T = jsonReader[T].read(this) + def compactPrint = CompactPrinter(this) + def prettyPrint = PrettyPrinter(this) + def convertTo[T :JsonReader]: T = jsonReader[T].read(this) + + @deprecated("Superceded by 'convertTo'", "1.1.0") + def fromJson[T :JsonReader]: T = convertTo } object JsValue { diff --git a/src/main/scala/cc/spray/json/StandardFormats.scala b/src/main/scala/cc/spray/json/StandardFormats.scala index 1c44716..56fb658 100644 --- a/src/main/scala/cc/spray/json/StandardFormats.scala +++ b/src/main/scala/cc/spray/json/StandardFormats.scala @@ -36,7 +36,7 @@ trait StandardFormats { } def read(value: JsValue) = value match { case JsNull => None - case x => Some(x.fromJson[T]) + case x => Some(x.convertTo[T]) } } @@ -45,7 +45,7 @@ trait StandardFormats { case Right(a) => a.toJson case Left(b) => b.toJson } - def read(value: JsValue) = (value.fromJson(safeReader[A]), value.fromJson(safeReader[B])) match { + def read(value: JsValue) = (value.convertTo(safeReader[A]), value.convertTo(safeReader[B])) match { case (Right(a), _: Left[_, _]) => Left(a) case (_: Left[_, _], Right(b)) => Right(b) case (_: Right[_, _], _: Right[_, _]) => throw new DeserializationException("Ambiguous Either value: can be read as both, Left and Right, values") @@ -55,13 +55,13 @@ trait StandardFormats { implicit def tuple1Format[A :JF] = new JF[Tuple1[A]] { def write(t: Tuple1[A]) = t._1.toJson - def read(value: JsValue) = Tuple1(value.fromJson[A]) + def read(value: JsValue) = Tuple1(value.convertTo[A]) } implicit def tuple2Format[A :JF, B :JF] = new JF[(A, B)] { def write(t: (A, B)) = JsArray(t._1.toJson, t._2.toJson) def read(value: JsValue) = value match { - case JsArray(a :: b :: Nil) => (a.fromJson[A], b.fromJson[B]) + case JsArray(a :: b :: Nil) => (a.convertTo[A], b.convertTo[B]) case _ => throw new DeserializationException("Tuple2 expected") } } @@ -69,7 +69,7 @@ trait StandardFormats { implicit def tuple3Format[A :JF, B :JF, C :JF] = new JF[(A, B, C)] { def write(t: (A, B, C)) = JsArray(t._1.toJson, t._2.toJson, t._3.toJson) def read(value: JsValue) = value match { - case JsArray(a :: b :: c :: Nil) => (a.fromJson[A], b.fromJson[B], c.fromJson[C]) + case JsArray(a :: b :: c :: Nil) => (a.convertTo[A], b.convertTo[B], c.convertTo[C]) case _ => throw new DeserializationException("Tuple3 expected") } } @@ -77,7 +77,7 @@ trait StandardFormats { implicit def tuple4Format[A :JF, B :JF, C :JF, D :JF] = new JF[(A, B, C, D)] { def write(t: (A, B, C, D)) = JsArray(t._1.toJson, t._2.toJson, t._3.toJson, t._4.toJson) def read(value: JsValue) = value match { - case JsArray(a :: b :: c :: d :: Nil) => (a.fromJson[A], b.fromJson[B], c.fromJson[C], d.fromJson[D]) + case JsArray(a :: b :: c :: d :: Nil) => (a.convertTo[A], b.convertTo[B], c.convertTo[C], d.convertTo[D]) case _ => throw new DeserializationException("Tuple4 expected") } } @@ -87,7 +87,7 @@ trait StandardFormats { def write(t: (A, B, C, D, E)) = JsArray(t._1.toJson, t._2.toJson, t._3.toJson, t._4.toJson, t._5.toJson) def read(value: JsValue) = value match { case JsArray(a :: b :: c :: d :: e :: Nil) => { - (a.fromJson[A], b.fromJson[B], c.fromJson[C], d.fromJson[D], e.fromJson[E]) + (a.convertTo[A], b.convertTo[B], c.convertTo[C], d.convertTo[D], e.convertTo[E]) } case _ => throw new DeserializationException("Tuple5 expected") } @@ -99,7 +99,7 @@ trait StandardFormats { def write(t: (A, B, C, D, E, F)) = JsArray(t._1.toJson, t._2.toJson, t._3.toJson, t._4.toJson, t._5.toJson, t._6.toJson) def read(value: JsValue) = value match { case JsArray(a :: b :: c :: d :: e :: f :: Nil) => { - (a.fromJson[A], b.fromJson[B], c.fromJson[C], d.fromJson[D], e.fromJson[E], f.fromJson[F]) + (a.convertTo[A], b.convertTo[B], c.convertTo[C], d.convertTo[D], e.convertTo[E], f.convertTo[F]) } case _ => throw new DeserializationException("Tuple6 expected") } @@ -111,7 +111,7 @@ trait StandardFormats { def write(t: (A, B, C, D, E, F, G)) = JsArray(t._1.toJson, t._2.toJson, t._3.toJson, t._4.toJson, t._5.toJson, t._6.toJson, t._6.toJson) def read(value: JsValue) = value match { case JsArray(a :: b :: c :: d :: e :: f :: g :: Nil) => { - (a.fromJson[A], b.fromJson[B], c.fromJson[C], d.fromJson[D], e.fromJson[E], f.fromJson[F], g.fromJson[G]) + (a.convertTo[A], b.convertTo[B], c.convertTo[C], d.convertTo[D], e.convertTo[E], f.convertTo[F], g.convertTo[G]) } case _ => throw new DeserializationException("Tuple7 expected") } diff --git a/src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala b/src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala index 7368128..0db3d5a 100644 --- a/src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala +++ b/src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala @@ -35,7 +35,7 @@ class AdditionalFormatsSpec extends Specification { "properly read a Container[Container[List[Int]]] from JSON" in { import ReaderProtocol._ - JsonParser("""{"content":{"content":[1,2,3]}}""").fromJson[Container[Container[List[Int]]]] mustEqual obj + JsonParser("""{"content":{"content":[1,2,3]}}""").convertTo[Container[Container[List[Int]]]] mustEqual obj } } } \ No newline at end of file diff --git a/src/test/scala/cc/spray/json/BasicFormatsSpec.scala b/src/test/scala/cc/spray/json/BasicFormatsSpec.scala index 11576a4..6a8ecd3 100644 --- a/src/test/scala/cc/spray/json/BasicFormatsSpec.scala +++ b/src/test/scala/cc/spray/json/BasicFormatsSpec.scala @@ -9,7 +9,7 @@ class BasicFormatsSpec extends Specification with DefaultJsonProtocol { 42.toJson mustEqual JsNumber(42) } "convert a JsNumber to an Int" in { - JsNumber(42).fromJson[Int] mustEqual 42 + JsNumber(42).convertTo[Int] mustEqual 42 } } @@ -18,7 +18,7 @@ class BasicFormatsSpec extends Specification with DefaultJsonProtocol { 42L.toJson mustEqual JsNumber(42L) } "convert a JsNumber to a Long" in { - JsNumber(42L).fromJson[Long] mustEqual 42L + JsNumber(42L).convertTo[Long] mustEqual 42L } } @@ -36,7 +36,7 @@ class BasicFormatsSpec extends Specification with DefaultJsonProtocol { Float.NegativeInfinity.toJson mustEqual JsNull } "convert a JsNumber to a Float" in { - JsNumber(4.2f).fromJson[Float] mustEqual 4.2f + JsNumber(4.2f).convertTo[Float] mustEqual 4.2f } "convert a JsNull to a Float" in { JsNull.fromJson[Float].isNaN mustEqual Float.NaN.isNaN @@ -57,7 +57,7 @@ class BasicFormatsSpec extends Specification with DefaultJsonProtocol { Double.NegativeInfinity.toJson mustEqual JsNull } "convert a JsNumber to a Double" in { - JsNumber(4.2).fromJson[Double] mustEqual 4.2 + JsNumber(4.2).convertTo[Double] mustEqual 4.2 } "convert a JsNull to a Double" in { JsNull.fromJson[Double].isNaN mustEqual Double.NaN.isNaN @@ -69,7 +69,7 @@ class BasicFormatsSpec extends Specification with DefaultJsonProtocol { 42.asInstanceOf[Byte].toJson mustEqual JsNumber(42) } "convert a JsNumber to a Byte" in { - JsNumber(42).fromJson[Byte] mustEqual 42 + JsNumber(42).convertTo[Byte] mustEqual 42 } } @@ -78,7 +78,7 @@ class BasicFormatsSpec extends Specification with DefaultJsonProtocol { 42.asInstanceOf[Short].toJson mustEqual JsNumber(42) } "convert a JsNumber to a Short" in { - JsNumber(42).fromJson[Short] mustEqual 42 + JsNumber(42).convertTo[Short] mustEqual 42 } } @@ -87,7 +87,7 @@ class BasicFormatsSpec extends Specification with DefaultJsonProtocol { BigDecimal(42).toJson mustEqual JsNumber(42) } "convert a JsNumber to a BigDecimal" in { - JsNumber(42).fromJson[BigDecimal] mustEqual BigDecimal(42) + JsNumber(42).convertTo[BigDecimal] mustEqual BigDecimal(42) } } @@ -96,7 +96,7 @@ class BasicFormatsSpec extends Specification with DefaultJsonProtocol { BigInt(42).toJson mustEqual JsNumber(42) } "convert a JsNumber to a BigInt" in { - JsNumber(42).fromJson[BigInt] mustEqual BigInt(42) + JsNumber(42).convertTo[BigInt] mustEqual BigInt(42) } } @@ -105,15 +105,15 @@ class BasicFormatsSpec extends Specification with DefaultJsonProtocol { ().toJson mustEqual JsNumber(1) } "convert a JsNumber to Unit" in { - JsNumber(1).fromJson[Unit] mustEqual () + JsNumber(1).convertTo[Unit] mustEqual () } } "The BooleanJsonFormat" should { "convert true to a JsTrue" in { true.toJson mustEqual JsTrue } "convert false to a JsFalse" in { false.toJson mustEqual JsFalse } - "convert a JsTrue to true" in { JsTrue.fromJson[Boolean] mustEqual true } - "convert a JsFalse to false" in { JsFalse.fromJson[Boolean] mustEqual false } + "convert a JsTrue to true" in { JsTrue.convertTo[Boolean] mustEqual true } + "convert a JsFalse to false" in { JsFalse.convertTo[Boolean] mustEqual false } } "The CharJsonFormat" should { @@ -121,7 +121,7 @@ class BasicFormatsSpec extends Specification with DefaultJsonProtocol { 'c'.toJson mustEqual JsString("c") } "convert a JsString to a Char" in { - JsString("c").fromJson[Char] mustEqual 'c' + JsString("c").convertTo[Char] mustEqual 'c' } } @@ -130,7 +130,7 @@ class BasicFormatsSpec extends Specification with DefaultJsonProtocol { "Hello".toJson mustEqual JsString("Hello") } "convert a JsString to a String" in { - JsString("Hello").fromJson[String] mustEqual "Hello" + JsString("Hello").convertTo[String] mustEqual "Hello" } } @@ -139,7 +139,7 @@ class BasicFormatsSpec extends Specification with DefaultJsonProtocol { 'Hello.toJson mustEqual JsString("Hello") } "convert a JsString to a Symbol" in { - JsString("Hello").fromJson[Symbol] mustEqual 'Hello + JsString("Hello").convertTo[Symbol] mustEqual 'Hello } } diff --git a/src/test/scala/cc/spray/json/CollectionFormatsSpec.scala b/src/test/scala/cc/spray/json/CollectionFormatsSpec.scala index d0cb509..009d2e2 100644 --- a/src/test/scala/cc/spray/json/CollectionFormatsSpec.scala +++ b/src/test/scala/cc/spray/json/CollectionFormatsSpec.scala @@ -12,7 +12,7 @@ class CollectionFormatsSpec extends Specification with DefaultJsonProtocol { list.toJson mustEqual json } "convert a JsArray of JsNumbers to a List[Int]" in { - json.fromJson[List[Int]] mustEqual list + json.convertTo[List[Int]] mustEqual list } } @@ -23,7 +23,7 @@ class CollectionFormatsSpec extends Specification with DefaultJsonProtocol { array.toJson mustEqual json } "convert a JsArray of JsNumbers to an Array[Int]" in { - Arrays.equals(json.fromJson[Array[Int]], array) must beTrue + Arrays.equals(json.convertTo[Array[Int]], array) must beTrue } } @@ -34,7 +34,7 @@ class CollectionFormatsSpec extends Specification with DefaultJsonProtocol { map.toJson mustEqual json } "be able to convert a JsObject to a Map[String, Long]" in { - json.fromJson[Map[String, Long]] mustEqual map + json.convertTo[Map[String, Long]] mustEqual map } "throw an Exception when trying to serialize a map whose key are not serialized to JsStrings" in { Map(1 -> "a").toJson must throwA(new SerializationException("Map key must be formatted as JsString, not '1'")) @@ -48,7 +48,7 @@ class CollectionFormatsSpec extends Specification with DefaultJsonProtocol { set.toJson mustEqual json } "convert a JsArray of JsNumbers to a Set[Int]" in { - json.fromJson[Set[Int]] mustEqual set + json.convertTo[Set[Int]] mustEqual set } } @@ -59,7 +59,7 @@ class CollectionFormatsSpec extends Specification with DefaultJsonProtocol { seq.toJson mustEqual json } "convert a JsArray of JsNumbers to a IndexedSeq[Int]" in { - json.fromJson[collection.IndexedSeq[Int]] mustEqual seq + json.convertTo[collection.IndexedSeq[Int]] mustEqual seq } } diff --git a/src/test/scala/cc/spray/json/ProductFormatsSpec.scala b/src/test/scala/cc/spray/json/ProductFormatsSpec.scala index 9a692ec..7c643e9 100644 --- a/src/test/scala/cc/spray/json/ProductFormatsSpec.scala +++ b/src/test/scala/cc/spray/json/ProductFormatsSpec.scala @@ -23,26 +23,26 @@ class ProductFormatsSpec extends Specification { obj.toJson mustEqual json } "convert a JsObject to the respective case class instance" in { - json.fromJson[Test2] mustEqual obj + json.convertTo[Test2] mustEqual obj } "throw a DeserializationException if the JsObject does not all required members" in ( - JsObject(JsField("b", 4.2)).fromJson[Test2] must + JsObject(JsField("b", 4.2)).convertTo[Test2] must throwA(new DeserializationException("Object is missing required member 'a'")) ) "not require the presence of optional fields for deserialization" in { - JsObject(JsField("a", 42)).fromJson[Test2] mustEqual Test2(42, None) + JsObject(JsField("a", 42)).convertTo[Test2] mustEqual Test2(42, None) } "not render `None` members during serialization" in { Test2(42, None).toJson mustEqual JsObject(JsField("a", 42)) } "ignore additional members during deserialization" in { - JsObject(JsField("a", 42), JsField("b", 4.2), JsField("c", 'no)).fromJson[Test2] mustEqual obj + JsObject(JsField("a", 42), JsField("b", 4.2), JsField("c", 'no)).convertTo[Test2] mustEqual obj } "not depend on any specific member order for deserialization" in { - JsObject(JsField("b", 4.2), JsField("a", 42)).fromJson[Test2] mustEqual obj + JsObject(JsField("b", 4.2), JsField("a", 42)).convertTo[Test2] mustEqual obj } "throw a DeserializationException if the JsValue is not a JsObject" in ( - JsNull.fromJson[Test2] must throwA(new DeserializationException("Object expected")) + JsNull.convertTo[Test2] must throwA(new DeserializationException("Object expected")) ) } @@ -64,7 +64,7 @@ class ProductFormatsSpec extends Specification { obj.toJson mustEqual json } "convert a JsObject to the respective case class instance" in { - json.fromJson[Test3[Int, String]] mustEqual obj + json.convertTo[Test3[Int, String]] mustEqual obj } } diff --git a/src/test/scala/cc/spray/json/ReadmeSpec.scala b/src/test/scala/cc/spray/json/ReadmeSpec.scala index 843d1fc..1a30d39 100644 --- a/src/test/scala/cc/spray/json/ReadmeSpec.scala +++ b/src/test/scala/cc/spray/json/ReadmeSpec.scala @@ -33,7 +33,7 @@ class ReadmeSpec extends Specification { import MyJsonProtocol._ val json = Color("CadetBlue", 95, 158, 160).toJson - val color = json.fromJson[Color] + val color = json.convertTo[Color] color mustEqual Color("CadetBlue", 95, 158, 160) } @@ -57,7 +57,7 @@ class ReadmeSpec extends Specification { import MyJsonProtocol._ val json = Color("CadetBlue", 95, 158, 160).toJson - val color = json.fromJson[Color] + val color = json.convertTo[Color] color mustEqual Color("CadetBlue", 95, 158, 160) } diff --git a/src/test/scala/cc/spray/json/StandardFormatsSpec.scala b/src/test/scala/cc/spray/json/StandardFormatsSpec.scala index ad9485c..f1aa5a5 100644 --- a/src/test/scala/cc/spray/json/StandardFormatsSpec.scala +++ b/src/test/scala/cc/spray/json/StandardFormatsSpec.scala @@ -10,13 +10,13 @@ class StandardFormatsSpec extends Specification with DefaultJsonProtocol { None.asInstanceOf[Option[Int]].toJson mustEqual JsNull } "convert JsNull to None" in { - JsNull.fromJson[Option[Int]] mustEqual None + JsNull.convertTo[Option[Int]] mustEqual None } "convert Some(Hello) to JsString(Hello)" in { Some("Hello").asInstanceOf[Option[String]].toJson mustEqual JsString("Hello") } "convert JsString(Hello) to Some(Hello)" in { - JsString("Hello").fromJson[Option[String]] mustEqual Some("Hello") + JsString("Hello").convertTo[Option[String]] mustEqual Some("Hello") } } @@ -31,10 +31,10 @@ class StandardFormatsSpec extends Specification with DefaultJsonProtocol { b.toJson mustEqual JsString("Hello") } "convert the left side of an Either value from Json" in { - JsNumber(42).fromJson[Either[Int, String]] mustEqual Left(42) + JsNumber(42).convertTo[Either[Int, String]] mustEqual Left(42) } "convert the right side of an Either value from Json" in { - JsString("Hello").fromJson[Either[Int, String]] mustEqual Right("Hello") + JsString("Hello").convertTo[Either[Int, String]] mustEqual Right("Hello") } } @@ -43,7 +43,7 @@ class StandardFormatsSpec extends Specification with DefaultJsonProtocol { Tuple1(42).toJson mustEqual JsNumber(42) } "be able to convert a JsNumber to a Tuple1[Int]" in { - JsNumber(42).fromJson[Tuple1[Int]] mustEqual Tuple1(42) + JsNumber(42).convertTo[Tuple1[Int]] mustEqual Tuple1(42) } } @@ -53,7 +53,7 @@ class StandardFormatsSpec extends Specification with DefaultJsonProtocol { (42, 4.2).toJson mustEqual json } "be able to convert a JsArray to a (Int, Double)]" in { - json.fromJson[(Int, Double)] mustEqual (42, 4.2) + json.convertTo[(Int, Double)] mustEqual (42, 4.2) } } -- cgit v1.2.3 From 9168e6ee3fd5f2366e66206a97144562aff70993 Mon Sep 17 00:00:00 2001 From: Mathias Date: Wed, 9 Nov 2011 12:14:08 +0100 Subject: Remove JsValue.apply method without replacement due to non-type-safety --- src/main/scala/cc/spray/json/JsValue.scala | 46 ++-------------------- src/main/scala/cc/spray/json/JsonParser.scala | 2 +- .../cc/spray/json/CollectionFormatsSpec.scala | 2 +- .../scala/cc/spray/json/CompactPrinterSpec.scala | 4 +- src/test/scala/cc/spray/json/JsonParserSpec.scala | 4 +- .../scala/cc/spray/json/ProductFormatsSpec.scala | 14 +++---- src/test/scala/cc/spray/json/ReadmeSpec.scala | 2 +- 7 files changed, 18 insertions(+), 56 deletions(-) (limited to 'src/test/scala/cc/spray') diff --git a/src/main/scala/cc/spray/json/JsValue.scala b/src/main/scala/cc/spray/json/JsValue.scala index 86d4101..cc1af91 100644 --- a/src/main/scala/cc/spray/json/JsValue.scala +++ b/src/main/scala/cc/spray/json/JsValue.scala @@ -33,45 +33,6 @@ sealed trait JsValue { @deprecated("Superceded by 'convertTo'", "1.1.0") def fromJson[T :JsonReader]: T = convertTo } -object JsValue { - - /** - * General converter to a JsValue. - * Throws an IllegalArgumentException if the given value cannot be converted. - */ - def apply(value: Any): JsValue = value match { - case null => JsNull - case true => JsTrue - case false => JsFalse - case x: JsValue => x - case x: String => JsString(x) - case x: Int => JsNumber(x) - case x: Long => JsNumber(x) - case x: Double => JsNumber(x) - case x: Char => JsString(String.valueOf(x)) - case x: Float => JsNumber(x) - case x: Byte => JsNumber(x) - case x: Short => JsNumber(x) - case x: BigInt => JsNumber(x) - case x: BigDecimal => JsNumber(x) - case x: Symbol => JsString(x.name) - case x: collection.Map[_, _] => JsObject(fromSeq(x)) - case x@ collection.Seq((_, _), _*) => JsObject(fromSeq(x.asInstanceOf[Seq[(_, _)]])) - case x: collection.Seq[_] => JsArray(x.toList.map(JsValue.apply)) - case x => throw new IllegalArgumentException(x.toString + " cannot be converted to a JsValue") - } - - private def fromSeq(seq: Iterable[(_, _)]) = { - val list = ListBuffer.empty[JsField] - seq.foreach { - case (key: String, value) => list += JsField(key, JsValue(value)) - case (key: Symbol, value) => list += JsField(key.name, JsValue(value)) - case (key: JsString, value) => list += JsField(key.value, JsValue(value)) - case (x, _) => throw new IllegalArgumentException(x.toString + " cannot be converted to a JsString") - } - list.toList - } -} /** * A JSON object. @@ -91,9 +52,6 @@ object JsObject { * The members/fields of a JSON object. */ case class JsField(name: String, value: JsValue) extends JsValue -object JsField { - def apply(name: String, value: Any) = new JsField(name, JsValue(value)) -} /** * A JSON array. @@ -108,6 +66,10 @@ object JsArray { */ case class JsString(value: String) extends JsValue +object JsString { + def apply(value: Symbol) = new JsString(value.name) +} + /** * A JSON number. */ diff --git a/src/main/scala/cc/spray/json/JsonParser.scala b/src/main/scala/cc/spray/json/JsonParser.scala index 4a2d64b..b93a9a6 100644 --- a/src/main/scala/cc/spray/json/JsonParser.scala +++ b/src/main/scala/cc/spray/json/JsonParser.scala @@ -40,7 +40,7 @@ object JsonParser extends Parser { JsonString | JsonNumber | JsonObject | JsonArray | JsonTrue | JsonFalse | JsonNull } - def JsonString = rule { JsonStringUnwrapped ~~> JsString } + def JsonString = rule { JsonStringUnwrapped ~~> (JsString(_)) } def JsonStringUnwrapped = rule { "\"" ~ Characters ~ "\" " ~~> (_.toString) } diff --git a/src/test/scala/cc/spray/json/CollectionFormatsSpec.scala b/src/test/scala/cc/spray/json/CollectionFormatsSpec.scala index 009d2e2..f5eafa8 100644 --- a/src/test/scala/cc/spray/json/CollectionFormatsSpec.scala +++ b/src/test/scala/cc/spray/json/CollectionFormatsSpec.scala @@ -29,7 +29,7 @@ class CollectionFormatsSpec extends Specification with DefaultJsonProtocol { "The mapFormat" should { val map = Map("a" -> 1, "b" -> 2, "c" -> 3) - val json = JsObject(JsField("a", 1), JsField("b", 2), JsField("c", 3)) + val json = JsObject(JsField("a", JsNumber(1)), JsField("b", JsNumber(2)), JsField("c", JsNumber(3))) "convert a Map[String, Long] to a JsObject" in { map.toJson mustEqual json } diff --git a/src/test/scala/cc/spray/json/CompactPrinterSpec.scala b/src/test/scala/cc/spray/json/CompactPrinterSpec.scala index 3bc4870..f2d92fc 100644 --- a/src/test/scala/cc/spray/json/CompactPrinterSpec.scala +++ b/src/test/scala/cc/spray/json/CompactPrinterSpec.scala @@ -36,11 +36,11 @@ class CompactPrinterSpec extends Specification { CompactPrinter(JsString("\"\\\b\f\n\r\t\u12AB")) mustEqual """"\"\\\b\f\n\r\t""" + "\\u12ab\"" } "properly print a simple JsObject" in ( - CompactPrinter(JsObject(JsField("key", 42), JsField("key2", "value"))) + CompactPrinter(JsObject(JsField("key", JsNumber(42)), JsField("key2", JsString("value")))) mustEqual """{"key":42,"key2":"value"}""" ) "properly print a simple JsArray" in ( - CompactPrinter(JsArray(JsNull, JsNumber(1.23), JsObject(JsField("key", true)))) + CompactPrinter(JsArray(JsNull, JsNumber(1.23), JsObject(JsField("key", JsBoolean(true))))) mustEqual """[null,1.23,{"key":true}]""" ) "properly print a JSON padding (JSONP) if requested" in { diff --git a/src/test/scala/cc/spray/json/JsonParserSpec.scala b/src/test/scala/cc/spray/json/JsonParserSpec.scala index 0860d05..f030013 100644 --- a/src/test/scala/cc/spray/json/JsonParserSpec.scala +++ b/src/test/scala/cc/spray/json/JsonParserSpec.scala @@ -35,11 +35,11 @@ class JsonParserSpec extends Specification { } "properly parse a simple JsObject" in ( JsonParser(""" { "key" :42, "key2": "value" }""") mustEqual - JsObject(JsField("key", 42), JsField("key2", "value")) + JsObject(JsField("key", JsNumber(42)), JsField("key2", JsString("value"))) ) "properly parse a simple JsArray" in ( JsonParser("""[null, 1.23 ,{"key":true } ] """) mustEqual - JsArray(JsNull, JsNumber(1.23), JsObject(JsField("key", true))) + JsArray(JsNull, JsNumber(1.23), JsObject(JsField("key", JsBoolean(true)))) ) "be reentrant" in { val largeJsonSource = FileUtils.readAllCharsFromResource("test.json") diff --git a/src/test/scala/cc/spray/json/ProductFormatsSpec.scala b/src/test/scala/cc/spray/json/ProductFormatsSpec.scala index 7c643e9..059b931 100644 --- a/src/test/scala/cc/spray/json/ProductFormatsSpec.scala +++ b/src/test/scala/cc/spray/json/ProductFormatsSpec.scala @@ -18,7 +18,7 @@ class ProductFormatsSpec extends Specification { "A JsonFormat created with `jsonFormat`, for a case class with 2 elements," should { import TestProtocol1._ val obj = Test2(42, Some(4.2)) - val json = JsObject(JsField("a", 42), JsField("b", 4.2)) + val json = JsObject(JsField("a", JsNumber(42)), JsField("b", JsNumber(4.2))) "convert to a respective JsObject" in { obj.toJson mustEqual json } @@ -26,20 +26,20 @@ class ProductFormatsSpec extends Specification { json.convertTo[Test2] mustEqual obj } "throw a DeserializationException if the JsObject does not all required members" in ( - JsObject(JsField("b", 4.2)).convertTo[Test2] must + JsObject(JsField("b", JsNumber(4.2))).convertTo[Test2] must throwA(new DeserializationException("Object is missing required member 'a'")) ) "not require the presence of optional fields for deserialization" in { - JsObject(JsField("a", 42)).convertTo[Test2] mustEqual Test2(42, None) + JsObject(JsField("a", JsNumber(42))).convertTo[Test2] mustEqual Test2(42, None) } "not render `None` members during serialization" in { - Test2(42, None).toJson mustEqual JsObject(JsField("a", 42)) + Test2(42, None).toJson mustEqual JsObject(JsField("a", JsNumber(42))) } "ignore additional members during deserialization" in { - JsObject(JsField("a", 42), JsField("b", 4.2), JsField("c", 'no)).convertTo[Test2] mustEqual obj + JsObject(JsField("a", JsNumber(42)), JsField("b", JsNumber(4.2)), JsField("c", JsString('no))).convertTo[Test2] mustEqual obj } "not depend on any specific member order for deserialization" in { - JsObject(JsField("b", 4.2), JsField("a", 42)).convertTo[Test2] mustEqual obj + JsObject(JsField("b", JsNumber(4.2)), JsField("a", JsNumber(42))).convertTo[Test2] mustEqual obj } "throw a DeserializationException if the JsValue is not a JsObject" in ( JsNull.convertTo[Test2] must throwA(new DeserializationException("Object expected")) @@ -49,7 +49,7 @@ class ProductFormatsSpec extends Specification { "A JsonProtocol mixing in NullOptions" should { "render `None` members to `null`" in { import TestProtocol2._ - Test2(42, None).toJson mustEqual JsObject(JsField("a", 42), JsField("b", JsNull)) + Test2(42, None).toJson mustEqual JsObject(JsField("a", JsNumber(42)), JsField("b", JsNull)) } } diff --git a/src/test/scala/cc/spray/json/ReadmeSpec.scala b/src/test/scala/cc/spray/json/ReadmeSpec.scala index 1a30d39..b1c6674 100644 --- a/src/test/scala/cc/spray/json/ReadmeSpec.scala +++ b/src/test/scala/cc/spray/json/ReadmeSpec.scala @@ -10,7 +10,7 @@ class ReadmeSpec extends Specification { val json = """{ "some": "JSON source" }""" val jsonAst = JsonParser(json) - jsonAst mustEqual JsObject(JsField("some", "JSON source")) + jsonAst mustEqual JsObject(JsField("some", JsString("JSON source"))) val json2 = PrettyPrinter(jsonAst) json2 mustEqual -- cgit v1.2.3 From c14e3c5fec716d32cfd7a3d358e527c1c6b24a15 Mon Sep 17 00:00:00 2001 From: Mathias Date: Wed, 9 Nov 2011 12:43:06 +0100 Subject: Fix remaining deprecation warning --- src/test/scala/cc/spray/json/BasicFormatsSpec.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/test/scala/cc/spray') diff --git a/src/test/scala/cc/spray/json/BasicFormatsSpec.scala b/src/test/scala/cc/spray/json/BasicFormatsSpec.scala index 6a8ecd3..e474707 100644 --- a/src/test/scala/cc/spray/json/BasicFormatsSpec.scala +++ b/src/test/scala/cc/spray/json/BasicFormatsSpec.scala @@ -39,7 +39,7 @@ class BasicFormatsSpec extends Specification with DefaultJsonProtocol { JsNumber(4.2f).convertTo[Float] mustEqual 4.2f } "convert a JsNull to a Float" in { - JsNull.fromJson[Float].isNaN mustEqual Float.NaN.isNaN + JsNull.convertTo[Float].isNaN mustEqual Float.NaN.isNaN } } @@ -60,7 +60,7 @@ class BasicFormatsSpec extends Specification with DefaultJsonProtocol { JsNumber(4.2).convertTo[Double] mustEqual 4.2 } "convert a JsNull to a Double" in { - JsNull.fromJson[Double].isNaN mustEqual Double.NaN.isNaN + JsNull.convertTo[Double].isNaN mustEqual Double.NaN.isNaN } } -- cgit v1.2.3 From 2f31d74fad963561f646802a0a0fc194f430b573 Mon Sep 17 00:00:00 2001 From: Mathias Date: Wed, 16 Nov 2011 09:38:34 +0100 Subject: Add test for lazyFormat wrapper (recursive JsonFormats) --- src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'src/test/scala/cc/spray') diff --git a/src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala b/src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala index 0db3d5a..1f5362a 100644 --- a/src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala +++ b/src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala @@ -38,4 +38,18 @@ class AdditionalFormatsSpec extends Specification { JsonParser("""{"content":{"content":[1,2,3]}}""").convertTo[Container[Container[List[Int]]]] mustEqual obj } } + + case class Foo(id: Long, name: String, foos: Option[List[Foo]] = None) + + object FooProtocol extends DefaultJsonProtocol { + implicit val FooProtocol: JsonFormat[Foo] = lazyFormat(jsonFormat(Foo, "id", "name", "foos")) + } + + "The lazyFormat wrapper" should { + "enable recursive format definitions" in { + import FooProtocol._ + Foo(1, "a", Some(Foo(2, "b", Some(Foo(3, "c") :: Nil)) :: Foo(4, "d") :: Nil)).toJson.toString mustEqual + """{"id":1,"name":"a","foos":[{"id":2,"name":"b","foos":[{"id":3,"name":"c"}]},{"id":4,"name":"d"}]}""" + } + } } \ No newline at end of file -- cgit v1.2.3 From 94b1fba92d40568c642a0b86c719c4bcc0865b54 Mon Sep 17 00:00:00 2001 From: Mathias Date: Tue, 6 Dec 2011 13:22:37 +0100 Subject: Remove JsField, turn JsObject(List[JsField]) into JsObject(Map[String, JsValue]) --- .../scala/cc/spray/json/CollectionFormats.scala | 9 +++---- src/main/scala/cc/spray/json/CompactPrinter.scala | 6 ++--- .../cc/spray/json/DeserializationException.scala | 2 +- src/main/scala/cc/spray/json/JsValue.scala | 21 ++++++---------- src/main/scala/cc/spray/json/JsonParser.scala | 4 ++-- src/main/scala/cc/spray/json/PrettyPrinter.scala | 6 ++--- src/main/scala/cc/spray/json/ProductFormats.scala | 28 +++++++++++----------- src/main/scala/cc/spray/json/package.scala | 2 ++ .../cc/spray/json/AdditionalFormatsSpec.scala | 4 ++-- .../cc/spray/json/CollectionFormatsSpec.scala | 2 +- .../scala/cc/spray/json/CompactPrinterSpec.scala | 4 ++-- src/test/scala/cc/spray/json/JsonParserSpec.scala | 6 ++--- .../scala/cc/spray/json/ProductFormatsSpec.scala | 18 +++++++------- src/test/scala/cc/spray/json/ReadmeSpec.scala | 2 +- 14 files changed, 55 insertions(+), 59 deletions(-) (limited to 'src/test/scala/cc/spray') diff --git a/src/main/scala/cc/spray/json/CollectionFormats.scala b/src/main/scala/cc/spray/json/CollectionFormats.scala index a17c43f..c6c3403 100644 --- a/src/main/scala/cc/spray/json/CollectionFormats.scala +++ b/src/main/scala/cc/spray/json/CollectionFormats.scala @@ -47,15 +47,16 @@ trait CollectionFormats { */ implicit def mapFormat[K :JsonFormat, V :JsonFormat] = new RootJsonFormat[Map[K, V]] { def write(m: Map[K, V]) = JsObject { - m.toList.map { t => - t._1.toJson match { - case JsString(x) => JsField(x, t._2.toJson) + m.map { field => + field._1.toJson match { + case JsString(x) => x -> field._2.toJson case x => throw new SerializationException("Map key must be formatted as JsString, not '" + x + "'") } } } def read(value: JsValue) = value match { - case JsObject(fields) => fields.map(field => (JsString(field.name).convertTo[K], field.value.convertTo[V])).toMap + case JsObject(fields) => + fields.map(field => (JsString(field._1).convertTo[K], field._2.convertTo[V])) (collection.breakOut) case x => throw new DeserializationException("Expected Map as JsObject, but got " + x) } } diff --git a/src/main/scala/cc/spray/json/CompactPrinter.scala b/src/main/scala/cc/spray/json/CompactPrinter.scala index 329c08e..467160d 100644 --- a/src/main/scala/cc/spray/json/CompactPrinter.scala +++ b/src/main/scala/cc/spray/json/CompactPrinter.scala @@ -31,12 +31,12 @@ trait CompactPrinter extends JsonPrinter { } } - private def printObject(members: List[JsField], sb: StringBuilder) { + private def printObject(members: Map[String, JsValue], sb: StringBuilder) { sb.append('{') printSeq(members, sb.append(',')) { m => - printString(m.name, sb) + printString(m._1, sb) sb.append(':') - print(m.value, sb) + print(m._2, sb) } sb.append('}') } diff --git a/src/main/scala/cc/spray/json/DeserializationException.scala b/src/main/scala/cc/spray/json/DeserializationException.scala index 283d996..b317a14 100644 --- a/src/main/scala/cc/spray/json/DeserializationException.scala +++ b/src/main/scala/cc/spray/json/DeserializationException.scala @@ -16,4 +16,4 @@ package cc.spray.json -class DeserializationException(msg: String) extends RuntimeException(msg) \ No newline at end of file +class DeserializationException(msg: String, cause: Throwable = null) extends RuntimeException(msg, cause) \ No newline at end of file diff --git a/src/main/scala/cc/spray/json/JsValue.scala b/src/main/scala/cc/spray/json/JsValue.scala index cc1af91..f4dbf91 100644 --- a/src/main/scala/cc/spray/json/JsValue.scala +++ b/src/main/scala/cc/spray/json/JsValue.scala @@ -18,7 +18,9 @@ package cc.spray.json -import collection.mutable.ListBuffer +import collection.mutable.{LinkedHashMap, ListBuffer} +import collection.immutable.ListMap + /** * The general type of a JSON AST node. @@ -37,22 +39,13 @@ sealed trait JsValue { /** * A JSON object. */ -case class JsObject(fields: List[JsField]) extends JsValue { - lazy val asMap: Map[String, JsValue] = { - val b = Map.newBuilder[String, JsValue] - for (JsField(name, value) <- fields) b += ((name, value)) - b.result() - } -} +case class JsObject(fields: Map[String, JsValue]) extends JsValue object JsObject { - def apply(members: JsField*) = new JsObject(members.toList) + // we use a ListMap in order to preserve the field order + def apply(members: JsField*) = new JsObject(ListMap(members: _*)) + def apply(members: List[JsField]) = new JsObject(ListMap(members: _*)) } -/** - * The members/fields of a JSON object. - */ -case class JsField(name: String, value: JsValue) extends JsValue - /** * A JSON array. */ diff --git a/src/main/scala/cc/spray/json/JsonParser.scala b/src/main/scala/cc/spray/json/JsonParser.scala index b93a9a6..21b1d68 100644 --- a/src/main/scala/cc/spray/json/JsonParser.scala +++ b/src/main/scala/cc/spray/json/JsonParser.scala @@ -31,10 +31,10 @@ object JsonParser extends Parser { def Json = rule { WhiteSpace ~ Value ~ EOI } def JsonObject: Rule1[JsObject] = rule { - "{ " ~ zeroOrMore(Pair, separator = ", ") ~ "} " ~~> (JsObject(_)) + "{ " ~ zeroOrMore(Pair, separator = ", ") ~ "} " ~~> (JsObject(_ :_*)) } - def Pair = rule { JsonStringUnwrapped ~ ": " ~ Value ~~> (JsField(_, _)) } + def Pair = rule { JsonStringUnwrapped ~ ": " ~ Value ~~> ((_, _)) } def Value: Rule1[JsValue] = rule { JsonString | JsonNumber | JsonObject | JsonArray | JsonTrue | JsonFalse | JsonNull diff --git a/src/main/scala/cc/spray/json/PrettyPrinter.scala b/src/main/scala/cc/spray/json/PrettyPrinter.scala index 93ac0b6..429c984 100644 --- a/src/main/scala/cc/spray/json/PrettyPrinter.scala +++ b/src/main/scala/cc/spray/json/PrettyPrinter.scala @@ -37,13 +37,13 @@ trait PrettyPrinter extends JsonPrinter { } } - private def printObject(members: List[JsField], sb: StringBuilder, indent: Int) { + private def printObject(members: Map[String, JsValue], sb: StringBuilder, indent: Int) { sb.append("{\n") printSeq(members, sb.append(",\n")) { m => printIndent(sb, indent + Indent) - printString(m.name, sb) + printString(m._1, sb) sb.append(": ") - print(m.value, sb, indent + Indent) + print(m._2, sb, indent + Indent) } sb.append('\n') printIndent(sb, indent) diff --git a/src/main/scala/cc/spray/json/ProductFormats.scala b/src/main/scala/cc/spray/json/ProductFormats.scala index 90c3349..810a6ec 100644 --- a/src/main/scala/cc/spray/json/ProductFormats.scala +++ b/src/main/scala/cc/spray/json/ProductFormats.scala @@ -401,24 +401,24 @@ trait ProductFormats { val value = p.productElement(ix).asInstanceOf[T] writer match { case _: OptionFormat[_] if (value == None) => rest - case _ => JsField(fieldName, writer.write(value)) :: rest + case _ => (fieldName, writer.write(value)) :: rest } } private def fromField[T](value: JsValue, fieldName: String)(implicit reader: JsonReader[T]) = { - @tailrec - def getFrom(fields: List[JsField]): T = { - if (fields.isEmpty) { - if (reader.isInstanceOf[OptionFormat[_]]) None.asInstanceOf[T] - else throw new DeserializationException("Object is missing required member '" + fieldName + "'") - } else if (fields.head.name == fieldName) { - reader.read(fields.head.value) - } else { - getFrom(fields.tail) - } - } value match { - case x: JsObject => getFrom(x.fields) + case x: JsObject => + var fieldFound = false + try { + val fieldValue = x.fields(fieldName) + fieldFound = true + reader.read(fieldValue) + } + catch { + case e: NoSuchElementException if !fieldFound => + if (reader.isInstanceOf[OptionFormat[_]]) None.asInstanceOf[T] + else throw new DeserializationException("Object is missing required member '" + fieldName + "'", e) + } case _ => throw new DeserializationException("Object expected") } } @@ -437,6 +437,6 @@ trait NullOptions extends ProductFormats { override protected def productElement2Field[T](fieldName: String, p: Product, ix: Int, rest: List[JsField]) (implicit writer: JsonWriter[T]) = { val value = p.productElement(ix).asInstanceOf[T] - JsField(fieldName, writer.write(value)) :: rest + (fieldName, writer.write(value)) :: rest } } \ No newline at end of file diff --git a/src/main/scala/cc/spray/json/package.scala b/src/main/scala/cc/spray/json/package.scala index ee50ec0..f080312 100644 --- a/src/main/scala/cc/spray/json/package.scala +++ b/src/main/scala/cc/spray/json/package.scala @@ -17,6 +17,8 @@ package cc.spray package object json { + + type JsField = (String, JsValue) def jsonReader[T](implicit reader: JsonReader[T]) = reader def jsonWriter[T](implicit writer: JsonWriter[T]) = writer diff --git a/src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala b/src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala index 1f5362a..a9fb2d1 100644 --- a/src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala +++ b/src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala @@ -10,7 +10,7 @@ class AdditionalFormatsSpec extends Specification { implicit def containerReader[T :JsonFormat] = lift { new JsonReader[Container[T]] { def read(value: JsValue) = value match { - case JsObject(JsField("content", obj: JsValue) :: Nil) => Container(Some(jsonReader[T].read(obj))) + case JsObject(fields) if fields.contains("content") => Container(Some(jsonReader[T].read(fields("content")))) case _ => throw new DeserializationException("Unexpected format: " + value.toString) } } @@ -20,7 +20,7 @@ class AdditionalFormatsSpec extends Specification { object WriterProtocol extends DefaultJsonProtocol { implicit def containerWriter[T :JsonFormat] = lift { new JsonWriter[Container[T]] { - def write(obj: Container[T]) = JsObject(JsField("content", obj.inner.toJson)) + def write(obj: Container[T]) = JsObject("content" -> obj.inner.toJson) } } } diff --git a/src/test/scala/cc/spray/json/CollectionFormatsSpec.scala b/src/test/scala/cc/spray/json/CollectionFormatsSpec.scala index f5eafa8..fe5e4bb 100644 --- a/src/test/scala/cc/spray/json/CollectionFormatsSpec.scala +++ b/src/test/scala/cc/spray/json/CollectionFormatsSpec.scala @@ -29,7 +29,7 @@ class CollectionFormatsSpec extends Specification with DefaultJsonProtocol { "The mapFormat" should { val map = Map("a" -> 1, "b" -> 2, "c" -> 3) - val json = JsObject(JsField("a", JsNumber(1)), JsField("b", JsNumber(2)), JsField("c", JsNumber(3))) + val json = JsObject("a" -> JsNumber(1), "b" -> JsNumber(2), "c" -> JsNumber(3)) "convert a Map[String, Long] to a JsObject" in { map.toJson mustEqual json } diff --git a/src/test/scala/cc/spray/json/CompactPrinterSpec.scala b/src/test/scala/cc/spray/json/CompactPrinterSpec.scala index f2d92fc..948ee4d 100644 --- a/src/test/scala/cc/spray/json/CompactPrinterSpec.scala +++ b/src/test/scala/cc/spray/json/CompactPrinterSpec.scala @@ -36,11 +36,11 @@ class CompactPrinterSpec extends Specification { CompactPrinter(JsString("\"\\\b\f\n\r\t\u12AB")) mustEqual """"\"\\\b\f\n\r\t""" + "\\u12ab\"" } "properly print a simple JsObject" in ( - CompactPrinter(JsObject(JsField("key", JsNumber(42)), JsField("key2", JsString("value")))) + CompactPrinter(JsObject("key" -> JsNumber(42), "key2" -> JsString("value"))) mustEqual """{"key":42,"key2":"value"}""" ) "properly print a simple JsArray" in ( - CompactPrinter(JsArray(JsNull, JsNumber(1.23), JsObject(JsField("key", JsBoolean(true))))) + CompactPrinter(JsArray(JsNull, JsNumber(1.23), JsObject("key" -> JsBoolean(true)))) mustEqual """[null,1.23,{"key":true}]""" ) "properly print a JSON padding (JSONP) if requested" in { diff --git a/src/test/scala/cc/spray/json/JsonParserSpec.scala b/src/test/scala/cc/spray/json/JsonParserSpec.scala index f030013..c298617 100644 --- a/src/test/scala/cc/spray/json/JsonParserSpec.scala +++ b/src/test/scala/cc/spray/json/JsonParserSpec.scala @@ -35,16 +35,16 @@ class JsonParserSpec extends Specification { } "properly parse a simple JsObject" in ( JsonParser(""" { "key" :42, "key2": "value" }""") mustEqual - JsObject(JsField("key", JsNumber(42)), JsField("key2", JsString("value"))) + JsObject("key" -> JsNumber(42), "key2" -> JsString("value")) ) "properly parse a simple JsArray" in ( JsonParser("""[null, 1.23 ,{"key":true } ] """) mustEqual - JsArray(JsNull, JsNumber(1.23), JsObject(JsField("key", JsBoolean(true)))) + JsArray(JsNull, JsNumber(1.23), JsObject("key" -> JsBoolean(true))) ) "be reentrant" in { val largeJsonSource = FileUtils.readAllCharsFromResource("test.json") List.fill(20)(largeJsonSource).par.map(JsonParser(_)).toList.map { - _.asInstanceOf[JsObject].asMap("questions").asInstanceOf[JsArray].elements.size + _.asInstanceOf[JsObject].fields("questions").asInstanceOf[JsArray].elements.size } mustEqual List.fill(20)(100) } } diff --git a/src/test/scala/cc/spray/json/ProductFormatsSpec.scala b/src/test/scala/cc/spray/json/ProductFormatsSpec.scala index 059b931..6e389c7 100644 --- a/src/test/scala/cc/spray/json/ProductFormatsSpec.scala +++ b/src/test/scala/cc/spray/json/ProductFormatsSpec.scala @@ -18,7 +18,7 @@ class ProductFormatsSpec extends Specification { "A JsonFormat created with `jsonFormat`, for a case class with 2 elements," should { import TestProtocol1._ val obj = Test2(42, Some(4.2)) - val json = JsObject(JsField("a", JsNumber(42)), JsField("b", JsNumber(4.2))) + val json = JsObject("a" -> JsNumber(42), "b" -> JsNumber(4.2)) "convert to a respective JsObject" in { obj.toJson mustEqual json } @@ -26,20 +26,20 @@ class ProductFormatsSpec extends Specification { json.convertTo[Test2] mustEqual obj } "throw a DeserializationException if the JsObject does not all required members" in ( - JsObject(JsField("b", JsNumber(4.2))).convertTo[Test2] must + JsObject("b" -> JsNumber(4.2)).convertTo[Test2] must throwA(new DeserializationException("Object is missing required member 'a'")) ) "not require the presence of optional fields for deserialization" in { - JsObject(JsField("a", JsNumber(42))).convertTo[Test2] mustEqual Test2(42, None) + JsObject("a" -> JsNumber(42)).convertTo[Test2] mustEqual Test2(42, None) } "not render `None` members during serialization" in { - Test2(42, None).toJson mustEqual JsObject(JsField("a", JsNumber(42))) + Test2(42, None).toJson mustEqual JsObject("a" -> JsNumber(42)) } "ignore additional members during deserialization" in { - JsObject(JsField("a", JsNumber(42)), JsField("b", JsNumber(4.2)), JsField("c", JsString('no))).convertTo[Test2] mustEqual obj + JsObject("a" -> JsNumber(42), "b" -> JsNumber(4.2), "c" -> JsString('no)).convertTo[Test2] mustEqual obj } "not depend on any specific member order for deserialization" in { - JsObject(JsField("b", JsNumber(4.2)), JsField("a", JsNumber(42))).convertTo[Test2] mustEqual obj + JsObject("b" -> JsNumber(4.2), "a" -> JsNumber(42)).convertTo[Test2] mustEqual obj } "throw a DeserializationException if the JsValue is not a JsObject" in ( JsNull.convertTo[Test2] must throwA(new DeserializationException("Object expected")) @@ -49,7 +49,7 @@ class ProductFormatsSpec extends Specification { "A JsonProtocol mixing in NullOptions" should { "render `None` members to `null`" in { import TestProtocol2._ - Test2(42, None).toJson mustEqual JsObject(JsField("a", JsNumber(42)), JsField("b", JsNull)) + Test2(42, None).toJson mustEqual JsObject("a" -> JsNumber(42), "b" -> JsNull) } } @@ -57,8 +57,8 @@ class ProductFormatsSpec extends Specification { import TestProtocol1._ val obj = Test3(42 :: 43 :: Nil, "x" :: "y" :: "z" :: Nil) val json = JsObject( - JsField("as", JsArray(JsNumber(42), JsNumber(43))), - JsField("bs", JsArray(JsString("x"), JsString("y"), JsString("z"))) + "as" -> JsArray(JsNumber(42), JsNumber(43)), + "bs" -> JsArray(JsString("x"), JsString("y"), JsString("z")) ) "convert to a respective JsObject" in { obj.toJson mustEqual json diff --git a/src/test/scala/cc/spray/json/ReadmeSpec.scala b/src/test/scala/cc/spray/json/ReadmeSpec.scala index b1c6674..ace94b1 100644 --- a/src/test/scala/cc/spray/json/ReadmeSpec.scala +++ b/src/test/scala/cc/spray/json/ReadmeSpec.scala @@ -10,7 +10,7 @@ class ReadmeSpec extends Specification { val json = """{ "some": "JSON source" }""" val jsonAst = JsonParser(json) - jsonAst mustEqual JsObject(JsField("some", JsString("JSON source"))) + jsonAst mustEqual JsObject("some" -> JsString("JSON source")) val json2 = PrettyPrinter(jsonAst) json2 mustEqual -- cgit v1.2.3 From fd80c9099d4d474a6080df11a5786bd7e9b5cb7f Mon Sep 17 00:00:00 2001 From: Mathias Date: Wed, 7 Dec 2011 09:26:19 +0100 Subject: Fix copyrights --- src/main/scala/cc/spray/json/ProductFormats.scala | 3 +-- src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala | 16 ++++++++++++++++ src/test/scala/cc/spray/json/BasicFormatsSpec.scala | 16 ++++++++++++++++ src/test/scala/cc/spray/json/CollectionFormatsSpec.scala | 16 ++++++++++++++++ src/test/scala/cc/spray/json/CompactPrinterSpec.scala | 16 ++++++++++++++++ src/test/scala/cc/spray/json/JsonParserSpec.scala | 16 ++++++++++++++++ src/test/scala/cc/spray/json/PrettyPrinterSpec.scala | 16 ++++++++++++++++ src/test/scala/cc/spray/json/ProductFormatsSpec.scala | 16 ++++++++++++++++ src/test/scala/cc/spray/json/ReadmeSpec.scala | 16 ++++++++++++++++ src/test/scala/cc/spray/json/StandardFormatsSpec.scala | 16 ++++++++++++++++ 10 files changed, 145 insertions(+), 2 deletions(-) (limited to 'src/test/scala/cc/spray') diff --git a/src/main/scala/cc/spray/json/ProductFormats.scala b/src/main/scala/cc/spray/json/ProductFormats.scala index 810a6ec..d82cbf9 100644 --- a/src/main/scala/cc/spray/json/ProductFormats.scala +++ b/src/main/scala/cc/spray/json/ProductFormats.scala @@ -1,6 +1,5 @@ /* - * Original implementation (C) 2009-2011 Debasish Ghosh - * Adapted and extended in 2011 by Mathias Doenitz + * Copyright (C) 2011 Mathias Doenitz * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala b/src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala index a9fb2d1..f40fa37 100644 --- a/src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala +++ b/src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2011 Mathias Doenitz + * + * 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 cc.spray.json import org.specs2.mutable._ diff --git a/src/test/scala/cc/spray/json/BasicFormatsSpec.scala b/src/test/scala/cc/spray/json/BasicFormatsSpec.scala index e474707..f873948 100644 --- a/src/test/scala/cc/spray/json/BasicFormatsSpec.scala +++ b/src/test/scala/cc/spray/json/BasicFormatsSpec.scala @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2011 Mathias Doenitz + * + * 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 cc.spray.json import org.specs2.mutable._ diff --git a/src/test/scala/cc/spray/json/CollectionFormatsSpec.scala b/src/test/scala/cc/spray/json/CollectionFormatsSpec.scala index fe5e4bb..a252b1f 100644 --- a/src/test/scala/cc/spray/json/CollectionFormatsSpec.scala +++ b/src/test/scala/cc/spray/json/CollectionFormatsSpec.scala @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2011 Mathias Doenitz + * + * 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 cc.spray.json import org.specs2.mutable._ diff --git a/src/test/scala/cc/spray/json/CompactPrinterSpec.scala b/src/test/scala/cc/spray/json/CompactPrinterSpec.scala index 948ee4d..c441864 100644 --- a/src/test/scala/cc/spray/json/CompactPrinterSpec.scala +++ b/src/test/scala/cc/spray/json/CompactPrinterSpec.scala @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2011 Mathias Doenitz + * + * 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 cc.spray.json import org.specs2.mutable._ diff --git a/src/test/scala/cc/spray/json/JsonParserSpec.scala b/src/test/scala/cc/spray/json/JsonParserSpec.scala index c298617..cfe270f 100644 --- a/src/test/scala/cc/spray/json/JsonParserSpec.scala +++ b/src/test/scala/cc/spray/json/JsonParserSpec.scala @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2011 Mathias Doenitz + * + * 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 cc.spray.json import org.specs2.mutable._ diff --git a/src/test/scala/cc/spray/json/PrettyPrinterSpec.scala b/src/test/scala/cc/spray/json/PrettyPrinterSpec.scala index feed8a9..3bc29ff 100644 --- a/src/test/scala/cc/spray/json/PrettyPrinterSpec.scala +++ b/src/test/scala/cc/spray/json/PrettyPrinterSpec.scala @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2011 Mathias Doenitz + * + * 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 cc.spray.json import org.specs2.mutable._ diff --git a/src/test/scala/cc/spray/json/ProductFormatsSpec.scala b/src/test/scala/cc/spray/json/ProductFormatsSpec.scala index 6e389c7..6ec1611 100644 --- a/src/test/scala/cc/spray/json/ProductFormatsSpec.scala +++ b/src/test/scala/cc/spray/json/ProductFormatsSpec.scala @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2011 Mathias Doenitz + * + * 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 cc.spray.json import org.specs2.mutable._ diff --git a/src/test/scala/cc/spray/json/ReadmeSpec.scala b/src/test/scala/cc/spray/json/ReadmeSpec.scala index ace94b1..3514d41 100644 --- a/src/test/scala/cc/spray/json/ReadmeSpec.scala +++ b/src/test/scala/cc/spray/json/ReadmeSpec.scala @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2011 Mathias Doenitz + * + * 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 cc.spray.json import org.specs2.mutable._ diff --git a/src/test/scala/cc/spray/json/StandardFormatsSpec.scala b/src/test/scala/cc/spray/json/StandardFormatsSpec.scala index f1aa5a5..ed6283f 100644 --- a/src/test/scala/cc/spray/json/StandardFormatsSpec.scala +++ b/src/test/scala/cc/spray/json/StandardFormatsSpec.scala @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2011 Mathias Doenitz + * + * 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 cc.spray.json import org.specs2.mutable._ -- cgit v1.2.3 From 6a5ccb2e6c37f7922db34f29c1f9cfd61a2397e8 Mon Sep 17 00:00:00 2001 From: Mathias Date: Wed, 7 Dec 2011 10:38:29 +0100 Subject: Move exception into package.scala, simplify throwing of (De)SerializationExceptions --- README.markdown | 4 ++-- src/main/scala/cc/spray/json/BasicFormats.scala | 24 +++++++++++----------- .../scala/cc/spray/json/CollectionFormats.scala | 13 ++++++------ .../cc/spray/json/DeserializationException.scala | 19 ----------------- src/main/scala/cc/spray/json/ProductFormats.scala | 6 ++---- .../cc/spray/json/SerializationException.scala | 19 ----------------- src/main/scala/cc/spray/json/StandardFormats.scala | 16 +++++++-------- src/main/scala/cc/spray/json/package.scala | 11 ++++++++-- .../cc/spray/json/AdditionalFormatsSpec.scala | 2 +- src/test/scala/cc/spray/json/ReadmeSpec.scala | 2 +- 10 files changed, 42 insertions(+), 74 deletions(-) delete mode 100644 src/main/scala/cc/spray/json/DeserializationException.scala delete mode 100644 src/main/scala/cc/spray/json/SerializationException.scala (limited to 'src/test/scala/cc/spray') diff --git a/README.markdown b/README.markdown index d8c3986..9034bec 100644 --- a/README.markdown +++ b/README.markdown @@ -131,7 +131,7 @@ Here is one way to do it: case JsArray(JsString(name) :: JsNumber(red) :: JsNumber(green) :: JsNumber(blue) :: Nil) => { new Color(name, red.toInt, green.toInt, blue.toInt) } - case _ => throw new DeserializationException("Color expected") + case _ => deserializationError("Color expected") } } } @@ -163,7 +163,7 @@ Another way would be to serialize `Color`s as JSON objects: ) => { new Color(name.value, red.value.toInt, green.value.toInt, blue.value.toInt) } - case _ => throw new DeserializationException("Color expected") + case _ => deserializationError("Color expected") } } } diff --git a/src/main/scala/cc/spray/json/BasicFormats.scala b/src/main/scala/cc/spray/json/BasicFormats.scala index ebb0b31..0389e3a 100644 --- a/src/main/scala/cc/spray/json/BasicFormats.scala +++ b/src/main/scala/cc/spray/json/BasicFormats.scala @@ -26,7 +26,7 @@ trait BasicFormats { def write(x: Int) = JsNumber(x) def read(value: JsValue) = value match { case JsNumber(x) => x.intValue - case x => throw new DeserializationException("Expected Int as JsNumber, but got " + x) + case x => deserializationError("Expected Int as JsNumber, but got " + x) } } @@ -34,7 +34,7 @@ trait BasicFormats { def write(x: Long) = JsNumber(x) def read(value: JsValue) = value match { case JsNumber(x) => x.longValue - case x => throw new DeserializationException("Expected Long as JsNumber, but got " + x) + case x => deserializationError("Expected Long as JsNumber, but got " + x) } } @@ -43,7 +43,7 @@ trait BasicFormats { def read(value: JsValue) = value match { case JsNumber(x) => x.floatValue case JsNull => Float.NaN - case x => throw new DeserializationException("Expected Float as JsNumber, but got " + x) + case x => deserializationError("Expected Float as JsNumber, but got " + x) } } @@ -52,7 +52,7 @@ trait BasicFormats { def read(value: JsValue) = value match { case JsNumber(x) => x.doubleValue case JsNull => Double.NaN - case x => throw new DeserializationException("Expected Double as JsNumber, but got " + x) + case x => deserializationError("Expected Double as JsNumber, but got " + x) } } @@ -60,7 +60,7 @@ trait BasicFormats { def write(x: Byte) = JsNumber(x) def read(value: JsValue) = value match { case JsNumber(x) => x.byteValue - case x => throw new DeserializationException("Expected Byte as JsNumber, but got " + x) + case x => deserializationError("Expected Byte as JsNumber, but got " + x) } } @@ -68,7 +68,7 @@ trait BasicFormats { def write(x: Short) = JsNumber(x) def read(value: JsValue) = value match { case JsNumber(x) => x.shortValue - case x => throw new DeserializationException("Expected Short as JsNumber, but got " + x) + case x => deserializationError("Expected Short as JsNumber, but got " + x) } } @@ -76,7 +76,7 @@ trait BasicFormats { def write(x: BigDecimal) = JsNumber(x) def read(value: JsValue) = value match { case JsNumber(x) => x - case x => throw new DeserializationException("Expected BigDecimal as JsNumber, but got " + x) + case x => deserializationError("Expected BigDecimal as JsNumber, but got " + x) } } @@ -84,7 +84,7 @@ trait BasicFormats { def write(x: BigInt) = JsNumber(x) def read(value: JsValue) = value match { case JsNumber(x) => x.toBigInt - case x => throw new DeserializationException("Expected BigInt as JsNumber, but got " + x) + case x => deserializationError("Expected BigInt as JsNumber, but got " + x) } } @@ -98,7 +98,7 @@ trait BasicFormats { def read(value: JsValue) = value match { case JsTrue => true case JsFalse => false - case x => throw new DeserializationException("Expected JsBoolean, but got " + x) + case x => deserializationError("Expected JsBoolean, but got " + x) } } @@ -106,7 +106,7 @@ trait BasicFormats { def write(x: Char) = JsString(String.valueOf(x)) def read(value: JsValue) = value match { case JsString(x) if x.length == 1 => x.charAt(0) - case x => throw new DeserializationException("Expected Char as single-character JsString, but got " + x) + case x => deserializationError("Expected Char as single-character JsString, but got " + x) } } @@ -114,7 +114,7 @@ trait BasicFormats { def write(x: String) = JsString(x) def read(value: JsValue) = value match { case JsString(x) => x - case x => throw new DeserializationException("Expected String as JsString, but got " + x) + case x => deserializationError("Expected String as JsString, but got " + x) } } @@ -122,7 +122,7 @@ trait BasicFormats { def write(x: Symbol) = JsString(x.name) def read(value: JsValue) = value match { case JsString(x) => Symbol(x) - case x => throw new DeserializationException("Expected Symbol as JsString, but got " + x) + case x => deserializationError("Expected Symbol as JsString, but got " + x) } } diff --git a/src/main/scala/cc/spray/json/CollectionFormats.scala b/src/main/scala/cc/spray/json/CollectionFormats.scala index c6c3403..93e41ab 100644 --- a/src/main/scala/cc/spray/json/CollectionFormats.scala +++ b/src/main/scala/cc/spray/json/CollectionFormats.scala @@ -26,7 +26,7 @@ trait CollectionFormats { def write(list: List[T]) = JsArray(list.map(_.toJson)) def read(value: JsValue) = value match { case JsArray(elements) => elements.map(_.convertTo[T]) - case x => throw new DeserializationException("Expected List as JsArray, but got " + x) + case x => deserializationError("Expected List as JsArray, but got " + x) } } @@ -37,7 +37,7 @@ trait CollectionFormats { def write(array: Array[T]) = JsArray(array.map(_.toJson).toList) def read(value: JsValue) = value match { case JsArray(elements) => elements.map(_.convertTo[T]).toArray[T] - case x => throw new DeserializationException("Expected Array as JsArray, but got " + x) + case x => deserializationError("Expected Array as JsArray, but got " + x) } } @@ -55,9 +55,10 @@ trait CollectionFormats { } } def read(value: JsValue) = value match { - case JsObject(fields) => - fields.map(field => (JsString(field._1).convertTo[K], field._2.convertTo[V])) (collection.breakOut) - case x => throw new DeserializationException("Expected Map as JsObject, but got " + x) + case x: JsObject => x.fields.map { field => + (JsString(field._1).convertTo[K], field._2.convertTo[V]) + } (collection.breakOut) + case x => deserializationError("Expected Map as JsObject, but got " + x) } } @@ -86,7 +87,7 @@ trait CollectionFormats { def write(iterable: I) = JsArray(iterable.map(_.toJson).toList) def read(value: JsValue) = value match { case JsArray(elements) => f(elements.map(_.convertTo[T])) - case x => throw new DeserializationException("Expected Collection as JsArray, but got " + x) + case x => deserializationError("Expected Collection as JsArray, but got " + x) } } diff --git a/src/main/scala/cc/spray/json/DeserializationException.scala b/src/main/scala/cc/spray/json/DeserializationException.scala deleted file mode 100644 index b317a14..0000000 --- a/src/main/scala/cc/spray/json/DeserializationException.scala +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2011 Mathias Doenitz - * - * 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 cc.spray.json - -class DeserializationException(msg: String, cause: Throwable = null) extends RuntimeException(msg, cause) \ No newline at end of file diff --git a/src/main/scala/cc/spray/json/ProductFormats.scala b/src/main/scala/cc/spray/json/ProductFormats.scala index d82cbf9..6a79f5f 100644 --- a/src/main/scala/cc/spray/json/ProductFormats.scala +++ b/src/main/scala/cc/spray/json/ProductFormats.scala @@ -16,8 +16,6 @@ package cc.spray.json -import annotation.tailrec - /** * Provides the helpers for constructing custom JsonFormat implementations for types implementing the Product trait * (especially case classes) @@ -416,9 +414,9 @@ trait ProductFormats { catch { case e: NoSuchElementException if !fieldFound => if (reader.isInstanceOf[OptionFormat[_]]) None.asInstanceOf[T] - else throw new DeserializationException("Object is missing required member '" + fieldName + "'", e) + else deserializationError("Object is missing required member '" + fieldName + "'", e) } - case _ => throw new DeserializationException("Object expected") + case _ => deserializationError("Object expected") } } } diff --git a/src/main/scala/cc/spray/json/SerializationException.scala b/src/main/scala/cc/spray/json/SerializationException.scala deleted file mode 100644 index f8cbc00..0000000 --- a/src/main/scala/cc/spray/json/SerializationException.scala +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2011 Mathias Doenitz - * - * 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 cc.spray.json - -class SerializationException(msg: String) extends RuntimeException(msg) \ No newline at end of file diff --git a/src/main/scala/cc/spray/json/StandardFormats.scala b/src/main/scala/cc/spray/json/StandardFormats.scala index 277dfc9..78bc539 100644 --- a/src/main/scala/cc/spray/json/StandardFormats.scala +++ b/src/main/scala/cc/spray/json/StandardFormats.scala @@ -48,8 +48,8 @@ trait StandardFormats { def read(value: JsValue) = (value.convertTo(safeReader[A]), value.convertTo(safeReader[B])) match { case (Right(a), _: Left[_, _]) => Left(a) case (_: Left[_, _], Right(b)) => Right(b) - case (_: Right[_, _], _: Right[_, _]) => throw new DeserializationException("Ambiguous Either value: can be read as both, Left and Right, values") - case (Left(ea), Left(eb)) => throw new DeserializationException("Could not read Either value:\n" + ea + "---------- and ----------\n" + eb) + case (_: Right[_, _], _: Right[_, _]) => deserializationError("Ambiguous Either value: can be read as both, Left and Right, values") + case (Left(ea), Left(eb)) => deserializationError("Could not read Either value:\n" + ea + "---------- and ----------\n" + eb) } } @@ -62,7 +62,7 @@ trait StandardFormats { def write(t: (A, B)) = JsArray(t._1.toJson, t._2.toJson) def read(value: JsValue) = value match { case JsArray(a :: b :: Nil) => (a.convertTo[A], b.convertTo[B]) - case x => throw new DeserializationException("Expected Tuple2 as JsArray, but got " + x) + case x => deserializationError("Expected Tuple2 as JsArray, but got " + x) } } @@ -70,7 +70,7 @@ trait StandardFormats { def write(t: (A, B, C)) = JsArray(t._1.toJson, t._2.toJson, t._3.toJson) def read(value: JsValue) = value match { case JsArray(a :: b :: c :: Nil) => (a.convertTo[A], b.convertTo[B], c.convertTo[C]) - case x => throw new DeserializationException("Expected Tuple3 as JsArray, but got " + x) + case x => deserializationError("Expected Tuple3 as JsArray, but got " + x) } } @@ -78,7 +78,7 @@ trait StandardFormats { def write(t: (A, B, C, D)) = JsArray(t._1.toJson, t._2.toJson, t._3.toJson, t._4.toJson) def read(value: JsValue) = value match { case JsArray(a :: b :: c :: d :: Nil) => (a.convertTo[A], b.convertTo[B], c.convertTo[C], d.convertTo[D]) - case x => throw new DeserializationException("Expected Tuple4 as JsArray, but got " + x) + case x => deserializationError("Expected Tuple4 as JsArray, but got " + x) } } @@ -88,7 +88,7 @@ trait StandardFormats { def read(value: JsValue) = value match { case JsArray(a :: b :: c :: d :: e :: Nil) => (a.convertTo[A], b.convertTo[B], c.convertTo[C], d.convertTo[D], e.convertTo[E]) - case x => throw new DeserializationException("Expected Tuple5 as JsArray, but got " + x) + case x => deserializationError("Expected Tuple5 as JsArray, but got " + x) } } } @@ -99,7 +99,7 @@ trait StandardFormats { def read(value: JsValue) = value match { case JsArray(a :: b :: c :: d :: e :: f :: Nil) => (a.convertTo[A], b.convertTo[B], c.convertTo[C], d.convertTo[D], e.convertTo[E], f.convertTo[F]) - case x => throw new DeserializationException("Expected Tuple6 as JsArray, but got " + x) + case x => deserializationError("Expected Tuple6 as JsArray, but got " + x) } } } @@ -110,7 +110,7 @@ trait StandardFormats { def read(value: JsValue) = value match { case JsArray(a :: b :: c :: d :: e :: f :: g :: Nil) => (a.convertTo[A], b.convertTo[B], c.convertTo[C], d.convertTo[D], e.convertTo[E], f.convertTo[F], g.convertTo[G]) - case x => throw new DeserializationException("Expected Tuple7 as JsArray, but got " + x) + case x => deserializationError("Expected Tuple7 as JsArray, but got " + x) } } } diff --git a/src/main/scala/cc/spray/json/package.scala b/src/main/scala/cc/spray/json/package.scala index f080312..1bc592f 100644 --- a/src/main/scala/cc/spray/json/package.scala +++ b/src/main/scala/cc/spray/json/package.scala @@ -19,8 +19,11 @@ package cc.spray package object json { type JsField = (String, JsValue) - - def jsonReader[T](implicit reader: JsonReader[T]) = reader + + def deserializationError(msg: String, cause: Throwable = null) = throw new DeserializationException(msg, cause) + def serializationError(msg: String) = throw new SerializationException(msg) + + def jsonReader[T](implicit reader: JsonReader[T]) = reader def jsonWriter[T](implicit writer: JsonWriter[T]) = writer implicit def pimpAny[T](any: T): PimpedAny[T] = new PimpedAny(any) @@ -28,6 +31,10 @@ package object json { } package json { + + class DeserializationException(msg: String, cause: Throwable = null) extends RuntimeException(msg, cause) + class SerializationException(msg: String) extends RuntimeException(msg) + private[json] class PimpedAny[T](any: T) { def toJson(implicit writer: JsonWriter[T]): JsValue = writer.write(any) } diff --git a/src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala b/src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala index f40fa37..217ae4a 100644 --- a/src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala +++ b/src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala @@ -27,7 +27,7 @@ class AdditionalFormatsSpec extends Specification { new JsonReader[Container[T]] { def read(value: JsValue) = value match { case JsObject(fields) if fields.contains("content") => Container(Some(jsonReader[T].read(fields("content")))) - case _ => throw new DeserializationException("Unexpected format: " + value.toString) + case _ => deserializationError("Unexpected format: " + value.toString) } } } diff --git a/src/test/scala/cc/spray/json/ReadmeSpec.scala b/src/test/scala/cc/spray/json/ReadmeSpec.scala index 3514d41..7e1d246 100644 --- a/src/test/scala/cc/spray/json/ReadmeSpec.scala +++ b/src/test/scala/cc/spray/json/ReadmeSpec.scala @@ -66,7 +66,7 @@ class ReadmeSpec extends Specification { case JsArray(JsString(name) :: JsNumber(red) :: JsNumber(green) :: JsNumber(blue) :: Nil) => { new Color(name, red.toInt, green.toInt, blue.toInt) } - case _ => throw new DeserializationException("Color expected") + case _ => deserializationError("Color expected") } } } -- cgit v1.2.3 From 6d12a46772034109e08492caca3da4c77627bcfe Mon Sep 17 00:00:00 2001 From: Mathias Date: Wed, 7 Dec 2011 10:39:48 +0100 Subject: Add JsValue.asJsObject methods, simplifies JsObject matching --- src/main/scala/cc/spray/json/JsValue.scala | 20 ++++++++-- .../scala/cc/spray/json/CustomFormatSpec.scala | 45 ++++++++++++++++++++++ 2 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 src/test/scala/cc/spray/json/CustomFormatSpec.scala (limited to 'src/test/scala/cc/spray') diff --git a/src/main/scala/cc/spray/json/JsValue.scala b/src/main/scala/cc/spray/json/JsValue.scala index f4dbf91..d14312b 100644 --- a/src/main/scala/cc/spray/json/JsValue.scala +++ b/src/main/scala/cc/spray/json/JsValue.scala @@ -18,20 +18,29 @@ package cc.spray.json -import collection.mutable.{LinkedHashMap, ListBuffer} import collection.immutable.ListMap /** * The general type of a JSON AST node. */ -sealed trait JsValue { +sealed abstract class JsValue { override def toString = compactPrint def toString(printer: (JsValue => String)) = printer(this) def compactPrint = CompactPrinter(this) def prettyPrint = PrettyPrinter(this) def convertTo[T :JsonReader]: T = jsonReader[T].read(this) + /** + * Returns `this` if this JsValue is a JsObject, otherwise throws a DeserializationException with the given error msg. + */ + def asJsObject(errorMsg: String = "JSON object expected"): JsObject = deserializationError(errorMsg) + + /** + * Returns `this` if this JsValue is a JsObject, otherwise throws a DeserializationException. + */ + def asJsObject: JsObject = asJsObject() + @deprecated("Superceded by 'convertTo'", "1.1.0") def fromJson[T :JsonReader]: T = convertTo } @@ -39,7 +48,10 @@ sealed trait JsValue { /** * A JSON object. */ -case class JsObject(fields: Map[String, JsValue]) extends JsValue +case class JsObject(fields: Map[String, JsValue]) extends JsValue { + override def asJsObject(errorMsg: String) = this + def getFields(fieldNames: String*): Seq[JsValue] = fieldNames.flatMap(fields.get) +} object JsObject { // we use a ListMap in order to preserve the field order def apply(members: JsField*) = new JsObject(ListMap(members: _*)) @@ -82,7 +94,7 @@ object JsNumber { /** * JSON Booleans. */ -sealed trait JsBoolean extends JsValue { +sealed abstract class JsBoolean extends JsValue { def value: Boolean } object JsBoolean { diff --git a/src/test/scala/cc/spray/json/CustomFormatSpec.scala b/src/test/scala/cc/spray/json/CustomFormatSpec.scala new file mode 100644 index 0000000..237e3f4 --- /dev/null +++ b/src/test/scala/cc/spray/json/CustomFormatSpec.scala @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2011 Mathias Doenitz + * + * 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 cc.spray.json + +import org.specs2.mutable.Specification + +class CustomFormatSpec extends Specification with DefaultJsonProtocol { + + case class MyType(name: String, value: Int) + + implicit val MyTypeProtocol = new RootJsonFormat[MyType] { + def read(json: JsValue) = { + json.asJsObject.getFields("name", "value") match { + case Seq(JsString(name), JsNumber(value)) => MyType(name, value.toInt) + case _ => deserializationError("Expected fields: 'name' (JSON string) and 'value' (JSON number)") + } + } + def write(obj: MyType) = JsObject("name" -> JsString(obj.name), "value" -> JsNumber(obj.value)) + } + + "A custom JsonFormat built with 'asJsonObject'" should { + val value = MyType("bob", 42) + "correctly deserialize valid JSON content" in { + JsonParser("""{ "name": "bob", "value": 42 }""").convertTo[MyType] mustEqual value + } + "support full round-trip (de)serialization" in { + value.toJson.convertTo[MyType] mustEqual value + } + } + +} \ No newline at end of file -- cgit v1.2.3 From cfbc301ecaaac325d3a1ad87305107d2cc785b7d Mon Sep 17 00:00:00 2001 From: Mathias Date: Tue, 17 Jan 2012 17:22:43 +0100 Subject: Pimp Strings with 'asJson' --- README.markdown | 4 ++-- src/main/scala/cc/spray/json/package.scala | 8 ++++++-- src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala | 2 +- src/test/scala/cc/spray/json/CustomFormatSpec.scala | 2 +- src/test/scala/cc/spray/json/ReadmeSpec.scala | 4 ++-- 5 files changed, 12 insertions(+), 8 deletions(-) (limited to 'src/test/scala/cc/spray') diff --git a/README.markdown b/README.markdown index fb35301..3ff1d02 100644 --- a/README.markdown +++ b/README.markdown @@ -32,8 +32,8 @@ and do one or more of the following: 1. Parse a JSON string into its Abstract Syntax Tree (AST) representation - val json = """{ "some": "JSON source" }""" - val jsonAst = JsonParser(json) + val source = """{ "some": "JSON source" }""" + val jsonAst = source.asJson // or JsonParser(source) 2. Print a JSON AST back to a String using either the `CompactPrinter` or the `PrettyPrinter` diff --git a/src/main/scala/cc/spray/json/package.scala b/src/main/scala/cc/spray/json/package.scala index 1bc592f..ec12939 100644 --- a/src/main/scala/cc/spray/json/package.scala +++ b/src/main/scala/cc/spray/json/package.scala @@ -26,8 +26,8 @@ package object json { def jsonReader[T](implicit reader: JsonReader[T]) = reader def jsonWriter[T](implicit writer: JsonWriter[T]) = writer - implicit def pimpAny[T](any: T): PimpedAny[T] = new PimpedAny(any) - + implicit def pimpAny[T](any: T) = new PimpedAny(any) + implicit def pimpString(string: String) = new PimpedString(string) } package json { @@ -38,4 +38,8 @@ package json { private[json] class PimpedAny[T](any: T) { def toJson(implicit writer: JsonWriter[T]): JsValue = writer.write(any) } + + private[json] class PimpedString(string: String) { + def asJson: JsValue = JsonParser(string) + } } \ No newline at end of file diff --git a/src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala b/src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala index 217ae4a..f25c9d8 100644 --- a/src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala +++ b/src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala @@ -51,7 +51,7 @@ class AdditionalFormatsSpec extends Specification { "properly read a Container[Container[List[Int]]] from JSON" in { import ReaderProtocol._ - JsonParser("""{"content":{"content":[1,2,3]}}""").convertTo[Container[Container[List[Int]]]] mustEqual obj + """{"content":{"content":[1,2,3]}}""".asJson.convertTo[Container[Container[List[Int]]]] mustEqual obj } } diff --git a/src/test/scala/cc/spray/json/CustomFormatSpec.scala b/src/test/scala/cc/spray/json/CustomFormatSpec.scala index 237e3f4..e8feb59 100644 --- a/src/test/scala/cc/spray/json/CustomFormatSpec.scala +++ b/src/test/scala/cc/spray/json/CustomFormatSpec.scala @@ -35,7 +35,7 @@ class CustomFormatSpec extends Specification with DefaultJsonProtocol { "A custom JsonFormat built with 'asJsonObject'" should { val value = MyType("bob", 42) "correctly deserialize valid JSON content" in { - JsonParser("""{ "name": "bob", "value": 42 }""").convertTo[MyType] mustEqual value + """{ "name": "bob", "value": 42 }""".asJson.convertTo[MyType] mustEqual value } "support full round-trip (de)serialization" in { value.toJson.convertTo[MyType] mustEqual value diff --git a/src/test/scala/cc/spray/json/ReadmeSpec.scala b/src/test/scala/cc/spray/json/ReadmeSpec.scala index 7e1d246..d60aa69 100644 --- a/src/test/scala/cc/spray/json/ReadmeSpec.scala +++ b/src/test/scala/cc/spray/json/ReadmeSpec.scala @@ -24,8 +24,8 @@ class ReadmeSpec extends Specification { "behave as expected" in { import DefaultJsonProtocol._ - val json = """{ "some": "JSON source" }""" - val jsonAst = JsonParser(json) + val source = """{ "some": "JSON source" }""" + val jsonAst = source.asJson jsonAst mustEqual JsObject("some" -> JsString("JSON source")) val json2 = PrettyPrinter(jsonAst) -- cgit v1.2.3 From 8e6d79cfc3080c276890a2653c545dade6387cf8 Mon Sep 17 00:00:00 2001 From: Mathias Date: Wed, 1 Feb 2012 10:56:57 +0100 Subject: Improve ReadmeSpec --- src/test/scala/cc/spray/json/ReadmeSpec.scala | 58 +++++++++++++++++---------- 1 file changed, 37 insertions(+), 21 deletions(-) (limited to 'src/test/scala/cc/spray') diff --git a/src/test/scala/cc/spray/json/ReadmeSpec.scala b/src/test/scala/cc/spray/json/ReadmeSpec.scala index d60aa69..e67312d 100644 --- a/src/test/scala/cc/spray/json/ReadmeSpec.scala +++ b/src/test/scala/cc/spray/json/ReadmeSpec.scala @@ -33,49 +33,65 @@ class ReadmeSpec extends Specification { """{ | "some": "JSON source" |}""".stripMargin - + val jsonAst2 = List(1, 2, 3).toJson jsonAst2 mustEqual JsArray(JsNumber(1), JsNumber(2), JsNumber(3)) } } - + case class Color(name: String, red: Int, green: Int, blue: Int) - + val color = Color("CadetBlue", 95, 158, 160) + "The case class example" should { "behave as expected" in { object MyJsonProtocol extends DefaultJsonProtocol { implicit val colorFormat = jsonFormat(Color, "name", "red", "green", "blue") - } + } import MyJsonProtocol._ - - val json = Color("CadetBlue", 95, 158, 160).toJson - val color = json.convertTo[Color] - - color mustEqual Color("CadetBlue", 95, 158, 160) + color.toJson.convertTo[Color] mustEqual color } } - - "The non case class example" should { + + "The non case class (array) example" should { "behave as expected" in { object MyJsonProtocol extends DefaultJsonProtocol { implicit object ColorJsonFormat extends JsonFormat[Color] { - def write(c: Color) = { + def write(c: Color) = JsArray(JsString(c.name), JsNumber(c.red), JsNumber(c.green), JsNumber(c.blue)) - } + def read(value: JsValue) = value match { - case JsArray(JsString(name) :: JsNumber(red) :: JsNumber(green) :: JsNumber(blue) :: Nil) => { + case JsArray(Seq(JsString(name), JsNumber(red), JsNumber(green), JsNumber(blue))) => new Color(name, red.toInt, green.toInt, blue.toInt) - } case _ => deserializationError("Color expected") } } - } + } import MyJsonProtocol._ - - val json = Color("CadetBlue", 95, 158, 160).toJson - val color = json.convertTo[Color] - - color mustEqual Color("CadetBlue", 95, 158, 160) + color.toJson.convertTo[Color] mustEqual color + } + } + + "The non case class (object) example" should { + "behave as expected" in { + object MyJsonProtocol extends DefaultJsonProtocol { + implicit object ColorJsonFormat extends JsonFormat[Color] { + def write(c: Color) = JsObject( + "name" -> JsString(c.name), + "red" -> JsNumber(c.red), + "green" -> JsNumber(c.green), + "blue" -> JsNumber(c.blue) + ) + def read(value: JsValue) = { + value.asJsObject.getFields("name", "red", "green", "blue") match { + case Seq(JsString(name), JsNumber(red), JsNumber(green), JsNumber(blue)) => + new Color(name, red.toInt, green.toInt, blue.toInt) + case _ => throw new DeserializationException("Color expected") + } + } + } + } + import MyJsonProtocol._ + color.toJson.convertTo[Color] mustEqual color } } -- cgit v1.2.3 From c6b5edcb5665b0ef60b5108d8cff3431c0663927 Mon Sep 17 00:00:00 2001 From: Mathias Date: Thu, 19 Jan 2012 16:48:46 +0100 Subject: Add jsonFormatX overloads providing automatic case class field name extraction Conflicts: src/main/scala/cc/spray/json/ProductFormats.scala --- src/main/scala/cc/spray/json/ProductFormats.scala | 132 +++++++++++++++++---- .../scala/cc/spray/json/ProductFormatsSpec.scala | 4 +- 2 files changed, 112 insertions(+), 24 deletions(-) (limited to 'src/test/scala/cc/spray') diff --git a/src/main/scala/cc/spray/json/ProductFormats.scala b/src/main/scala/cc/spray/json/ProductFormats.scala index 6a79f5f..1f26259 100644 --- a/src/main/scala/cc/spray/json/ProductFormats.scala +++ b/src/main/scala/cc/spray/json/ProductFormats.scala @@ -23,7 +23,11 @@ package cc.spray.json trait ProductFormats { this: StandardFormats => - def jsonFormat[A :JF, T <: Product](construct: A => T, a: String) = new RootJsonFormat[T]{ + def jsonFormat1[A :JF, T <: Product :ClassManifest](construct: A => T): RootJsonFormat[T] = { + val Array(a) = extractFieldNames(classManifest[T]) + jsonFormat(construct, a) + } + def jsonFormat[A :JF, T <: Product](construct: A => T, a: String): RootJsonFormat[T] = new RootJsonFormat[T]{ def write(p: T) = JsObject( productElement2Field[A](a, p, 0) ) @@ -32,7 +36,11 @@ trait ProductFormats { ) } - def jsonFormat[A :JF, B :JF, T <: Product](construct: (A, B) => T, a: String, b: String) = new RootJsonFormat[T]{ + def jsonFormat2[A :JF, B :JF, T <: Product :ClassManifest](construct: (A, B) => T): RootJsonFormat[T] = { + val Array(a, b) = extractFieldNames(classManifest[T]) + jsonFormat(construct, a, b) + } + def jsonFormat[A :JF, B :JF, T <: Product](construct: (A, B) => T, a: String, b: String): RootJsonFormat[T] = new RootJsonFormat[T]{ def write(p: T) = JsObject( productElement2Field[A](a, p, 0, productElement2Field[B](b, p, 1)) @@ -43,8 +51,12 @@ trait ProductFormats { ) } + def jsonFormat3[A :JF, B :JF, C :JF, T <: Product :ClassManifest](construct: (A, B, C) => T): RootJsonFormat[T] = { + val Array(a, b, c) = extractFieldNames(classManifest[T]) + jsonFormat(construct, a, b, c) + } def jsonFormat[A :JF, B :JF, C :JF, T <: Product](construct: (A, B, C) => T, - a: String, b: String, c: String) = new RootJsonFormat[T]{ + a: String, b: String, c: String): RootJsonFormat[T] = new RootJsonFormat[T]{ def write(p: T) = JsObject( productElement2Field[A](a, p, 0, productElement2Field[B](b, p, 1, @@ -57,8 +69,13 @@ trait ProductFormats { ) } + def jsonFormat4[A :JF, B :JF, C :JF, D :JF, T <: Product :ClassManifest] + (construct: (A, B, C, D) => T): RootJsonFormat[T] = { + val Array(a, b, c, d) = extractFieldNames(classManifest[T]) + jsonFormat(construct, a, b, c, d) + } def jsonFormat[A :JF, B :JF, C :JF, D :JF, T <: Product](construct: (A, B, C, D) => T, - a: String, b: String, c: String, d: String) = new RootJsonFormat[T]{ + a: String, b: String, c: String, d: String): RootJsonFormat[T] = new RootJsonFormat[T]{ def write(p: T) = JsObject( productElement2Field[A](a, p, 0, productElement2Field[B](b, p, 1, @@ -73,8 +90,13 @@ trait ProductFormats { ) } + def jsonFormat5[A :JF, B :JF, C :JF, D :JF, E :JF, T <: Product :ClassManifest] + (construct: (A, B, C, D, E) => T): RootJsonFormat[T] = { + val Array(a, b, c, d, e) = extractFieldNames(classManifest[T]) + jsonFormat(construct, a, b, c, d, e) + } def jsonFormat[A :JF, B :JF, C :JF, D :JF, E :JF, T <: Product](construct: (A, B, C, D, E) => T, - a: String, b: String, c: String, d: String, e: String) = new RootJsonFormat[T]{ + a: String, b: String, c: String, d: String, e: String): RootJsonFormat[T] = new RootJsonFormat[T]{ def write(p: T) = JsObject( productElement2Field[A](a, p, 0, productElement2Field[B](b, p, 1, @@ -90,9 +112,14 @@ trait ProductFormats { fromField[E](value, e) ) } - + + def jsonFormat6[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, T <: Product :ClassManifest] + (construct: (A, B, C, D, E, F) => T): RootJsonFormat[T] = { + val Array(a, b, c, d, e, f) = extractFieldNames(classManifest[T]) + jsonFormat(construct, a, b, c, d, e, f) + } def jsonFormat[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, T <: Product](construct: (A, B, C, D, E, F) => T, - a: String, b: String, c: String, d: String, e: String, f: String) = new RootJsonFormat[T]{ + a: String, b: String, c: String, d: String, e: String, f: String): RootJsonFormat[T] = new RootJsonFormat[T]{ def write(p: T) = JsObject( productElement2Field[A](a, p, 0, productElement2Field[B](b, p, 1, @@ -110,9 +137,14 @@ trait ProductFormats { fromField[F](value, f) ) } - + + def jsonFormat7[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, T <: Product :ClassManifest] + (construct: (A, B, C, D, E, F, G) => T): RootJsonFormat[T] = { + val Array(a, b, c, d, e, f, g) = extractFieldNames(classManifest[T]) + jsonFormat(construct, a, b, c, d, e, f, g) + } def jsonFormat[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, T <: Product](construct: (A, B, C, D, E, F, G) => T, - a: String, b: String, c: String, d: String, e: String, f: String, g: String) = new RootJsonFormat[T]{ + a: String, b: String, c: String, d: String, e: String, f: String, g: String): RootJsonFormat[T] = new RootJsonFormat[T]{ def write(p: T) = JsObject( productElement2Field[A](a, p, 0, productElement2Field[B](b, p, 1, @@ -132,10 +164,15 @@ trait ProductFormats { fromField[G](value, g) ) } - + + def jsonFormat8[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, T <: Product :ClassManifest] + (construct: (A, B, C, D, E, F, G, H) => T): RootJsonFormat[T] = { + val Array(a, b, c, d, e, f, g, h) = extractFieldNames(classManifest[T]) + jsonFormat(construct, a, b, c, d, e, f, g, h) + } def jsonFormat[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, T <: Product] (construct: (A, B, C, D, E, F, G, H) => T, - a: String, b: String, c: String, d: String, e: String, f: String, g: String, h: String) = new RootJsonFormat[T]{ + a: String, b: String, c: String, d: String, e: String, f: String, g: String, h: String): RootJsonFormat[T] = new RootJsonFormat[T]{ def write(p: T) = JsObject( productElement2Field[A](a, p, 0, productElement2Field[B](b, p, 1, @@ -157,10 +194,15 @@ trait ProductFormats { fromField[H](value, h) ) } - + + def jsonFormat9[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, T <: Product :ClassManifest] + (construct: (A, B, C, D, E, F, G, H, I) => T): RootJsonFormat[T] = { + val Array(a, b, c, d, e, f, g, h, i) = extractFieldNames(classManifest[T]) + jsonFormat(construct, a, b, c, d, e, f, g, h, i) + } def jsonFormat[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, T <: Product] (construct: (A, B, C, D, E, F, G, H, I) => T, a: String, b: String, c: String, d: String, e: String, f: String, - g: String, h: String, i: String) = new RootJsonFormat[T]{ + g: String, h: String, i: String): RootJsonFormat[T] = new RootJsonFormat[T]{ def write(p: T) = JsObject( productElement2Field[A](a, p, 0, productElement2Field[B](b, p, 1, @@ -184,10 +226,15 @@ trait ProductFormats { fromField[I](value, i) ) } - + + def jsonFormat10[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, J :JF, T <: Product :ClassManifest] + (construct: (A, B, C, D, E, F, G, H, I, J) => T): RootJsonFormat[T] = { + val Array(a, b, c, d, e, f, g, h, i, j) = extractFieldNames(classManifest[T]) + jsonFormat(construct, a, b, c, d, e, f, g, h, i, j) + } def jsonFormat[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, J :JF, T <: Product] (construct: (A, B, C, D, E, F, G, H, I, J) => T, a: String, b: String, c: String, d: String, e: String, - f: String, g: String, h: String, i: String, j: String) = new RootJsonFormat[T]{ + f: String, g: String, h: String, i: String, j: String): RootJsonFormat[T] = new RootJsonFormat[T]{ def write(p: T) = JsObject( productElement2Field[A](a, p, 0, productElement2Field[B](b, p, 1, @@ -213,10 +260,15 @@ trait ProductFormats { fromField[J](value, j) ) } - + + def jsonFormat11[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, J :JF, K :JF, T <: Product :ClassManifest] + (construct: (A, B, C, D, E, F, G, H, I, J, K) => T): RootJsonFormat[T] = { + val Array(a, b, c, d, e, f, g, h, i, j, k) = extractFieldNames(classManifest[T]) + jsonFormat(construct, a, b, c, d, e, f, g, h, i, j, k) + } def jsonFormat[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, J :JF, K :JF, T <: Product] (construct: (A, B, C, D, E, F, G, H, I, J, K) => T, a: String, b: String, c: String, d: String, e: String, - f: String, g: String, h: String, i: String, j: String, k: String) = new RootJsonFormat[T]{ + f: String, g: String, h: String, i: String, j: String, k: String): RootJsonFormat[T] = new RootJsonFormat[T]{ def write(p: T) = JsObject( productElement2Field[A](a, p, 0, productElement2Field[B](b, p, 1, @@ -245,9 +297,14 @@ trait ProductFormats { ) } + def jsonFormat12[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, J :JF, K :JF, L: JF, T <: Product :ClassManifest] + (construct: (A, B, C, D, E, F, G, H, I, J, K, L) => T): RootJsonFormat[T] = { + val Array(a, b, c, d, e, f, g, h, i, j, k, l) = extractFieldNames(classManifest[T]) + jsonFormat(construct, a, b, c, d, e, f, g, h, i, j, k, l) + } def jsonFormat[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, J :JF, K :JF, L :JF, T <: Product] (construct: (A, B, C, D, E, F, G, H, I, J, K, L) => T, a: String, b: String, c: String, d: String, e: String, - f: String, g: String, h: String, i: String, j: String, k: String, l: String) = new RootJsonFormat[T]{ + f: String, g: String, h: String, i: String, j: String, k: String, l: String): RootJsonFormat[T] = new RootJsonFormat[T]{ def write(p: T) = JsObject( productElement2Field[A](a, p, 0, productElement2Field[B](b, p, 1, @@ -278,9 +335,14 @@ trait ProductFormats { ) } + def jsonFormat13[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, J :JF, K :JF, L: JF, M :JF, T <: Product :ClassManifest] + (construct: (A, B, C, D, E, F, G, H, I, J, K, L, M) => T): RootJsonFormat[T] = { + val Array(a, b, c, d, e, f, g, h, i, j, k, l, m) = extractFieldNames(classManifest[T]) + jsonFormat(construct, a, b, c, d, e, f, g, h, i, j, k, l, m) + } def jsonFormat[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, J :JF, K :JF, L :JF, M :JF, T <: Product] (construct: (A, B, C, D, E, F, G, H, I, J, K, L, M) => T, a: String, b: String, c: String, d: String, e: String, - f: String, g: String, h: String, i: String, j: String, k: String, l: String, m: String) = new RootJsonFormat[T]{ + f: String, g: String, h: String, i: String, j: String, k: String, l: String, m: String): RootJsonFormat[T] = new RootJsonFormat[T]{ def write(p: T) = JsObject( productElement2Field[A](a, p, 0, productElement2Field[B](b, p, 1, @@ -313,10 +375,15 @@ trait ProductFormats { ) } + def jsonFormat14[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, J :JF, K :JF, L: JF, M :JF, N :JF, T <: Product :ClassManifest] + (construct: (A, B, C, D, E, F, G, H, I, J, K, L, M, N) => T): RootJsonFormat[T] = { + val Array(a, b, c, d, e, f, g, h, i, j, k, l, m, n) = extractFieldNames(classManifest[T]) + jsonFormat(construct, a, b, c, d, e, f, g, h, i, j, k, l, m, n) + } def jsonFormat[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, J :JF, K :JF, L :JF, M :JF, N :JF, T <: Product] (construct: (A, B, C, D, E, F, G, H, I, J, K, L, M, N) => T, a: String, b: String, c: String, d: String, e: String, f: String, g: String, h: String, i: String, j: String, k: String, l: String, m: String, - n: String) = new RootJsonFormat[T]{ + n: String): RootJsonFormat[T] = new RootJsonFormat[T]{ def write(p: T) = JsObject( productElement2Field[A](a, p, 0, productElement2Field[B](b, p, 1, @@ -351,10 +418,15 @@ trait ProductFormats { ) } + def jsonFormat15[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, J :JF, K :JF, L: JF, M :JF, N :JF, O :JF, T <: Product :ClassManifest] + (construct: (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O) => T): RootJsonFormat[T] = { + val Array(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) = extractFieldNames(classManifest[T]) + jsonFormat(construct, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) + } def jsonFormat[A :JF, B :JF, C :JF, D :JF, E :JF, F :JF, G :JF, H :JF, I :JF, J :JF, K :JF, L :JF, M :JF, N :JF, O :JF, T <: Product] (construct: (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O) => T, a: String, b: String, c: String, d: String, e: String, f: String, g: String, h: String, i: String, j: String, k: String, l: String, m: String, n: String, - o: String) = new RootJsonFormat[T]{ + o: String): RootJsonFormat[T] = new RootJsonFormat[T]{ def write(p: T) = JsObject( productElement2Field[A](a, p, 0, productElement2Field[B](b, p, 1, @@ -401,7 +473,7 @@ trait ProductFormats { case _ => (fieldName, writer.write(value)) :: rest } } - + private def fromField[T](value: JsValue, fieldName: String)(implicit reader: JsonReader[T]) = { value match { case x: JsObject => @@ -419,6 +491,22 @@ trait ProductFormats { case _ => deserializationError("Object expected") } } + + protected def extractFieldNames(classManifest: ClassManifest[_]): Array[String] = { + val clazz = classManifest.erasure + try { + val copyDefaultMethods = clazz.getMethods.filter(_.getName.startsWith("copy$default$")) + val fields = clazz.getDeclaredFields.filterNot(_.getName.startsWith("$")) + if (copyDefaultMethods.length != fields.length) + sys.error("Case class declares additional fields") + if (fields.zip(copyDefaultMethods).exists { case (f, m) => f.getType != m.getReturnType }) + sys.error("Cannot determine field order") + fields.map(_.getName) + } catch { + case ex => throw new RuntimeException("Cannot automatically determine case class field names and order, " + + "please use the 'jsonFormat' overload with explicit field name specification", ex) + } + } } /** diff --git a/src/test/scala/cc/spray/json/ProductFormatsSpec.scala b/src/test/scala/cc/spray/json/ProductFormatsSpec.scala index 6ec1611..c34f491 100644 --- a/src/test/scala/cc/spray/json/ProductFormatsSpec.scala +++ b/src/test/scala/cc/spray/json/ProductFormatsSpec.scala @@ -25,8 +25,8 @@ class ProductFormatsSpec extends Specification { trait TestProtocol { this: DefaultJsonProtocol => - implicit val test2Format = jsonFormat(Test2, "a", "b") - implicit def test3Format[A: JsonFormat, B: JsonFormat] = jsonFormat(Test3.apply[A, B], "as", "bs") + implicit val test2Format = jsonFormat2(Test2) + implicit def test3Format[A: JsonFormat, B: JsonFormat] = jsonFormat2(Test3.apply[A, B]) } object TestProtocol1 extends DefaultJsonProtocol with TestProtocol object TestProtocol2 extends DefaultJsonProtocol with TestProtocol with NullOptions -- cgit v1.2.3 From 86a975a4857bf6ee207e30fe4a7fc427345fb08b Mon Sep 17 00:00:00 2001 From: Mathias Date: Wed, 1 Feb 2012 15:17:39 +0100 Subject: Update README --- README.markdown | 89 +++++++++++++++------------ src/test/scala/cc/spray/json/ReadmeSpec.scala | 6 +- 2 files changed, 53 insertions(+), 42 deletions(-) (limited to 'src/test/scala/cc/spray') diff --git a/README.markdown b/README.markdown index 3ff1d02..cca4603 100644 --- a/README.markdown +++ b/README.markdown @@ -10,15 +10,16 @@ It sports the following features: ### Installation -_spray-json_ is available from the [scala-tools.org] repositories. -The latest release is `1.0.1` and is built against Scala 2.9.1. +_spray-json_ is available from the [repo.spray.cc] repositories. +The latest release is `1.1.0` and is built against Scala 2.9.1. -If you use SBT (0.7.x) you can include _spray-json_ in your project with +If you use SBT you can include _spray-json_ in your project with - val sprayJson = "cc.spray.json" %% "spray-json" % "1.0.1" % "compile" withSources() + "cc.spray" %% "spray-json" % "1.1.0" _spray-json_ has only one dependency: the parsing library [parboiled][] -(which is also a dependency of _spray-server_ and _spray-client_, so if you use _spray-json_ with either of them you are not incurring any additional dependency). +(which is also a dependency of _spray-server_ and _spray-client_, so if you use _spray-json_ with either of them you +are not incurring any additional dependency). ### Usage @@ -54,16 +55,24 @@ The way you normally do this is via a "JsonProtocol". ### JsonProtocol -_spray-json_ uses [SJSON]s Scala-idiomatic type-class-based approach to connect an existing type `T` with the logic how to (de)serialize its instances to and from JSON. (In fact _spray-json_ even reuses some of [SJSON]s code, see the 'Credits' section below). +_spray-json_ uses [SJSON]s Scala-idiomatic type-class-based approach to connect an existing type `T` with the logic how +to (de)serialize its instances to and from JSON. (In fact _spray-json_ even reuses some of [SJSON]s code, see the +'Credits' section below). This approach has the advantage of not requiring any change (or even access) to `T`s source code. All (de)serialization -logic is attached 'from the outside'. There is no reflection involved, so the resulting conversions are fast. Scalas excellent type inference reduces verbosity and boilerplate to a minimum, while the Scala compiler will make sure at compile time that you provided all required (de)serialization logic. +logic is attached 'from the outside'. There is no reflection involved, so the resulting conversions are fast. Scalas +excellent type inference reduces verbosity and boilerplate to a minimum, while the Scala compiler will make sure at +compile time that you provided all required (de)serialization logic. -In _spray-jsons_ terminology a 'JsonProtocol' is nothing but a bunch of implicit values of type `JsonFormat[T]`, whereby each `JsonFormat[T]` contains the logic of how to convert instance of `T` to and from JSON. All `JsonFormat[T]`s of a protocol need to be "mece" (mutually exclusive, collectively exhaustive), i.e. they are not allowed to overlap and together need to span all types required by the application. +In _spray-jsons_ terminology a 'JsonProtocol' is nothing but a bunch of implicit values of type `JsonFormat[T]`, whereby +each `JsonFormat[T]` contains the logic of how to convert instance of `T` to and from JSON. All `JsonFormat[T]`s of a +protocol need to be "mece" (mutually exclusive, collectively exhaustive), i.e. they are not allowed to overlap and +together need to span all types required by the application. This may sound more complicated than it is. -_spray-json_ comes with a `DefaultJsonProtocol`, which already covers all of Scalas value types as well as the most important reference and collection types. As long as your code uses nothing more than these you only need the `DefaultJsonProtocol`. -Here are the types already taken care of by the `DefaultJsonProtocol`: +_spray-json_ comes with a `DefaultJsonProtocol`, which already covers all of Scalas value types as well as the most +important reference and collection types. As long as your code uses nothing more than these you only need the +`DefaultJsonProtocol`. Here are the types already taken care of by the `DefaultJsonProtocol`: * Byte, Short, Int, Long, Float, Double, Char, Unit, Boolean * String, Symbol @@ -74,7 +83,8 @@ Here are the types already taken care of by the `DefaultJsonProtocol`: * collection.{Iterable, Seq, IndexedSeq, LinearSeq, Set} * JsValue -In most cases however you'll also want to convert types not covered by the `DefaultJsonProtocol`. In these cases you need to provide `JsonFormat[T]`s for your custom types. This is not hard at all. +In most cases however you'll also want to convert types not covered by the `DefaultJsonProtocol`. In these cases you +need to provide `JsonFormat[T]`s for your custom types. This is not hard at all. ### Providing JsonFormats for Case Classes @@ -84,7 +94,7 @@ If your custom type `T` is a case class then augmenting the `DefaultJsonProtocol case class Color(name: String, red: Int, green: Int, blue: Int) object MyJsonProtocol extends DefaultJsonProtocol { - implicit val colorFormat = jsonFormat(Color, "name", "red", "green", "blue") + implicit val colorFormat = jsonFormat4(Color) } import MyJsonProtocol._ @@ -92,26 +102,32 @@ If your custom type `T` is a case class then augmenting the `DefaultJsonProtocol val json = Color("CadetBlue", 95, 158, 160).toJson val color = json.convertTo[Color] -The `jsonFormat` method reduces the boilerplate to a minimum, just pass it the companion object of your case class as -well as the field names (in order) and it will return a ready-to-use `JsonFormat` for your type. -There is one quirk though: If you explicitly declare the companion object for your case class the notation above will +The `jsonFormatX` methods reduce the boilerplate to a minimum, just pass the right one the companion object of your +case class and it will return a ready-to-use `JsonFormat` for your type (the right one is the one matching the number +of arguments to your case class constructor, e.g. if your case class has 13 fields you need to use the `jsonFormat13` +method). The `jsonFormatX` methods try to extract the field names of your case class before calling the more general +`jsonFormat` overloads, which let you specify the field name manually. So, if spray-json has trouble determining the +field names or if your JSON objects use member names that differ from the case class fields you can also use +`jsonFormat` directly. + +There is one additional quirk: If you explicitly declare the companion object for your case class the notation above will stop working. You'll have to explicitly refer to the companion objects `apply` method to fix this: case class Color(name: String, red: Int, green: Int, blue: Int) object Color object MyJsonProtocol extends DefaultJsonProtocol { - implicit val colorFormat = jsonFormat(Color.apply, "name", "red", "green", "blue") + implicit val colorFormat = jsonFormat4(Color.apply) } -If your case class is generic in that it takes type parameters itself the `jsonFormat` method can also help you. +If your case class is generic in that it takes type parameters itself the `jsonFormat` methods can also help you. However, there is a little more boilerplate required as you need to add context bounds for all type parameters and explicitly refer to the case classes `apply` method as in this example: case class NamedList[A](name: String, items: List[A]) object MyJsonProtocol extends DefaultJsonProtocol { - implicit def namedListFormat[A :JsonFormat] = jsonFormat(NamedList.apply[A], "name", "items") + implicit def namedListFormat[A :JsonFormat] = jsonFormat2(NamedList.apply[A]) } @@ -124,13 +140,12 @@ Here is one way to do it: object MyJsonProtocol extends DefaultJsonProtocol { implicit object ColorJsonFormat extends JsonFormat[Color] { - def write(c: Color) = { + def write(c: Color) = JsArray(JsString(c.name), JsNumber(c.red), JsNumber(c.green), JsNumber(c.blue)) - } + def read(value: JsValue) = value match { - case JsArray(JsString(name) :: JsNumber(red) :: JsNumber(green) :: JsNumber(blue) :: Nil) => { + case JsArray(JsString(name) :: JsNumber(red) :: JsNumber(green) :: JsNumber(blue) :: Nil) => new Color(name, red.toInt, green.toInt, blue.toInt) - } case _ => deserializationError("Color expected") } } @@ -146,30 +161,26 @@ You need to know that the color components are ordered "red, green, blue". Another way would be to serialize `Color`s as JSON objects: - object MyJsonProtocol extends DefaultJsonProtocol { + object MyJsonProtocol extends DefaultJsonProtocol { implicit object ColorJsonFormat extends JsonFormat[Color] { def write(c: Color) = JsObject( - JsField("name", c.name), - JsField("red", c.red), - JsField("green", c.green), - JsField("blue", c.blue) + "name" -> JsString(c.name), + "red" -> JsNumber(c.red), + "green" -> JsNumber(c.green), + "blue" -> JsNumber(c.blue) ) - def read(value: JsValue) = value match { - case JsObject( - JsField("name", JsString(name)), - JsField("red", JsNumber(red)), - JsField("green", JsNumber(green)), - JsField("blue", JsNumber(blue)) - ) => { - new Color(name.value, red.value.toInt, green.value.toInt, blue.value.toInt) + def read(value: JsValue) = { + value.asJsObject.getFields("name", "red", "green", "blue") match { + case Seq(JsString(name), JsNumber(red), JsNumber(green), JsNumber(blue)) => + new Color(name, red.toInt, green.toInt, blue.toInt) + case _ => throw new DeserializationException("Color expected") } - case _ => deserializationError("Color expected") } } } -This is a bit more verbose in its definition and the resulting JSON but transports the field semantics over to the JSON side. -Note that this is the approach _spray-json_ uses for case classes. +This is a bit more verbose in its definition and the resulting JSON but transports the field semantics over to the +JSON side. Note that this is the approach _spray-json_ uses for case classes. ### JsonFormats for recursive Types @@ -214,7 +225,7 @@ _spray-json_ project under the project’s open source license. [JSON]: http://json.org [parboiled]: http://parboiled.org - [scala-tools.org]: http://scala-tools.org + [repo.spray.cc]: http://repo.spray.cc [SJSON]: https://github.com/debasishg/sjson [Databinder-Dispatch]: https://github.com/n8han/Databinder-Dispatch [APL 2.0]: http://www.apache.org/licenses/LICENSE-2.0 diff --git a/src/test/scala/cc/spray/json/ReadmeSpec.scala b/src/test/scala/cc/spray/json/ReadmeSpec.scala index e67312d..791720f 100644 --- a/src/test/scala/cc/spray/json/ReadmeSpec.scala +++ b/src/test/scala/cc/spray/json/ReadmeSpec.scala @@ -28,7 +28,7 @@ class ReadmeSpec extends Specification { val jsonAst = source.asJson jsonAst mustEqual JsObject("some" -> JsString("JSON source")) - val json2 = PrettyPrinter(jsonAst) + val json2 = jsonAst.prettyPrint json2 mustEqual """{ | "some": "JSON source" @@ -45,7 +45,7 @@ class ReadmeSpec extends Specification { "The case class example" should { "behave as expected" in { object MyJsonProtocol extends DefaultJsonProtocol { - implicit val colorFormat = jsonFormat(Color, "name", "red", "green", "blue") + implicit val colorFormat = jsonFormat4(Color) } import MyJsonProtocol._ color.toJson.convertTo[Color] mustEqual color @@ -60,7 +60,7 @@ class ReadmeSpec extends Specification { JsArray(JsString(c.name), JsNumber(c.red), JsNumber(c.green), JsNumber(c.blue)) def read(value: JsValue) = value match { - case JsArray(Seq(JsString(name), JsNumber(red), JsNumber(green), JsNumber(blue))) => + case JsArray(JsString(name) :: JsNumber(red) :: JsNumber(green) :: JsNumber(blue) :: Nil) => new Color(name, red.toInt, green.toInt, blue.toInt) case _ => deserializationError("Color expected") } -- cgit v1.2.3