summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMathias <mathias@spray.cc>2012-02-03 15:52:43 +0100
committerMathias <mathias@spray.cc>2012-02-03 15:52:43 +0100
commit25b428758a36145e85033fba059ce85aad224929 (patch)
treeb0ea9205790f8f8148f4e655fd3569d24c7fc4fc /src
parent3df2a713d9251d62efcbf12165c6b81460a02047 (diff)
parent129760aa1a75c62e6cd2088fa6ad37ff7ee35d68 (diff)
downloadspray-json-25b428758a36145e85033fba059ce85aad224929.tar.gz
spray-json-25b428758a36145e85033fba059ce85aad224929.tar.bz2
spray-json-25b428758a36145e85033fba059ce85aad224929.zip
Merge branch 'release/1.1.0'v1.1.0
Diffstat (limited to 'src')
-rw-r--r--src/main/ls/1.1.0.json22
-rw-r--r--src/main/scala/cc/spray/json/AdditionalFormats.scala30
-rw-r--r--src/main/scala/cc/spray/json/BasicFormats.scala28
-rw-r--r--src/main/scala/cc/spray/json/CollectionFormats.scala32
-rw-r--r--src/main/scala/cc/spray/json/CompactPrinter.scala6
-rw-r--r--src/main/scala/cc/spray/json/DeserializationException.scala19
-rw-r--r--src/main/scala/cc/spray/json/JsValue.scala90
-rw-r--r--src/main/scala/cc/spray/json/JsonFormat.scala18
-rw-r--r--src/main/scala/cc/spray/json/JsonParser.scala6
-rw-r--r--src/main/scala/cc/spray/json/PrettyPrinter.scala6
-rw-r--r--src/main/scala/cc/spray/json/ProductFormats.scala175
-rw-r--r--src/main/scala/cc/spray/json/SerializationException.scala19
-rw-r--r--src/main/scala/cc/spray/json/StandardFormats.scala55
-rw-r--r--src/main/scala/cc/spray/json/package.scala21
-rw-r--r--src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala38
-rw-r--r--src/test/scala/cc/spray/json/BasicFormatsSpec.scala70
-rw-r--r--src/test/scala/cc/spray/json/CollectionFormatsSpec.scala28
-rw-r--r--src/test/scala/cc/spray/json/CompactPrinterSpec.scala20
-rw-r--r--src/test/scala/cc/spray/json/CustomFormatSpec.scala45
-rw-r--r--src/test/scala/cc/spray/json/JsonParserSpec.scala22
-rw-r--r--src/test/scala/cc/spray/json/PrettyPrinterSpec.scala16
-rw-r--r--src/test/scala/cc/spray/json/ProductFormatsSpec.scala44
-rw-r--r--src/test/scala/cc/spray/json/ReadmeSpec.scala84
-rw-r--r--src/test/scala/cc/spray/json/StandardFormatsSpec.scala28
24 files changed, 635 insertions, 287 deletions
diff --git a/src/main/ls/1.1.0.json b/src/main/ls/1.1.0.json
new file mode 100644
index 0000000..9504e42
--- /dev/null
+++ b/src/main/ls/1.1.0.json
@@ -0,0 +1,22 @@
+
+{
+ "organization":"cc.spray",
+ "name":"spray-json",
+ "version":"1.1.0",
+ "description":"A Scala library for easy and idiomatic JSON (de)serialization",
+ "site":"https://github.com/spray/spray-json",
+ "tags":["json"],
+ "docs":"http://spray.github.com/spray/api/spray-json/",
+ "licenses": [{
+ "name": "Apache 2",
+ "url": "http://www.apache.org/licenses/LICENSE-2.0.txt"
+ }],
+ "resolvers": ["http://repo.spray.cc"],
+ "dependencies": [{
+ "organization":"org.parboiled",
+ "name": "parboiled-scala",
+ "version": "1.0.2"
+ }],
+ "scalas": ["2.9.1"],
+ "sbt": false
+} \ No newline at end of file
diff --git a/src/main/scala/cc/spray/json/AdditionalFormats.scala b/src/main/scala/cc/spray/json/AdditionalFormats.scala
index db9d3ba..889fdaf 100644
--- a/src/main/scala/cc/spray/json/AdditionalFormats.scala
+++ b/src/main/scala/cc/spray/json/AdditionalFormats.scala
@@ -36,6 +36,12 @@ trait AdditionalFormats {
}
/**
+ * Constructs a RootJsonFormat from its two parts, RootJsonReader and RootJsonWriter.
+ */
+ def rootJsonFormat[T](reader: RootJsonReader[T], writer: RootJsonWriter[T]) =
+ rootFormat(jsonFormat(reader, writer))
+
+ /**
* Turns a JsonWriter into a JsonFormat that throws an UnsupportedOperationException for reads.
*/
def lift[T](writer :JsonWriter[T]) = new JsonFormat[T] {
@@ -45,6 +51,12 @@ trait AdditionalFormats {
}
/**
+ * Turns a RootJsonWriter into a RootJsonFormat that throws an UnsupportedOperationException for reads.
+ */
+ def lift[T](writer :RootJsonWriter[T]): RootJsonFormat[T] =
+ rootFormat(lift(writer :JsonWriter[T]))
+
+ /**
* Turns a JsonReader into a JsonFormat that throws an UnsupportedOperationException for writes.
*/
def lift[T <: AnyRef](reader :JsonReader[T]) = new JsonFormat[T] {
@@ -54,21 +66,35 @@ trait AdditionalFormats {
}
/**
+ * Turns a RootJsonReader into a RootJsonFormat that throws an UnsupportedOperationException for writes.
+ */
+ def lift[T <: AnyRef](reader :RootJsonReader[T]): RootJsonFormat[T] =
+ rootFormat(lift(reader :JsonReader[T]))
+
+ /**
* Lazy wrapper around serialization. Useful when you want to serialize (mutually) recursive structures.
*/
- def lazyFormat[T](format: => JsonFormat[T]) = new JsonFormat[T]{
+ def lazyFormat[T](format: => JsonFormat[T]) = new JsonFormat[T] {
lazy val delegate = format;
def write(x: T) = delegate.write(x);
def read(value: JsValue) = delegate.read(value);
}
/**
+ * Explicitly turns a JsonFormat into a RootJsonFormat.
+ */
+ def rootFormat[T](format: JsonFormat[T]) = new RootJsonFormat[T] {
+ def write(obj: T) = format.write(obj)
+ def read(json: JsValue) = format.read(json)
+ }
+
+ /**
* Wraps an existing JsonReader with Exception protection.
*/
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/BasicFormats.scala b/src/main/scala/cc/spray/json/BasicFormats.scala
index e16b4ca..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 _ => throw new DeserializationException("Int expected")
+ 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 _ => throw new DeserializationException("Long expected")
+ case x => deserializationError("Expected Long as JsNumber, but got " + x)
}
}
@@ -42,7 +42,8 @@ trait BasicFormats {
def write(x: Float) = JsNumber(x)
def read(value: JsValue) = value match {
case JsNumber(x) => x.floatValue
- case _ => throw new DeserializationException("Float expected")
+ case JsNull => Float.NaN
+ case x => deserializationError("Expected Float as JsNumber, but got " + x)
}
}
@@ -50,7 +51,8 @@ trait BasicFormats {
def write(x: Double) = JsNumber(x)
def read(value: JsValue) = value match {
case JsNumber(x) => x.doubleValue
- case _ => throw new DeserializationException("Double expected")
+ case JsNull => Double.NaN
+ case x => deserializationError("Expected Double as JsNumber, but got " + x)
}
}
@@ -58,7 +60,7 @@ trait BasicFormats {
def write(x: Byte) = JsNumber(x)
def read(value: JsValue) = value match {
case JsNumber(x) => x.byteValue
- case _ => throw new DeserializationException("Byte expected")
+ case x => deserializationError("Expected Byte as JsNumber, but got " + x)
}
}
@@ -66,7 +68,7 @@ trait BasicFormats {
def write(x: Short) = JsNumber(x)
def read(value: JsValue) = value match {
case JsNumber(x) => x.shortValue
- case _ => throw new DeserializationException("Short expected")
+ case x => deserializationError("Expected Short as JsNumber, but got " + x)
}
}
@@ -74,7 +76,7 @@ trait BasicFormats {
def write(x: BigDecimal) = JsNumber(x)
def read(value: JsValue) = value match {
case JsNumber(x) => x
- case _ => throw new DeserializationException("String expected")
+ case x => deserializationError("Expected BigDecimal as JsNumber, but got " + x)
}
}
@@ -82,7 +84,7 @@ trait BasicFormats {
def write(x: BigInt) = JsNumber(x)
def read(value: JsValue) = value match {
case JsNumber(x) => x.toBigInt
- case _ => throw new DeserializationException("BigInt expected")
+ case x => deserializationError("Expected BigInt as JsNumber, but got " + x)
}
}
@@ -96,7 +98,7 @@ trait BasicFormats {
def read(value: JsValue) = value match {
case JsTrue => true
case JsFalse => false
- case _ => throw new DeserializationException("Boolean expected")
+ case x => deserializationError("Expected JsBoolean, but got " + x)
}
}
@@ -104,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 _ => throw new DeserializationException("Char expected")
+ case x => deserializationError("Expected Char as single-character JsString, but got " + x)
}
}
@@ -112,7 +114,7 @@ trait BasicFormats {
def write(x: String) = JsString(x)
def read(value: JsValue) = value match {
case JsString(x) => x
- case _ => throw new DeserializationException("String expected")
+ case x => deserializationError("Expected String as JsString, but got " + x)
}
}
@@ -120,8 +122,8 @@ trait BasicFormats {
def write(x: Symbol) = JsString(x.name)
def read(value: JsValue) = value match {
case JsString(x) => Symbol(x)
- case _ => throw new DeserializationException("Symbol expected")
+ case x => deserializationError("Expected Symbol as JsString, but got " + x)
}
}
-} \ No newline at end of file
+}
diff --git a/src/main/scala/cc/spray/json/CollectionFormats.scala b/src/main/scala/cc/spray/json/CollectionFormats.scala
index 07592a8..93e41ab 100644
--- a/src/main/scala/cc/spray/json/CollectionFormats.scala
+++ b/src/main/scala/cc/spray/json/CollectionFormats.scala
@@ -22,22 +22,22 @@ trait CollectionFormats {
/**
* Supplies the JsonFormat for Lists.
*/
- implicit def listFormat[T :JsonFormat] = new JsonFormat[List[T]] {
+ implicit def listFormat[T :JsonFormat] = new RootJsonFormat[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 _ => throw new DeserializationException("List expected")
+ case JsArray(elements) => elements.map(_.convertTo[T])
+ case x => deserializationError("Expected List as JsArray, but got " + x)
}
}
/**
* Supplies the JsonFormat for Arrays.
*/
- implicit def arrayFormat[T :JsonFormat :ClassManifest] = new JsonFormat[Array[T]] {
+ implicit def arrayFormat[T :JsonFormat :ClassManifest] = new RootJsonFormat[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 _ => throw new DeserializationException("Array expected")
+ case JsArray(elements) => elements.map(_.convertTo[T]).toArray[T]
+ case x => deserializationError("Expected Array as JsArray, but got " + x)
}
}
@@ -45,18 +45,20 @@ trait CollectionFormats {
* Supplies the JsonFormat for Maps. The implicitly available JsonFormat for the key type K must
* always write JsStrings, otherwise a [[cc.spray.json.SerializationException]] will be thrown.
*/
- implicit def mapFormat[K :JsonFormat, V :JsonFormat] = new JsonFormat[Map[K, V]] {
+ 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).fromJson[K], field.value.fromJson[V])).toMap
- case _ => throw new DeserializationException("Map expected")
+ 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)
}
}
@@ -81,11 +83,11 @@ trait CollectionFormats {
* A JsonFormat construction helper that creates a JsonFormat for an Iterable type I from a builder function
* List => I.
*/
- def viaList[I <: Iterable[T], T :JsonFormat](f: List[T] => I): JsonFormat[I] = new JsonFormat[I] {
+ def viaList[I <: Iterable[T], T :JsonFormat](f: List[T] => I): RootJsonFormat[I] = new RootJsonFormat[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 _ => throw new DeserializationException("Collection expected")
+ case JsArray(elements) => f(elements.map(_.convertTo[T]))
+ case x => deserializationError("Expected Collection as JsArray, 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
deleted file mode 100644
index 283d996..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) extends RuntimeException(msg) \ 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 86dbb06..d14312b 100644
--- a/src/main/scala/cc/spray/json/JsValue.scala
+++ b/src/main/scala/cc/spray/json/JsValue.scala
@@ -18,76 +18,44 @@
package cc.spray.json
-import collection.mutable.ListBuffer
+import collection.immutable.ListMap
+
/**
* The general type of a JSON AST node.
*/
-sealed trait JsValue {
- override def toString = CompactPrinter(this)
+sealed abstract class JsValue {
+ override def toString = compactPrint
def toString(printer: (JsValue => String)) = printer(this)
- def fromJson[T :JsonReader]: T = jsonReader[T].read(this)
-}
-object JsValue {
+ def compactPrint = CompactPrinter(this)
+ def prettyPrint = PrettyPrinter(this)
+ def convertTo[T :JsonReader]: T = jsonReader[T].read(this)
/**
- * General converter to a JsValue.
- * Throws an IllegalArgumentException if the given value cannot be converted.
+ * Returns `this` if this JsValue is a JsObject, otherwise throws a DeserializationException with the given error msg.
*/
- 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")
- }
+ def asJsObject(errorMsg: String = "JSON object expected"): JsObject = deserializationError(errorMsg)
- 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
- }
+ /**
+ * 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
}
/**
* 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 {
+ override def asJsObject(errorMsg: String) = this
+ def getFields(fieldNames: String*): Seq[JsValue] = fieldNames.flatMap(fields.get)
}
object JsObject {
- def apply(members: JsField*) = new JsObject(members.toList)
-}
-
-/**
- * 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))
+ // 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: _*))
}
/**
@@ -103,6 +71,10 @@ object JsArray {
*/
case class JsString(value: String) extends JsValue
+object JsString {
+ def apply(value: Symbol) = new JsString(value.name)
+}
+
/**
* A JSON number.
*/
@@ -110,7 +82,11 @@ case class JsNumber(value: BigDecimal) extends JsValue
object JsNumber {
def apply(n: Int) = new JsNumber(BigDecimal(n))
def apply(n: Long) = new JsNumber(BigDecimal(n))
- def apply(n: Double) = new JsNumber(BigDecimal(n))
+ def apply(n: Double) = n match {
+ case n if n.isNaN => JsNull
+ case n if n.isInfinity => JsNull
+ case _ => new JsNumber(BigDecimal(n))
+ }
def apply(n: BigInt) = new JsNumber(BigDecimal(n))
def apply(n: String) = new JsNumber(BigDecimal(n))
}
@@ -118,7 +94,7 @@ object JsNumber {
/**
* JSON Booleans.
*/
-sealed trait JsBoolean extends JsValue {
+sealed abstract class JsBoolean extends JsValue {
def value: Boolean
}
object JsBoolean {
@@ -135,4 +111,4 @@ case object JsFalse extends JsBoolean {
/**
* The representation for JSON null.
*/
-case object JsNull extends JsValue \ No newline at end of file
+case object JsNull extends JsValue
diff --git a/src/main/scala/cc/spray/json/JsonFormat.scala b/src/main/scala/cc/spray/json/JsonFormat.scala
index c5bc53e..683a823 100644
--- a/src/main/scala/cc/spray/json/JsonFormat.scala
+++ b/src/main/scala/cc/spray/json/JsonFormat.scala
@@ -51,3 +51,21 @@ object JsonWriter {
* Provides the JSON deserialization and serialization for type T.
*/
trait JsonFormat[T] extends JsonReader[T] with JsonWriter[T]
+
+/**
+ * A special JsonReader capable of reading a legal JSON root object, i.e. either a JSON array or a JSON object.
+ */
+@implicitNotFound(msg = "Cannot find RootJsonReader or RootJsonFormat type class for ${T}")
+trait RootJsonReader[T] extends JsonReader[T]
+
+/**
+ * A special JsonWriter capable of writing a legal JSON root object, i.e. either a JSON array or a JSON object.
+ */
+@implicitNotFound(msg = "Cannot find RootJsonWriter or RootJsonFormat type class for ${T}")
+trait RootJsonWriter[T] extends JsonWriter[T]
+
+/**
+ * A special JsonFormat signaling that the format produces a legal JSON root object, i.e. either a JSON array
+ * or a JSON object.
+ */
+trait RootJsonFormat[T] extends JsonFormat[T] with RootJsonReader[T] with RootJsonWriter[T] \ No newline at end of file
diff --git a/src/main/scala/cc/spray/json/JsonParser.scala b/src/main/scala/cc/spray/json/JsonParser.scala
index 4a2d64b..21b1d68 100644
--- a/src/main/scala/cc/spray/json/JsonParser.scala
+++ b/src/main/scala/cc/spray/json/JsonParser.scala
@@ -31,16 +31,16 @@ 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
}
- def JsonString = rule { JsonStringUnwrapped ~~> JsString }
+ def JsonString = rule { JsonStringUnwrapped ~~> (JsString(_)) }
def JsonStringUnwrapped = rule { "\"" ~ Characters ~ "\" " ~~> (_.toString) }
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 d0263c8..1f26259 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.
@@ -17,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)
@@ -26,7 +23,11 @@ import annotation.tailrec
trait ProductFormats {
this: StandardFormats =>
- def jsonFormat[A :JF, T <: Product](construct: A => T, a: String) = new JF[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)
)
@@ -35,7 +36,11 @@ trait ProductFormats {
)
}
- def jsonFormat[A :JF, B :JF, T <: Product](construct: (A, B) => T, a: String, b: String) = new JF[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))
@@ -46,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 JF[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,
@@ -60,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 JF[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,
@@ -76,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 JF[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,
@@ -93,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 JF[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,
@@ -113,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 JF[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,
@@ -135,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 JF[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,
@@ -160,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 JF[T]{
+ (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): RootJsonFormat[T] = new RootJsonFormat[T]{
def write(p: T) = JsObject(
productElement2Field[A](a, p, 0,
productElement2Field[B](b, p, 1,
@@ -187,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 JF[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,
@@ -216,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 JF[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,
@@ -248,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 JF[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,
@@ -281,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 JF[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,
@@ -316,9 +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 JF[T]{
+ (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): RootJsonFormat[T] = new RootJsonFormat[T]{
def write(p: T) = JsObject(
productElement2Field[A](a, p, 0,
productElement2Field[B](b, p, 1,
@@ -353,9 +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 JF[T]{
+ (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): RootJsonFormat[T] = new RootJsonFormat[T]{
def write(p: T) = JsObject(
productElement2Field[A](a, p, 0,
productElement2Field[B](b, p, 1,
@@ -399,25 +470,41 @@ 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 _ => throw new DeserializationException("Object expected")
+ 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 deserializationError("Object is missing required member '" + fieldName + "'", e)
+ }
+ 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)
}
}
}
@@ -435,6 +522,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/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 1c44716..78bc539 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,75 +45,72 @@ 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")
- 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)
}
}
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)] {
+ implicit def tuple2Format[A :JF, B :JF] = new RootJsonFormat[(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 _ => throw new DeserializationException("Tuple2 expected")
+ case JsArray(a :: b :: Nil) => (a.convertTo[A], b.convertTo[B])
+ case x => deserializationError("Expected Tuple2 as JsArray, but got " + x)
}
}
- implicit def tuple3Format[A :JF, B :JF, C :JF] = new JF[(A, B, C)] {
+ implicit def tuple3Format[A :JF, B :JF, C :JF] = new RootJsonFormat[(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 _ => throw new DeserializationException("Tuple3 expected")
+ case JsArray(a :: b :: c :: Nil) => (a.convertTo[A], b.convertTo[B], c.convertTo[C])
+ case x => deserializationError("Expected Tuple3 as JsArray, but got " + x)
}
}
- implicit def tuple4Format[A :JF, B :JF, C :JF, D :JF] = new JF[(A, B, C, D)] {
+ implicit def tuple4Format[A :JF, B :JF, C :JF, D :JF] = new RootJsonFormat[(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 _ => throw new DeserializationException("Tuple4 expected")
+ case JsArray(a :: b :: c :: d :: Nil) => (a.convertTo[A], b.convertTo[B], c.convertTo[C], d.convertTo[D])
+ case x => deserializationError("Expected Tuple4 as JsArray, but got " + x)
}
}
implicit def tuple5Format[A :JF, B :JF, C :JF, D :JF, E :JF] = {
- new JF[(A, B, C, D, E)] {
+ new RootJsonFormat[(A, B, C, D, E)] {
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])
- }
- case _ => throw new DeserializationException("Tuple5 expected")
+ 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 => deserializationError("Expected Tuple5 as JsArray, but got " + x)
}
}
}
implicit def tuple6Format[A :JF, B :JF, C :JF, D :JF, E :JF, F: JF] = {
- new JF[(A, B, C, D, E, F)] {
+ new RootJsonFormat[(A, B, C, D, E, F)] {
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])
- }
- case _ => throw new DeserializationException("Tuple6 expected")
+ 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 => deserializationError("Expected Tuple6 as JsArray, but got " + x)
}
}
}
implicit def tuple7Format[A :JF, B :JF, C :JF, D :JF, E :JF, F: JF, G: JF] = {
- new JF[(A, B, C, D, E, F, G)] {
+ new RootJsonFormat[(A, B, C, D, E, F, G)] {
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])
- }
- case _ => throw new DeserializationException("Tuple7 expected")
+ 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 => 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 ee50ec0..ec12939 100644
--- a/src/main/scala/cc/spray/json/package.scala
+++ b/src/main/scala/cc/spray/json/package.scala
@@ -17,16 +17,29 @@
package cc.spray
package object json {
-
- def jsonReader[T](implicit reader: JsonReader[T]) = reader
+
+ type JsField = (String, JsValue)
+
+ 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)
-
+ implicit def pimpAny[T](any: T) = new PimpedAny(any)
+ implicit def pimpString(string: String) = new PimpedString(string)
}
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)
}
+
+ 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 7368128..f25c9d8 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._
@@ -10,8 +26,8 @@ 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 _ => throw new DeserializationException("Unexpected format: " + value.toString)
+ case JsObject(fields) if fields.contains("content") => Container(Some(jsonReader[T].read(fields("content"))))
+ case _ => deserializationError("Unexpected format: " + value.toString)
}
}
}
@@ -20,7 +36,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)
}
}
}
@@ -35,7 +51,21 @@ 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
+ """{"content":{"content":[1,2,3]}}""".asJson.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
diff --git a/src/test/scala/cc/spray/json/BasicFormatsSpec.scala b/src/test/scala/cc/spray/json/BasicFormatsSpec.scala
index fcae4bb..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._
@@ -9,7 +25,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 +34,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
}
}
@@ -26,8 +42,20 @@ 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
+ JsNumber(4.2f).convertTo[Float] mustEqual 4.2f
+ }
+ "convert a JsNull to a Float" in {
+ JsNull.convertTo[Float].isNaN mustEqual Float.NaN.isNaN
}
}
@@ -35,8 +63,20 @@ 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
+ JsNumber(4.2).convertTo[Double] mustEqual 4.2
+ }
+ "convert a JsNull to a Double" in {
+ JsNull.convertTo[Double].isNaN mustEqual Double.NaN.isNaN
}
}
@@ -45,7 +85,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
}
}
@@ -54,7 +94,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
}
}
@@ -63,7 +103,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)
}
}
@@ -72,7 +112,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)
}
}
@@ -81,15 +121,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 {
@@ -97,7 +137,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'
}
}
@@ -106,7 +146,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"
}
}
@@ -115,8 +155,8 @@ 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
}
}
-} \ No newline at end of file
+}
diff --git a/src/test/scala/cc/spray/json/CollectionFormatsSpec.scala b/src/test/scala/cc/spray/json/CollectionFormatsSpec.scala
index d0cb509..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._
@@ -12,7 +28,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,18 +39,18 @@ 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
}
}
"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("a" -> JsNumber(1), "b" -> JsNumber(2), "c" -> JsNumber(3))
"convert a Map[String, Long] to a JsObject" in {
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 +64,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 +75,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/CompactPrinterSpec.scala b/src/test/scala/cc/spray/json/CompactPrinterSpec.scala
index 3bc4870..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._
@@ -36,11 +52,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("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", 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/CustomFormatSpec.scala b/src/test/scala/cc/spray/json/CustomFormatSpec.scala
new file mode 100644
index 0000000..e8feb59
--- /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 {
+ """{ "name": "bob", "value": 42 }""".asJson.convertTo[MyType] mustEqual value
+ }
+ "support full round-trip (de)serialization" in {
+ value.toJson.convertTo[MyType] mustEqual value
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/test/scala/cc/spray/json/JsonParserSpec.scala b/src/test/scala/cc/spray/json/JsonParserSpec.scala
index 0860d05..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._
@@ -35,16 +51,16 @@ class JsonParserSpec extends Specification {
}
"properly parse a simple JsObject" in (
JsonParser(""" { "key" :42, "key2": "value" }""") mustEqual
- JsObject(JsField("key", 42), JsField("key2", "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", 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/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 9a692ec..c34f491 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._
@@ -9,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
@@ -18,38 +34,38 @@ 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("a" -> JsNumber(42), "b" -> JsNumber(4.2))
"convert to a respective JsObject" in {
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("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)).fromJson[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", 42))
+ Test2(42, None).toJson mustEqual JsObject("a" -> JsNumber(42))
}
"ignore additional members during deserialization" in {
- JsObject(JsField("a", 42), JsField("b", 4.2), JsField("c", 'no)).fromJson[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", 4.2), JsField("a", 42)).fromJson[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.fromJson[Test2] must throwA(new DeserializationException("Object expected"))
+ JsNull.convertTo[Test2] must throwA(new DeserializationException("Object expected"))
)
}
"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("a" -> JsNumber(42), "b" -> JsNull)
}
}
@@ -57,14 +73,14 @@ 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
}
"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..791720f 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._
@@ -8,58 +24,74 @@ class ReadmeSpec extends Specification {
"behave as expected" in {
import DefaultJsonProtocol._
- val json = """{ "some": "JSON source" }"""
- val jsonAst = JsonParser(json)
- jsonAst mustEqual JsObject(JsField("some", "JSON source"))
+ val source = """{ "some": "JSON source" }"""
+ val jsonAst = source.asJson
+ jsonAst mustEqual JsObject("some" -> JsString("JSON source"))
- val json2 = PrettyPrinter(jsonAst)
+ val json2 = jsonAst.prettyPrint
json2 mustEqual
"""{
| "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")
- }
+ implicit val colorFormat = jsonFormat4(Color)
+ }
import MyJsonProtocol._
-
- val json = Color("CadetBlue", 95, 158, 160).toJson
- val color = json.fromJson[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(JsString(name) :: JsNumber(red) :: JsNumber(green) :: JsNumber(blue) :: Nil) =>
new Color(name, red.toInt, green.toInt, blue.toInt)
+ case _ => deserializationError("Color expected")
+ }
+ }
+ }
+ import MyJsonProtocol._
+ 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")
}
- case _ => throw new DeserializationException("Color expected")
}
}
- }
+ }
import MyJsonProtocol._
-
- val json = Color("CadetBlue", 95, 158, 160).toJson
- val color = json.fromJson[Color]
-
- color mustEqual Color("CadetBlue", 95, 158, 160)
+ color.toJson.convertTo[Color] mustEqual color
}
}
diff --git a/src/test/scala/cc/spray/json/StandardFormatsSpec.scala b/src/test/scala/cc/spray/json/StandardFormatsSpec.scala
index ad9485c..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._
@@ -10,13 +26,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 +47,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 +59,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 +69,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)
}
}