summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main/scala/cc/spray/json/CollectionFormats.scala9
-rw-r--r--src/main/scala/cc/spray/json/CompactPrinter.scala6
-rw-r--r--src/main/scala/cc/spray/json/DeserializationException.scala2
-rw-r--r--src/main/scala/cc/spray/json/JsValue.scala21
-rw-r--r--src/main/scala/cc/spray/json/JsonParser.scala4
-rw-r--r--src/main/scala/cc/spray/json/PrettyPrinter.scala6
-rw-r--r--src/main/scala/cc/spray/json/ProductFormats.scala28
-rw-r--r--src/main/scala/cc/spray/json/package.scala2
-rw-r--r--src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala4
-rw-r--r--src/test/scala/cc/spray/json/CollectionFormatsSpec.scala2
-rw-r--r--src/test/scala/cc/spray/json/CompactPrinterSpec.scala4
-rw-r--r--src/test/scala/cc/spray/json/JsonParserSpec.scala6
-rw-r--r--src/test/scala/cc/spray/json/ProductFormatsSpec.scala18
-rw-r--r--src/test/scala/cc/spray/json/ReadmeSpec.scala2
14 files changed, 55 insertions, 59 deletions
diff --git a/src/main/scala/cc/spray/json/CollectionFormats.scala b/src/main/scala/cc/spray/json/CollectionFormats.scala
index a17c43f..c6c3403 100644
--- a/src/main/scala/cc/spray/json/CollectionFormats.scala
+++ b/src/main/scala/cc/spray/json/CollectionFormats.scala
@@ -47,15 +47,16 @@ trait CollectionFormats {
*/
implicit def mapFormat[K :JsonFormat, V :JsonFormat] = new RootJsonFormat[Map[K, V]] {
def write(m: Map[K, V]) = JsObject {
- m.toList.map { t =>
- t._1.toJson match {
- case JsString(x) => JsField(x, t._2.toJson)
+ m.map { field =>
+ field._1.toJson match {
+ case JsString(x) => x -> field._2.toJson
case x => throw new SerializationException("Map key must be formatted as JsString, not '" + x + "'")
}
}
}
def read(value: JsValue) = value match {
- case JsObject(fields) => fields.map(field => (JsString(field.name).convertTo[K], field.value.convertTo[V])).toMap
+ case JsObject(fields) =>
+ fields.map(field => (JsString(field._1).convertTo[K], field._2.convertTo[V])) (collection.breakOut)
case x => throw new DeserializationException("Expected Map as JsObject, but got " + x)
}
}
diff --git a/src/main/scala/cc/spray/json/CompactPrinter.scala b/src/main/scala/cc/spray/json/CompactPrinter.scala
index 329c08e..467160d 100644
--- a/src/main/scala/cc/spray/json/CompactPrinter.scala
+++ b/src/main/scala/cc/spray/json/CompactPrinter.scala
@@ -31,12 +31,12 @@ trait CompactPrinter extends JsonPrinter {
}
}
- private def printObject(members: List[JsField], sb: StringBuilder) {
+ private def printObject(members: Map[String, JsValue], sb: StringBuilder) {
sb.append('{')
printSeq(members, sb.append(',')) { m =>
- printString(m.name, sb)
+ printString(m._1, sb)
sb.append(':')
- print(m.value, sb)
+ print(m._2, sb)
}
sb.append('}')
}
diff --git a/src/main/scala/cc/spray/json/DeserializationException.scala b/src/main/scala/cc/spray/json/DeserializationException.scala
index 283d996..b317a14 100644
--- a/src/main/scala/cc/spray/json/DeserializationException.scala
+++ b/src/main/scala/cc/spray/json/DeserializationException.scala
@@ -16,4 +16,4 @@
package cc.spray.json
-class DeserializationException(msg: String) extends RuntimeException(msg) \ No newline at end of file
+class DeserializationException(msg: String, cause: Throwable = null) extends RuntimeException(msg, cause) \ No newline at end of file
diff --git a/src/main/scala/cc/spray/json/JsValue.scala b/src/main/scala/cc/spray/json/JsValue.scala
index cc1af91..f4dbf91 100644
--- a/src/main/scala/cc/spray/json/JsValue.scala
+++ b/src/main/scala/cc/spray/json/JsValue.scala
@@ -18,7 +18,9 @@
package cc.spray.json
-import collection.mutable.ListBuffer
+import collection.mutable.{LinkedHashMap, ListBuffer}
+import collection.immutable.ListMap
+
/**
* The general type of a JSON AST node.
@@ -37,23 +39,14 @@ sealed trait JsValue {
/**
* A JSON object.
*/
-case class JsObject(fields: List[JsField]) extends JsValue {
- lazy val asMap: Map[String, JsValue] = {
- val b = Map.newBuilder[String, JsValue]
- for (JsField(name, value) <- fields) b += ((name, value))
- b.result()
- }
-}
+case class JsObject(fields: Map[String, JsValue]) extends JsValue
object JsObject {
- def apply(members: JsField*) = new JsObject(members.toList)
+ // we use a ListMap in order to preserve the field order
+ def apply(members: JsField*) = new JsObject(ListMap(members: _*))
+ def apply(members: List[JsField]) = new JsObject(ListMap(members: _*))
}
/**
- * The members/fields of a JSON object.
- */
-case class JsField(name: String, value: JsValue) extends JsValue
-
-/**
* A JSON array.
*/
case class JsArray(elements: List[JsValue]) extends JsValue
diff --git a/src/main/scala/cc/spray/json/JsonParser.scala b/src/main/scala/cc/spray/json/JsonParser.scala
index b93a9a6..21b1d68 100644
--- a/src/main/scala/cc/spray/json/JsonParser.scala
+++ b/src/main/scala/cc/spray/json/JsonParser.scala
@@ -31,10 +31,10 @@ object JsonParser extends Parser {
def Json = rule { WhiteSpace ~ Value ~ EOI }
def JsonObject: Rule1[JsObject] = rule {
- "{ " ~ zeroOrMore(Pair, separator = ", ") ~ "} " ~~> (JsObject(_))
+ "{ " ~ zeroOrMore(Pair, separator = ", ") ~ "} " ~~> (JsObject(_ :_*))
}
- def Pair = rule { JsonStringUnwrapped ~ ": " ~ Value ~~> (JsField(_, _)) }
+ def Pair = rule { JsonStringUnwrapped ~ ": " ~ Value ~~> ((_, _)) }
def Value: Rule1[JsValue] = rule {
JsonString | JsonNumber | JsonObject | JsonArray | JsonTrue | JsonFalse | JsonNull
diff --git a/src/main/scala/cc/spray/json/PrettyPrinter.scala b/src/main/scala/cc/spray/json/PrettyPrinter.scala
index 93ac0b6..429c984 100644
--- a/src/main/scala/cc/spray/json/PrettyPrinter.scala
+++ b/src/main/scala/cc/spray/json/PrettyPrinter.scala
@@ -37,13 +37,13 @@ trait PrettyPrinter extends JsonPrinter {
}
}
- private def printObject(members: List[JsField], sb: StringBuilder, indent: Int) {
+ private def printObject(members: Map[String, JsValue], sb: StringBuilder, indent: Int) {
sb.append("{\n")
printSeq(members, sb.append(",\n")) { m =>
printIndent(sb, indent + Indent)
- printString(m.name, sb)
+ printString(m._1, sb)
sb.append(": ")
- print(m.value, sb, indent + Indent)
+ print(m._2, sb, indent + Indent)
}
sb.append('\n')
printIndent(sb, indent)
diff --git a/src/main/scala/cc/spray/json/ProductFormats.scala b/src/main/scala/cc/spray/json/ProductFormats.scala
index 90c3349..810a6ec 100644
--- a/src/main/scala/cc/spray/json/ProductFormats.scala
+++ b/src/main/scala/cc/spray/json/ProductFormats.scala
@@ -401,24 +401,24 @@ trait ProductFormats {
val value = p.productElement(ix).asInstanceOf[T]
writer match {
case _: OptionFormat[_] if (value == None) => rest
- case _ => JsField(fieldName, writer.write(value)) :: rest
+ case _ => (fieldName, writer.write(value)) :: rest
}
}
private def fromField[T](value: JsValue, fieldName: String)(implicit reader: JsonReader[T]) = {
- @tailrec
- def getFrom(fields: List[JsField]): T = {
- if (fields.isEmpty) {
- if (reader.isInstanceOf[OptionFormat[_]]) None.asInstanceOf[T]
- else throw new DeserializationException("Object is missing required member '" + fieldName + "'")
- } else if (fields.head.name == fieldName) {
- reader.read(fields.head.value)
- } else {
- getFrom(fields.tail)
- }
- }
value match {
- case x: JsObject => getFrom(x.fields)
+ case x: JsObject =>
+ var fieldFound = false
+ try {
+ val fieldValue = x.fields(fieldName)
+ fieldFound = true
+ reader.read(fieldValue)
+ }
+ catch {
+ case e: NoSuchElementException if !fieldFound =>
+ if (reader.isInstanceOf[OptionFormat[_]]) None.asInstanceOf[T]
+ else throw new DeserializationException("Object is missing required member '" + fieldName + "'", e)
+ }
case _ => throw new DeserializationException("Object expected")
}
}
@@ -437,6 +437,6 @@ trait NullOptions extends ProductFormats {
override protected def productElement2Field[T](fieldName: String, p: Product, ix: Int, rest: List[JsField])
(implicit writer: JsonWriter[T]) = {
val value = p.productElement(ix).asInstanceOf[T]
- JsField(fieldName, writer.write(value)) :: rest
+ (fieldName, writer.write(value)) :: rest
}
} \ No newline at end of file
diff --git a/src/main/scala/cc/spray/json/package.scala b/src/main/scala/cc/spray/json/package.scala
index ee50ec0..f080312 100644
--- a/src/main/scala/cc/spray/json/package.scala
+++ b/src/main/scala/cc/spray/json/package.scala
@@ -17,6 +17,8 @@
package cc.spray
package object json {
+
+ type JsField = (String, JsValue)
def jsonReader[T](implicit reader: JsonReader[T]) = reader
def jsonWriter[T](implicit writer: JsonWriter[T]) = writer
diff --git a/src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala b/src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala
index 1f5362a..a9fb2d1 100644
--- a/src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala
+++ b/src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala
@@ -10,7 +10,7 @@ class AdditionalFormatsSpec extends Specification {
implicit def containerReader[T :JsonFormat] = lift {
new JsonReader[Container[T]] {
def read(value: JsValue) = value match {
- case JsObject(JsField("content", obj: JsValue) :: Nil) => Container(Some(jsonReader[T].read(obj)))
+ case JsObject(fields) if fields.contains("content") => Container(Some(jsonReader[T].read(fields("content"))))
case _ => throw new DeserializationException("Unexpected format: " + value.toString)
}
}
@@ -20,7 +20,7 @@ class AdditionalFormatsSpec extends Specification {
object WriterProtocol extends DefaultJsonProtocol {
implicit def containerWriter[T :JsonFormat] = lift {
new JsonWriter[Container[T]] {
- def write(obj: Container[T]) = JsObject(JsField("content", obj.inner.toJson))
+ def write(obj: Container[T]) = JsObject("content" -> obj.inner.toJson)
}
}
}
diff --git a/src/test/scala/cc/spray/json/CollectionFormatsSpec.scala b/src/test/scala/cc/spray/json/CollectionFormatsSpec.scala
index f5eafa8..fe5e4bb 100644
--- a/src/test/scala/cc/spray/json/CollectionFormatsSpec.scala
+++ b/src/test/scala/cc/spray/json/CollectionFormatsSpec.scala
@@ -29,7 +29,7 @@ class CollectionFormatsSpec extends Specification with DefaultJsonProtocol {
"The mapFormat" should {
val map = Map("a" -> 1, "b" -> 2, "c" -> 3)
- val json = JsObject(JsField("a", JsNumber(1)), JsField("b", JsNumber(2)), JsField("c", JsNumber(3)))
+ val json = JsObject("a" -> JsNumber(1), "b" -> JsNumber(2), "c" -> JsNumber(3))
"convert a Map[String, Long] to a JsObject" in {
map.toJson mustEqual json
}
diff --git a/src/test/scala/cc/spray/json/CompactPrinterSpec.scala b/src/test/scala/cc/spray/json/CompactPrinterSpec.scala
index f2d92fc..948ee4d 100644
--- a/src/test/scala/cc/spray/json/CompactPrinterSpec.scala
+++ b/src/test/scala/cc/spray/json/CompactPrinterSpec.scala
@@ -36,11 +36,11 @@ class CompactPrinterSpec extends Specification {
CompactPrinter(JsString("\"\\\b\f\n\r\t\u12AB")) mustEqual """"\"\\\b\f\n\r\t""" + "\\u12ab\""
}
"properly print a simple JsObject" in (
- CompactPrinter(JsObject(JsField("key", JsNumber(42)), JsField("key2", JsString("value"))))
+ CompactPrinter(JsObject("key" -> JsNumber(42), "key2" -> JsString("value")))
mustEqual """{"key":42,"key2":"value"}"""
)
"properly print a simple JsArray" in (
- CompactPrinter(JsArray(JsNull, JsNumber(1.23), JsObject(JsField("key", JsBoolean(true)))))
+ CompactPrinter(JsArray(JsNull, JsNumber(1.23), JsObject("key" -> JsBoolean(true))))
mustEqual """[null,1.23,{"key":true}]"""
)
"properly print a JSON padding (JSONP) if requested" in {
diff --git a/src/test/scala/cc/spray/json/JsonParserSpec.scala b/src/test/scala/cc/spray/json/JsonParserSpec.scala
index f030013..c298617 100644
--- a/src/test/scala/cc/spray/json/JsonParserSpec.scala
+++ b/src/test/scala/cc/spray/json/JsonParserSpec.scala
@@ -35,16 +35,16 @@ class JsonParserSpec extends Specification {
}
"properly parse a simple JsObject" in (
JsonParser(""" { "key" :42, "key2": "value" }""") mustEqual
- JsObject(JsField("key", JsNumber(42)), JsField("key2", JsString("value")))
+ JsObject("key" -> JsNumber(42), "key2" -> JsString("value"))
)
"properly parse a simple JsArray" in (
JsonParser("""[null, 1.23 ,{"key":true } ] """) mustEqual
- JsArray(JsNull, JsNumber(1.23), JsObject(JsField("key", JsBoolean(true))))
+ JsArray(JsNull, JsNumber(1.23), JsObject("key" -> JsBoolean(true)))
)
"be reentrant" in {
val largeJsonSource = FileUtils.readAllCharsFromResource("test.json")
List.fill(20)(largeJsonSource).par.map(JsonParser(_)).toList.map {
- _.asInstanceOf[JsObject].asMap("questions").asInstanceOf[JsArray].elements.size
+ _.asInstanceOf[JsObject].fields("questions").asInstanceOf[JsArray].elements.size
} mustEqual List.fill(20)(100)
}
}
diff --git a/src/test/scala/cc/spray/json/ProductFormatsSpec.scala b/src/test/scala/cc/spray/json/ProductFormatsSpec.scala
index 059b931..6e389c7 100644
--- a/src/test/scala/cc/spray/json/ProductFormatsSpec.scala
+++ b/src/test/scala/cc/spray/json/ProductFormatsSpec.scala
@@ -18,7 +18,7 @@ class ProductFormatsSpec extends Specification {
"A JsonFormat created with `jsonFormat`, for a case class with 2 elements," should {
import TestProtocol1._
val obj = Test2(42, Some(4.2))
- val json = JsObject(JsField("a", JsNumber(42)), JsField("b", JsNumber(4.2)))
+ val json = JsObject("a" -> JsNumber(42), "b" -> JsNumber(4.2))
"convert to a respective JsObject" in {
obj.toJson mustEqual json
}
@@ -26,20 +26,20 @@ class ProductFormatsSpec extends Specification {
json.convertTo[Test2] mustEqual obj
}
"throw a DeserializationException if the JsObject does not all required members" in (
- JsObject(JsField("b", JsNumber(4.2))).convertTo[Test2] must
+ JsObject("b" -> JsNumber(4.2)).convertTo[Test2] must
throwA(new DeserializationException("Object is missing required member 'a'"))
)
"not require the presence of optional fields for deserialization" in {
- JsObject(JsField("a", JsNumber(42))).convertTo[Test2] mustEqual Test2(42, None)
+ JsObject("a" -> JsNumber(42)).convertTo[Test2] mustEqual Test2(42, None)
}
"not render `None` members during serialization" in {
- Test2(42, None).toJson mustEqual JsObject(JsField("a", JsNumber(42)))
+ Test2(42, None).toJson mustEqual JsObject("a" -> JsNumber(42))
}
"ignore additional members during deserialization" in {
- JsObject(JsField("a", JsNumber(42)), JsField("b", JsNumber(4.2)), JsField("c", JsString('no))).convertTo[Test2] mustEqual obj
+ JsObject("a" -> JsNumber(42), "b" -> JsNumber(4.2), "c" -> JsString('no)).convertTo[Test2] mustEqual obj
}
"not depend on any specific member order for deserialization" in {
- JsObject(JsField("b", JsNumber(4.2)), JsField("a", JsNumber(42))).convertTo[Test2] mustEqual obj
+ JsObject("b" -> JsNumber(4.2), "a" -> JsNumber(42)).convertTo[Test2] mustEqual obj
}
"throw a DeserializationException if the JsValue is not a JsObject" in (
JsNull.convertTo[Test2] must throwA(new DeserializationException("Object expected"))
@@ -49,7 +49,7 @@ class ProductFormatsSpec extends Specification {
"A JsonProtocol mixing in NullOptions" should {
"render `None` members to `null`" in {
import TestProtocol2._
- Test2(42, None).toJson mustEqual JsObject(JsField("a", JsNumber(42)), JsField("b", JsNull))
+ Test2(42, None).toJson mustEqual JsObject("a" -> JsNumber(42), "b" -> JsNull)
}
}
@@ -57,8 +57,8 @@ class ProductFormatsSpec extends Specification {
import TestProtocol1._
val obj = Test3(42 :: 43 :: Nil, "x" :: "y" :: "z" :: Nil)
val json = JsObject(
- JsField("as", JsArray(JsNumber(42), JsNumber(43))),
- JsField("bs", JsArray(JsString("x"), JsString("y"), JsString("z")))
+ "as" -> JsArray(JsNumber(42), JsNumber(43)),
+ "bs" -> JsArray(JsString("x"), JsString("y"), JsString("z"))
)
"convert to a respective JsObject" in {
obj.toJson mustEqual json
diff --git a/src/test/scala/cc/spray/json/ReadmeSpec.scala b/src/test/scala/cc/spray/json/ReadmeSpec.scala
index b1c6674..ace94b1 100644
--- a/src/test/scala/cc/spray/json/ReadmeSpec.scala
+++ b/src/test/scala/cc/spray/json/ReadmeSpec.scala
@@ -10,7 +10,7 @@ class ReadmeSpec extends Specification {
val json = """{ "some": "JSON source" }"""
val jsonAst = JsonParser(json)
- jsonAst mustEqual JsObject(JsField("some", JsString("JSON source")))
+ jsonAst mustEqual JsObject("some" -> JsString("JSON source"))
val json2 = PrettyPrinter(jsonAst)
json2 mustEqual