summaryrefslogtreecommitdiff
path: root/shared/src/test/scala/spray
diff options
context:
space:
mode:
Diffstat (limited to 'shared/src/test/scala/spray')
-rw-r--r--shared/src/test/scala/spray/json/AdditionalFormatsSpec.scala73
-rw-r--r--shared/src/test/scala/spray/json/BasicFormatsSpec.scala168
-rw-r--r--shared/src/test/scala/spray/json/CollectionFormatsSpec.scala82
-rw-r--r--shared/src/test/scala/spray/json/CompactPrinterSpec.scala73
-rw-r--r--shared/src/test/scala/spray/json/CustomFormatSpec.scala45
-rw-r--r--shared/src/test/scala/spray/json/HashCodeCollider.scala26
-rw-r--r--shared/src/test/scala/spray/json/JsonParserSpec.scala155
-rw-r--r--shared/src/test/scala/spray/json/PrettyPrinterSpec.scala71
-rw-r--r--shared/src/test/scala/spray/json/RoundTripSpecs.scala77
-rw-r--r--shared/src/test/scala/spray/json/SortedPrinterSpec.scala65
-rw-r--r--shared/src/test/scala/spray/json/StandardFormatsSpec.scala121
11 files changed, 956 insertions, 0 deletions
diff --git a/shared/src/test/scala/spray/json/AdditionalFormatsSpec.scala b/shared/src/test/scala/spray/json/AdditionalFormatsSpec.scala
new file mode 100644
index 0000000..01127e6
--- /dev/null
+++ b/shared/src/test/scala/spray/json/AdditionalFormatsSpec.scala
@@ -0,0 +1,73 @@
+/*
+ * 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 spray.json
+
+import org.specs2.mutable._
+
+class AdditionalFormatsSpec extends Specification {
+
+ case class Container[A](inner: Option[A])
+
+ object ReaderProtocol extends DefaultJsonProtocol {
+ implicit def containerReader[T :JsonFormat] = lift {
+ new JsonReader[Container[T]] {
+ def read(value: JsValue) = value match {
+ case JsObject(fields) if fields.contains("content") => Container(Some(jsonReader[T].read(fields("content"))))
+ case _ => deserializationError("Unexpected format: " + value.toString)
+ }
+ }
+ }
+ }
+
+ object WriterProtocol extends DefaultJsonProtocol {
+ implicit def containerWriter[T :JsonFormat] = lift {
+ new JsonWriter[Container[T]] {
+ def write(obj: Container[T]) = JsObject("content" -> obj.inner.toJson)
+ }
+ }
+ }
+
+ "The liftJsonWriter" should {
+ val obj = Container(Some(Container(Some(List(1, 2, 3)))))
+
+ "properly write a Container[Container[List[Int]]] to JSON" in {
+ import WriterProtocol._
+ obj.toJson.toString mustEqual """{"content":{"content":[1,2,3]}}"""
+ }
+
+ "properly read a Container[Container[List[Int]]] from JSON" in {
+ import ReaderProtocol._
+ """{"content":{"content":[1,2,3]}}""".parseJson.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._
+ val json = Foo(1, "a", Some(Foo(2, "b", Some(Foo(3, "c") :: Nil)) :: Foo(4, "d") :: Nil)).toJson
+
+ json mustEqual
+ """{"id":1,"name":"a","foos":[{"id":2,"name":"b","foos":[{"id":3,"name":"c"}]},{"id":4,"name":"d"}]}""".parseJson
+ }
+ }
+} \ No newline at end of file
diff --git a/shared/src/test/scala/spray/json/BasicFormatsSpec.scala b/shared/src/test/scala/spray/json/BasicFormatsSpec.scala
new file mode 100644
index 0000000..454e1cc
--- /dev/null
+++ b/shared/src/test/scala/spray/json/BasicFormatsSpec.scala
@@ -0,0 +1,168 @@
+/*
+ * 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 spray.json
+
+import org.specs2.mutable._
+
+class BasicFormatsSpec extends Specification with DefaultJsonProtocol {
+
+ "The IntJsonFormat" should {
+ "convert an Int to a JsNumber" in {
+ 42.toJson mustEqual JsNumber(42)
+ }
+ "convert a JsNumber to an Int" in {
+ JsNumber(42).convertTo[Int] mustEqual 42
+ }
+ }
+
+ "The LongJsonFormat" should {
+ "convert a Long to a JsNumber" in {
+ 7563661897011259335L.toJson mustEqual JsNumber(7563661897011259335L)
+ }
+ "convert a JsNumber to a Long" in {
+ JsNumber(7563661897011259335L).convertTo[Long] mustEqual 7563661897011259335L
+ }
+ }
+
+ "The FloatJsonFormat" should {
+ "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).convertTo[Float] mustEqual 4.2f
+ }
+ "convert a JsNull to a Float" in {
+ JsNull.convertTo[Float].isNaN mustEqual Float.NaN.isNaN
+ }
+ }
+
+ "The DoubleJsonFormat" should {
+ "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).convertTo[Double] mustEqual 4.2
+ }
+ "convert a JsNull to a Double" in {
+ JsNull.convertTo[Double].isNaN mustEqual Double.NaN.isNaN
+ }
+ }
+
+ "The ByteJsonFormat" should {
+ "convert a Byte to a JsNumber" in {
+ 42.asInstanceOf[Byte].toJson mustEqual JsNumber(42)
+ }
+ "convert a JsNumber to a Byte" in {
+ JsNumber(42).convertTo[Byte] mustEqual 42
+ }
+ }
+
+ "The ShortJsonFormat" should {
+ "convert a Short to a JsNumber" in {
+ 42.asInstanceOf[Short].toJson mustEqual JsNumber(42)
+ }
+ "convert a JsNumber to a Short" in {
+ JsNumber(42).convertTo[Short] mustEqual 42
+ }
+ }
+
+ "The BigDecimalJsonFormat" should {
+ "convert a BigDecimal to a JsNumber" in {
+ BigDecimal(42).toJson mustEqual JsNumber(42)
+ }
+ "convert a JsNumber to a BigDecimal" in {
+ JsNumber(42).convertTo[BigDecimal] mustEqual BigDecimal(42)
+ }
+ """convert a JsString to a BigDecimal (to allow the quoted-large-numbers pattern)""" in {
+ JsString("9223372036854775809").convertTo[BigDecimal] mustEqual BigDecimal("9223372036854775809")
+ }
+ }
+
+ "The BigIntJsonFormat" should {
+ "convert a BigInt to a JsNumber" in {
+ BigInt(42).toJson mustEqual JsNumber(42)
+ }
+ "convert a JsNumber to a BigInt" in {
+ JsNumber(42).convertTo[BigInt] mustEqual BigInt(42)
+ }
+ """convert a JsString to a BigInt (to allow the quoted-large-numbers pattern)""" in {
+ JsString("9223372036854775809").convertTo[BigInt] mustEqual BigInt("9223372036854775809")
+ }
+ }
+
+ "The UnitJsonFormat" should {
+ "convert Unit to a JsNumber(1)" in {
+ ().toJson mustEqual JsNumber(1)
+ }
+ "convert a JsNumber to Unit" in {
+ 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.convertTo[Boolean] mustEqual true }
+ "convert a JsFalse to false" in { JsFalse.convertTo[Boolean] mustEqual false }
+ }
+
+ "The CharJsonFormat" should {
+ "convert a Char to a JsString" in {
+ 'c'.toJson mustEqual JsString("c")
+ }
+ "convert a JsString to a Char" in {
+ JsString("c").convertTo[Char] mustEqual 'c'
+ }
+ }
+
+ "The StringJsonFormat" should {
+ "convert a String to a JsString" in {
+ "Hello".toJson mustEqual JsString("Hello")
+ }
+ "convert a JsString to a String" in {
+ JsString("Hello").convertTo[String] mustEqual "Hello"
+ }
+ }
+
+ "The SymbolJsonFormat" should {
+ "convert a Symbol to a JsString" in {
+ Symbol("Hello").toJson mustEqual JsString("Hello")
+ }
+ "convert a JsString to a Symbol" in {
+ JsString("Hello").convertTo[Symbol] mustEqual Symbol("Hello")
+ }
+ }
+
+}
diff --git a/shared/src/test/scala/spray/json/CollectionFormatsSpec.scala b/shared/src/test/scala/spray/json/CollectionFormatsSpec.scala
new file mode 100644
index 0000000..9d6970b
--- /dev/null
+++ b/shared/src/test/scala/spray/json/CollectionFormatsSpec.scala
@@ -0,0 +1,82 @@
+/*
+ * 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 spray.json
+
+import org.specs2.mutable._
+import java.util.Arrays
+
+class CollectionFormatsSpec extends Specification with DefaultJsonProtocol {
+
+ "The listFormat" should {
+ val list = List(1, 2, 3)
+ val json = JsArray(JsNumber(1), JsNumber(2), JsNumber(3))
+ "convert a List[Int] to a JsArray of JsNumbers" in {
+ list.toJson mustEqual json
+ }
+ "convert a JsArray of JsNumbers to a List[Int]" in {
+ json.convertTo[List[Int]] mustEqual list
+ }
+ }
+
+ "The arrayFormat" should {
+ val array = Array(1, 2, 3)
+ val json = JsArray(JsNumber(1), JsNumber(2), JsNumber(3))
+ "convert an Array[Int] to a JsArray of JsNumbers" in {
+ array.toJson mustEqual json
+ }
+ "convert a JsArray of JsNumbers to an Array[Int]" in {
+ Arrays.equals(json.convertTo[Array[Int]], array) must beTrue
+ }
+ }
+
+ "The mapFormat" should {
+ val map = Map("a" -> 1, "b" -> 2, "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.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'"))
+ }
+ }
+
+ "The immutableSetFormat" should {
+ val set = Set(1, 2, 3)
+ val numbers = Set(JsNumber(1), JsNumber(2), JsNumber(3))
+ "convert a Set[Int] to a JsArray of JsNumbers" in {
+ set.toJson.asInstanceOf[JsArray].elements.toSet mustEqual numbers
+ }
+ "convert a JsArray of JsNumbers to a Set[Int]" in {
+ JsArray(numbers.toVector).convertTo[Set[Int]] mustEqual set
+ }
+ }
+
+ "The indexedSeqFormat" should {
+ val seq = collection.IndexedSeq(1, 2, 3)
+ val json = JsArray(JsNumber(1), JsNumber(2), JsNumber(3))
+ "convert a Set[Int] to a JsArray of JsNumbers" in {
+ seq.toJson mustEqual json
+ }
+ "convert a JsArray of JsNumbers to a IndexedSeq[Int]" in {
+ json.convertTo[collection.IndexedSeq[Int]] mustEqual seq
+ }
+ }
+
+} \ No newline at end of file
diff --git a/shared/src/test/scala/spray/json/CompactPrinterSpec.scala b/shared/src/test/scala/spray/json/CompactPrinterSpec.scala
new file mode 100644
index 0000000..691daa9
--- /dev/null
+++ b/shared/src/test/scala/spray/json/CompactPrinterSpec.scala
@@ -0,0 +1,73 @@
+/*
+ * 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 spray.json
+
+import org.specs2.mutable._
+
+class CompactPrinterSpec extends Specification {
+
+ "The CompactPrinter" should {
+ "print JsNull to 'null'" in {
+ CompactPrinter(JsNull) mustEqual "null"
+ }
+ "print JsTrue to 'true'" in {
+ CompactPrinter(JsTrue) mustEqual "true"
+ }
+ "print JsFalse to 'false'" in {
+ CompactPrinter(JsFalse) mustEqual "false"
+ }
+ "print JsNumber(0) to '0'" in {
+ CompactPrinter(JsNumber(0)) mustEqual "0"
+ }
+ "print JsNumber(1.23) to '1.23'" in {
+ CompactPrinter(JsNumber(1.23)) mustEqual "1.23"
+ }
+ "print JsNumber(1.23) to '1.23'" in {
+ CompactPrinter(JsNumber(1.23)) mustEqual "1.23"
+ }
+ "print JsNumber(12.34e-10) to '12.34e-10'" in {
+ CompactPrinter(JsNumber(12.34e-10)) mustEqual "1.234E-9"
+ }
+ "print JsString(\"xyz\") to \"xyz\"" in {
+ CompactPrinter(JsString("xyz")) mustEqual "\"xyz\""
+ }
+ "properly escape special chars in JsString" in {
+ CompactPrinter(JsString("\"\\\b\f\n\r\t")) mustEqual """"\"\\\b\f\n\r\t""""
+ CompactPrinter(JsString("\u1000")) mustEqual "\"\u1000\""
+ CompactPrinter(JsString("\u0100")) mustEqual "\"\u0100\""
+ CompactPrinter(JsString("\u0010")) mustEqual "\"\\u0010\""
+ CompactPrinter(JsString("\u0001")) mustEqual "\"\\u0001\""
+ CompactPrinter(JsString("\u001e")) mustEqual "\"\\u001e\""
+ // don't escape as it isn't required by the spec
+ CompactPrinter(JsString("\u007f")) mustEqual "\"\u007f\""
+ CompactPrinter(JsString("飞机因此受到损伤")) mustEqual "\"飞机因此受到损伤\""
+ CompactPrinter(JsString("\uD834\uDD1E")) mustEqual "\"\uD834\uDD1E\""
+ }
+ "properly print a simple JsObject" in (
+ 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("key" -> JsBoolean(true))))
+ mustEqual """[null,1.23,{"key":true}]"""
+ )
+ "properly print a JSON padding (JSONP) if requested" in {
+ CompactPrinter(JsTrue, Some("customCallback")) mustEqual("customCallback(true)")
+ }
+ }
+
+} \ No newline at end of file
diff --git a/shared/src/test/scala/spray/json/CustomFormatSpec.scala b/shared/src/test/scala/spray/json/CustomFormatSpec.scala
new file mode 100644
index 0000000..2397abc
--- /dev/null
+++ b/shared/src/test/scala/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 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 }""".parseJson.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/shared/src/test/scala/spray/json/HashCodeCollider.scala b/shared/src/test/scala/spray/json/HashCodeCollider.scala
new file mode 100644
index 0000000..57388b9
--- /dev/null
+++ b/shared/src/test/scala/spray/json/HashCodeCollider.scala
@@ -0,0 +1,26 @@
+package spray.json
+
+/**
+ * Helper that creates strings that all share the same hashCode == 0.
+ *
+ * Adapted from MIT-licensed code by Andriy Plokhotnyuk
+ * at https://github.com/plokhotnyuk/jsoniter-scala/blob/26b5ecdd4f8c2ab7e97bd8106cefdda4c1e701ce/jsoniter-scala-benchmark/src/main/scala/com/github/plokhotnyuk/jsoniter_scala/macros/HashCodeCollider.scala#L6.
+ */
+object HashCodeCollider {
+ val visibleChars = (33 until 127).filterNot(c => c == '\\' || c == '"')
+ def asciiChars: Iterator[Int] = visibleChars.toIterator
+ def asciiCharsAndHash(previousHash: Int): Iterator[(Int, Int)] = visibleChars.toIterator.map(c => c -> (previousHash + c) * 31)
+
+ /** Creates an iterator of Strings that all have hashCode == 0 */
+ def zeroHashCodeIterator(): Iterator[String] =
+ for {
+ (i0, h0) <- asciiCharsAndHash(0)
+ (i1, h1) <- asciiCharsAndHash(h0)
+ (i2, h2) <- asciiCharsAndHash(h1) if (((h2 + 32) * 923521) ^ ((h2 + 127) * 923521)) < 0
+ (i3, h3) <- asciiCharsAndHash(h2) if (((h3 + 32) * 29791) ^ ((h3 + 127) * 29791)) < 0
+ (i4, h4) <- asciiCharsAndHash(h3) if (((h4 + 32) * 961) ^ ((h4 + 127) * 961)) < 0
+ (i5, h5) <- asciiCharsAndHash(h4) if (((h5 + 32) * 31) ^ ((h5 + 127) * 31)) < 0
+ (i6, h6) <- asciiCharsAndHash(h5) if ((h6 + 32) ^ (h6 + 127)) < 0
+ (i7, h7) <- asciiCharsAndHash(h6) if h6 + i7 == 0
+ } yield new String(Array(i0, i1, i2, i3, i4, i5, i6, i7).map(_.toChar))
+}
diff --git a/shared/src/test/scala/spray/json/JsonParserSpec.scala b/shared/src/test/scala/spray/json/JsonParserSpec.scala
new file mode 100644
index 0000000..0793e66
--- /dev/null
+++ b/shared/src/test/scala/spray/json/JsonParserSpec.scala
@@ -0,0 +1,155 @@
+/*
+ * 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 spray.json
+
+import org.specs2.mutable._
+
+class JsonParserSpec extends Specification {
+
+ "The JsonParser" should {
+ "parse 'null' to JsNull" in {
+ JsonParser("null") === JsNull
+ }
+ "parse 'true' to JsTrue" in {
+ JsonParser("true") === JsTrue
+ }
+ "parse 'false' to JsFalse" in {
+ JsonParser("false") === JsFalse
+ }
+ "parse '0' to JsNumber" in {
+ JsonParser("0") === JsNumber(0)
+ }
+ "parse '1.23' to JsNumber" in {
+ JsonParser("1.23") === JsNumber(1.23)
+ }
+ "parse '-1E10' to JsNumber" in {
+ JsonParser("-1E10") === JsNumber("-1E+10")
+ }
+ "parse '12.34e-10' to JsNumber" in {
+ JsonParser("12.34e-10") === JsNumber("1.234E-9")
+ }
+ "parse \"xyz\" to JsString" in {
+ JsonParser("\"xyz\"") === JsString("xyz")
+ }
+ "parse escapes in a JsString" in {
+ JsonParser(""""\"\\/\b\f\n\r\t"""") === JsString("\"\\/\b\f\n\r\t")
+ JsonParser("\"L\\" + "u00e4nder\"") === JsString("Länder")
+ }
+ "parse all representations of the slash (SOLIDUS) character in a JsString" in {
+ JsonParser( "\"" + "/\\/\\u002f" + "\"") === JsString("///")
+ }
+ "parse a simple JsObject" in (
+ JsonParser(""" { "key" :42, "key2": "value" }""") ===
+ JsObject("key" -> JsNumber(42), "key2" -> JsString("value"))
+ )
+ "parse a simple JsArray" in (
+ JsonParser("""[null, 1.23 ,{"key":true } ] """) ===
+ JsArray(JsNull, JsNumber(1.23), JsObject("key" -> JsTrue))
+ )
+ "parse directly from UTF-8 encoded bytes" in {
+ val json = JsObject(
+ "7-bit" -> JsString("This is regular 7-bit ASCII text."),
+ "2-bytes" -> JsString("2-byte UTF-8 chars like £, æ or Ö"),
+ "3-bytes" -> JsString("3-byte UTF-8 chars like ヨ, ᄅ or ᐁ."),
+ "4-bytes" -> JsString("4-byte UTF-8 chars like \uD801\uDC37, \uD852\uDF62 or \uD83D\uDE01."))
+ JsonParser(json.prettyPrint.getBytes("UTF-8")) === json
+ }
+ "parse directly from UTF-8 encoded bytes when string starts with a multi-byte character" in {
+ val json = JsString("£0.99")
+ JsonParser(json.prettyPrint.getBytes("UTF-8")) === json
+ }
+ "not show bad performance characteristics when object keys' hashCodes collide" in {
+ val numKeys = 100000
+ val value = "null"
+
+ val regularKeys = Iterator.from(1).map(i => s"key_$i").take(numKeys)
+ val collidingKeys = HashCodeCollider.zeroHashCodeIterator().take(numKeys)
+
+ def createJson(keys: Iterator[String]): String = keys.mkString("""{"""", s"""":$value,"""", s"""":$value}""")
+
+ def nanoBench(block: => Unit): Long = {
+ // great microbenchmark (the comment must be kept, otherwise it's not true)
+ val f = block _
+
+ // warmup
+ (1 to 10).foreach(_ => f())
+
+ val start = System.nanoTime()
+ f()
+ val end = System.nanoTime()
+ end - start
+ }
+
+ val regularJson = createJson(regularKeys)
+ val collidingJson = createJson(collidingKeys)
+
+ val regularTime = nanoBench { JsonParser(regularJson) }
+ val collidingTime = nanoBench { JsonParser(collidingJson) }
+
+ collidingTime / regularTime must be < 2L // speed must be in same order of magnitude
+ }
+
+ "produce proper error messages" in {
+ def errorMessage(input: String, settings: JsonParserSettings = JsonParserSettings.default) =
+ try JsonParser(input, settings) catch { case e: JsonParser.ParsingException => e.getMessage }
+
+ errorMessage("""[null, 1.23 {"key":true } ]""") ===
+ """Unexpected character '{' at input index 12 (line 1, position 13), expected ']':
+ |[null, 1.23 {"key":true } ]
+ | ^
+ |""".stripMargin
+
+ errorMessage("""[null, 1.23, { key":true } ]""") ===
+ """Unexpected character 'k' at input index 16 (line 1, position 17), expected '"':
+ |[null, 1.23, { key":true } ]
+ | ^
+ |""".stripMargin
+
+ errorMessage("""{"a}""") ===
+ """Unexpected end-of-input at input index 4 (line 1, position 5), expected '"':
+ |{"a}
+ | ^
+ |""".stripMargin
+
+ errorMessage("""{}x""") ===
+ """Unexpected character 'x' at input index 2 (line 1, position 3), expected end-of-input:
+ |{}x
+ | ^
+ |""".stripMargin
+
+ "reject numbers which are too big / have too high precision" in {
+ val settings = JsonParserSettings.default.withMaxNumberCharacters(5)
+ errorMessage("123.4567890", settings) ===
+ "Number too long:The number starting with '123.4567890' had 11 characters which is more than the allowed limit " +
+ "maxNumberCharacters = 5. If this is legit input consider increasing the limit."
+ }
+ }
+
+ "parse multiple values when allowTrailingInput" in {
+ val parser = new JsonParser("""{"key":1}{"key":2}""")
+ parser.parseJsValue(true) === JsObject("key" -> JsNumber(1))
+ parser.parseJsValue(true) === JsObject("key" -> JsNumber(2))
+ }
+ "reject trailing input when !allowTrailingInput" in {
+ def parser = JsonParser("""{"key":1}x""")
+ parser must throwA[JsonParser.ParsingException].like {
+ case e: JsonParser.ParsingException => e.getMessage must contain("expected end-of-input")
+ }
+ }
+
+ }
+}
diff --git a/shared/src/test/scala/spray/json/PrettyPrinterSpec.scala b/shared/src/test/scala/spray/json/PrettyPrinterSpec.scala
new file mode 100644
index 0000000..b547f59
--- /dev/null
+++ b/shared/src/test/scala/spray/json/PrettyPrinterSpec.scala
@@ -0,0 +1,71 @@
+/*
+ * 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 spray.json
+
+import scala.collection.immutable.ListMap
+import org.specs2.mutable._
+
+class PrettyPrinterSpec extends Specification {
+
+ "The PrettyPrinter" should {
+ "print a more complicated JsObject nicely aligned" in {
+ val js = JsonParser {
+ """{
+ | "Boolean no": false,
+ | "Boolean yes":true,
+ | "Unic\u00f8de" : "Long string with newline\nescape",
+ | "key with \"quotes\"" : "string",
+ | "key with spaces": null,
+ | "number": -1.2323424E-5,
+ | "simpleKey" : "some value",
+ | "sub object" : {
+ | "sub key": 26.5,
+ | "a": "b",
+ | "array": [1, 2, { "yes":1, "no":0 }, ["a", "b", null], false]
+ | },
+ | "zero": 0
+ |}""".stripMargin
+ }
+ def fixedFieldOrder(js: JsValue): JsValue = js match {
+ case JsObject(fields) => JsObject(ListMap(fields.toSeq.sortBy(_._1).map { case (k, v) => (k, fixedFieldOrder(v)) }:_*))
+ case x => x
+ }
+
+ PrettyPrinter(fixedFieldOrder(js)) mustEqual {
+ """{
+ | "Boolean no": false,
+ | "Boolean yes": true,
+ | "Unic\u00f8de": "Long string with newline\nescape",
+ | "key with \"quotes\"": "string",
+ | "key with spaces": null,
+ | "number": -0.000012323424,
+ | "simpleKey": "some value",
+ | "sub object": {
+ | "a": "b",
+ | "array": [1, 2, {
+ | "no": 0,
+ | "yes": 1
+ | }, ["a", "b", null], false],
+ | "sub key": 26.5
+ | },
+ | "zero": 0
+ |}""".stripMargin
+ }
+ }
+ }
+
+} \ No newline at end of file
diff --git a/shared/src/test/scala/spray/json/RoundTripSpecs.scala b/shared/src/test/scala/spray/json/RoundTripSpecs.scala
new file mode 100644
index 0000000..6bee7b4
--- /dev/null
+++ b/shared/src/test/scala/spray/json/RoundTripSpecs.scala
@@ -0,0 +1,77 @@
+/*
+ * 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 spray.json
+
+import org.specs2.mutable.Specification
+import org.scalacheck._
+import org.specs2.ScalaCheck
+
+object JsValueGenerators {
+ import Gen._
+ import Arbitrary.arbitrary
+
+ val parseableString: Gen[String] = Gen.someOf(('\u0020' to '\u007E').toVector).map(_.mkString)
+ val genString: Gen[JsString] = parseableString.map(JsString(_))
+ val genBoolean: Gen[JsBoolean] = oneOf(JsFalse, JsTrue)
+ val genLongNumber: Gen[JsNumber] = arbitrary[Long].map(JsNumber(_))
+ val genIntNumber: Gen[JsNumber] = arbitrary[Long].map(JsNumber(_))
+ val genDoubleNumber: Gen[JsNumber] = arbitrary[Long].map(JsNumber(_))
+ def genArray(depth: Int): Gen[JsArray] =
+ if (depth == 0) JsArray()
+ else
+ for {
+ n <- choose(0, 15)
+ els <- Gen.containerOfN[List, JsValue](n, genValue(depth - 1))
+ } yield JsArray(els.toVector)
+ def genField(depth: Int): Gen[(String, JsValue)] =
+ for {
+ key <- parseableString
+ value <- genValue(depth)
+ } yield key -> value
+ def genObject(depth: Int): Gen[JsObject] =
+ if (depth == 0) JsObject()
+ else
+ for {
+ n <- choose(0, 15)
+ fields <- Gen.containerOfN[List, (String, JsValue)](n, genField(depth - 1))
+ } yield JsObject(fields: _*)
+
+ def genValue(depth: Int): Gen[JsValue] =
+ oneOf(
+ JsNull: Gen[JsValue],
+ genString,
+ genBoolean,
+ genLongNumber,
+ genDoubleNumber,
+ genIntNumber,
+ genArray(depth),
+ genObject(depth))
+ implicit val arbitraryValue: Arbitrary[JsValue] = Arbitrary(genValue(5))
+}
+
+class RoundTripSpecs extends Specification with ScalaCheck {
+ import JsValueGenerators.arbitraryValue
+
+ "Parsing / Printing round-trip" should {
+ "starting from JSON using compactPrint" in prop { (json: JsValue) =>
+ json.compactPrint.parseJson must_== json
+ }
+ "starting from JSON using prettyPrint" in prop { (json: JsValue) =>
+ json.prettyPrint.parseJson must_== json
+ }
+ }
+}
diff --git a/shared/src/test/scala/spray/json/SortedPrinterSpec.scala b/shared/src/test/scala/spray/json/SortedPrinterSpec.scala
new file mode 100644
index 0000000..f91640e
--- /dev/null
+++ b/shared/src/test/scala/spray/json/SortedPrinterSpec.scala
@@ -0,0 +1,65 @@
+/*
+ * 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 spray.json
+
+import org.specs2.mutable._
+
+class SortedPrinterSpec extends Specification {
+
+ "The SortedPrinter" should {
+ "print a more complicated JsObject nicely aligned with fields sorted" in {
+ val obj = JsonParser {
+ """{
+ | "Unic\u00f8de" : "Long string with newline\nescape",
+ | "Boolean no": false,
+ | "number": -1.2323424E-5,
+ | "key with \"quotes\"" : "string",
+ | "key with spaces": null,
+ | "simpleKey" : "some value",
+ | "zero": 0,
+ | "sub object" : {
+ | "sub key": 26.5,
+ | "a": "b",
+ | "array": [1, 2, { "yes":1, "no":0 }, ["a", "b", null], false]
+ | },
+ | "Boolean yes":true
+ |}""".stripMargin
+ }
+ SortedPrinter(obj) mustEqual {
+ """{
+ | "Boolean no": false,
+ | "Boolean yes": true,
+ | "Unic\u00f8de": "Long string with newline\nescape",
+ | "key with \"quotes\"": "string",
+ | "key with spaces": null,
+ | "number": -0.000012323424,
+ | "simpleKey": "some value",
+ | "sub object": {
+ | "a": "b",
+ | "array": [1, 2, {
+ | "no": 0,
+ | "yes": 1
+ | }, ["a", "b", null], false],
+ | "sub key": 26.5
+ | },
+ | "zero": 0
+ |}""".stripMargin
+ }
+ }
+ }
+
+}
diff --git a/shared/src/test/scala/spray/json/StandardFormatsSpec.scala b/shared/src/test/scala/spray/json/StandardFormatsSpec.scala
new file mode 100644
index 0000000..833f06a
--- /dev/null
+++ b/shared/src/test/scala/spray/json/StandardFormatsSpec.scala
@@ -0,0 +1,121 @@
+/*
+ * 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 spray.json
+
+import org.specs2.mutable._
+import scala.Right
+
+class StandardFormatsSpec extends Specification with DefaultJsonProtocol {
+
+ "The optionFormat" should {
+ "convert None to JsNull" in {
+ None.asInstanceOf[Option[Int]].toJson mustEqual JsNull
+ }
+ "convert JsNull to None" in {
+ 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").convertTo[Option[String]] mustEqual Some("Hello")
+ }
+ }
+
+ "The eitherFormat" should {
+ val a: Either[Int, String] = Left(42)
+ val b: Either[Int, String] = Right("Hello")
+
+ "convert the left side of an Either value to Json" in {
+ a.toJson mustEqual JsNumber(42)
+ }
+ "convert the right side of an Either value to Json" in {
+ b.toJson mustEqual JsString("Hello")
+ }
+ "convert the left side of an Either value from Json" in {
+ JsNumber(42).convertTo[Either[Int, String]] mustEqual Left(42)
+ }
+ "convert the right side of an Either value from Json" in {
+ JsString("Hello").convertTo[Either[Int, String]] mustEqual Right("Hello")
+ }
+ }
+
+ "The tuple1Format" should {
+ "convert (42) to a JsNumber" in {
+ Tuple1(42).toJson mustEqual JsNumber(42)
+ }
+ "be able to convert a JsNumber to a Tuple1[Int]" in {
+ JsNumber(42).convertTo[Tuple1[Int]] mustEqual Tuple1(42)
+ }
+ }
+
+ "The tuple2Format" should {
+ val json = JsArray(JsNumber(42), JsNumber(4.2))
+ "convert (42, 4.2) to a JsArray" in {
+ (42, 4.2).toJson mustEqual json
+ }
+ "be able to convert a JsArray to a (Int, Double)]" in {
+ json.convertTo[(Int, Double)] mustEqual ((42, 4.2))
+ }
+ }
+
+ "The tuple3Format" should {
+ val json = JsArray(JsNumber(42), JsNumber(4.2), JsNumber(3))
+ "convert (42, 4.2, 3) to a JsArray" in {
+ (42, 4.2, 3).toJson mustEqual json
+ }
+ "be able to convert a JsArray to a (Int, Double, Int)]" in {
+ json.convertTo[(Int, Double, Int)] mustEqual ((42, 4.2, 3))
+ }
+ }
+ "The tuple4Format" should {
+ val json = JsArray(JsNumber(42), JsNumber(4.2), JsNumber(3), JsNumber(4))
+ "convert (42, 4.2, 3, 4) to a JsArray" in {
+ (42, 4.2, 3, 4).toJson mustEqual json
+ }
+ "be able to convert a JsArray to a (Int, Double, Int, Int)]" in {
+ json.convertTo[(Int, Double, Int, Int)] mustEqual ((42, 4.2, 3, 4))
+ }
+ }
+ "The tuple5Format" should {
+ val json = JsArray(JsNumber(42), JsNumber(4.2), JsNumber(3), JsNumber(4), JsNumber(5))
+ "convert (42, 4.2, 3, 4, 5) to a JsArray" in {
+ (42, 4.2, 3, 4, 5).toJson mustEqual json
+ }
+ "be able to convert a JsArray to a (Int, Double, Int, Int, Int)]" in {
+ json.convertTo[(Int, Double, Int, Int, Int)] mustEqual ((42, 4.2, 3, 4, 5))
+ }
+ }
+ "The tuple6Format" should {
+ val json = JsArray(JsNumber(42), JsNumber(4.2), JsNumber(3), JsNumber(4), JsNumber(5), JsNumber(6))
+ "convert (42, 4.2, 3, 4, 5, 6) to a JsArray" in {
+ (42, 4.2, 3, 4, 5, 6).toJson mustEqual json
+ }
+ "be able to convert a JsArray to a (Int, Double, Int, Int, Int, Int)]" in {
+ json.convertTo[(Int, Double, Int, Int, Int, Int)] mustEqual ((42, 4.2, 3, 4, 5, 6))
+ }
+ }
+ "The tuple7Format" should {
+ val json = JsArray(JsNumber(42), JsNumber(4.2), JsNumber(3), JsNumber(4), JsNumber(5), JsNumber(6), JsNumber(7))
+ "convert (42, 4.2, 3, 4, 5, 6, 7) to a JsArray" in {
+ (42, 4.2, 3, 4, 5, 6, 7).toJson mustEqual json
+ }
+ "be able to convert a JsArray to a (Int, Double, Int, Int, Int, Int, Int)]" in {
+ json.convertTo[(Int, Double, Int, Int, Int, Int, Int)] mustEqual ((42, 4.2, 3, 4, 5, 6, 7))
+ }
+ }
+}