diff options
author | Mathias <mathias@spray.cc> | 2012-02-03 15:52:43 +0100 |
---|---|---|
committer | Mathias <mathias@spray.cc> | 2012-02-03 15:52:43 +0100 |
commit | 25b428758a36145e85033fba059ce85aad224929 (patch) | |
tree | b0ea9205790f8f8148f4e655fd3569d24c7fc4fc /src | |
parent | 3df2a713d9251d62efcbf12165c6b81460a02047 (diff) | |
parent | 129760aa1a75c62e6cd2088fa6ad37ff7ee35d68 (diff) | |
download | spray-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')
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) } } |