summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml10
-rw-r--r--README.markdown47
-rw-r--r--build.sbt38
-rw-r--r--images/Conversions.pngbin0 -> 27833 bytes
-rw-r--r--images/Conversions.xml1
-rw-r--r--images/HOWTOEDIT.md5
-rw-r--r--project/build.properties2
-rw-r--r--project/plugins.sbt6
-rw-r--r--src/main/boilerplate/spray/json/ProductFormatsInstances.scala.template2
-rw-r--r--src/main/scala/spray/json/BasicFormats.scala16
-rw-r--r--src/main/scala/spray/json/CompactPrinter.scala4
-rw-r--r--src/main/scala/spray/json/JsonPrinter.scala25
-rw-r--r--src/main/scala/spray/json/PrettyPrinter.scala19
-rw-r--r--src/main/scala/spray/json/ProductFormats.scala3
-rw-r--r--src/main/scala/spray/json/package.scala4
-rw-r--r--src/test/scala/spray/json/AdditionalFormatsSpec.scala2
-rw-r--r--src/test/scala/spray/json/CompactPrinterSpec.scala11
-rw-r--r--src/test/scala/spray/json/CustomFormatSpec.scala2
-rw-r--r--src/test/scala/spray/json/JsonParserSpec.scala3
-rw-r--r--src/test/scala/spray/json/PrettyPrinterSpec.scala2
-rw-r--r--src/test/scala/spray/json/ReadmeSpec.scala2
-rw-r--r--src/test/scala/spray/json/RoundTripSpecs.scala63
22 files changed, 192 insertions, 75 deletions
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..de044d7
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,10 @@
+language: scala
+script:
+ - sbt "+ test"
+jdk:
+ - oraclejdk7
+ - openjdk7
+notifications:
+ email:
+ - johannes@spray.io
+ - mathias@spray.io \ No newline at end of file
diff --git a/README.markdown b/README.markdown
index 60a7e7b..1573322 100644
--- a/README.markdown
+++ b/README.markdown
@@ -1,12 +1,20 @@
_spray-json_ is a lightweight, clean and efficient [JSON] implementation in Scala.
It sports the following features:
-
-* Simple immutable model of the JSON language elements
+
+* A simple immutable model of the JSON language elements
* An efficient JSON PEG parser (implemented with [parboiled][])
* Choice of either compact or pretty JSON-to-string printing
* Type-class based (de)serialization of custom objects (no reflection, no intrusion)
+_spray-json_ allows you to convert between
+ * String JSON documents
+ * JSON Abstract Syntax Trees (ASTs) with base type JsValue
+ * instances of arbitrary Scala types
+
+as depicted in this diagram:
+
+![Spray-JSON conversions](images/Conversions.png "Conversions possible with Spray-JSON")
### Installation
@@ -25,11 +33,10 @@ _spray-json_ has only one dependency: the parsing library [parboiled][]
(which is also a dependency of _spray-http_, so if you use _spray-json_ together with other modules of the *spray*
suite you are not incurring any additional dependency).
-
### Usage
-_spray-json_ is really easy to use.
-Just bring all relevant elements in scope with
+_spray-json_ is really easy to use.
+Just bring all relevant elements in scope with
```scala
import spray.json._
@@ -41,7 +48,7 @@ and do one or more of the following:
1. Parse a JSON string into its Abstract Syntax Tree (AST) representation
```scala
val source = """{ "some": "JSON source" }"""
-val jsonAst = source.asJson // or JsonParser(source)
+val jsonAst = source.parseJson // or JsonParser(source)
```
2. Print a JSON AST back to a String using either the `CompactPrinter` or the `PrettyPrinter`
@@ -49,7 +56,7 @@ val jsonAst = source.asJson // or JsonParser(source)
val json = jsonAst.prettyPrint // or .compactPrint
```
- 3. Convert any Scala object to a JSON AST using the pimped `toJson` method
+ 3. Convert any Scala object to a JSON AST using the pimped `toJson` method
```scala
val jsonAst = List(1, 2, 3).toJson
```
@@ -74,17 +81,17 @@ This approach has the advantage of not requiring any change (or even access) to
logic is attached 'from the outside'. There is no reflection involved, so the resulting conversions are fast. Scalas
excellent type inference reduces verbosity and boilerplate to a minimum, while the Scala compiler will make sure at
compile time that you provided all required (de)serialization logic.
-
+
In _spray-jsons_ terminology a 'JsonProtocol' is nothing but a bunch of implicit values of type `JsonFormat[T]`, whereby
each `JsonFormat[T]` contains the logic of how to convert instance of `T` to and from JSON. All `JsonFormat[T]`s of a
protocol need to be "mece" (mutually exclusive, collectively exhaustive), i.e. they are not allowed to overlap and
together need to span all types required by the application.
-
-This may sound more complicated than it is.
+
+This may sound more complicated than it is.
_spray-json_ comes with a `DefaultJsonProtocol`, which already covers all of Scalas value types as well as the most
important reference and collection types. As long as your code uses nothing more than these you only need the
`DefaultJsonProtocol`. Here are the types already taken care of by the `DefaultJsonProtocol`:
-
+
* Byte, Short, Int, Long, Float, Double, Char, Unit, Boolean
* String, Symbol
* BigInt, BigDecimal
@@ -96,7 +103,7 @@ important reference and collection types. As long as your code uses nothing more
In most cases however you'll also want to convert types not covered by the `DefaultJsonProtocol`. In these cases you
need to provide `JsonFormat[T]`s for your custom types. This is not hard at all.
-
+
### Providing JsonFormats for Case Classes
@@ -108,9 +115,9 @@ case class Color(name: String, red: Int, green: Int, blue: Int)
object MyJsonProtocol extends DefaultJsonProtocol {
implicit val colorFormat = jsonFormat4(Color)
}
-
+
import MyJsonProtocol._
-
+
val json = Color("CadetBlue", 95, 158, 160).toJson
val color = json.convertTo[Color]
```
@@ -159,12 +166,12 @@ optional members as `None`.)
### Providing JsonFormats for other Types
-Of course you can also supply (de)serialization logic for types that aren't case classes.
+Of course you can also supply (de)serialization logic for types that aren't case classes.
Here is one way to do it:
```scala
class Color(val name: String, val red: Int, val green: Int, val blue: Int)
-
+
object MyJsonProtocol extends DefaultJsonProtocol {
implicit object ColorJsonFormat extends RootJsonFormat[Color] {
def write(c: Color) =
@@ -248,8 +255,8 @@ implicit val fooFormat: JsonFormat[Foo] = lazyFormat(jsonFormat(Foo, "i", "foo")
```
Otherwise your code will either not compile (no explicit type annotation) or throw an NPE at runtime (no `lazyFormat`
-wrapper). Note, that `lazyFormat` returns a `JsonFormat` even if it was given a `RootJsonFormat` which means it isn't
-picked up by `SprayJsonSupport`. To get back a `RootJsonFormat` just wrap the complete `lazyFormat` call with another
+wrapper). Note, that `lazyFormat` returns a `JsonFormat` even if it was given a `RootJsonFormat` which means it isn't
+picked up by `SprayJsonSupport`. To get back a `RootJsonFormat` just wrap the complete `lazyFormat` call with another
call to `rootFormat`.
@@ -259,7 +266,7 @@ Most of type-class (de)serialization code is nothing but a polished copy of what
with his [SJSON] library. These code parts therefore bear his copyright.
Additionally the JSON AST model is heavily inspired by the one contributed by **Jorge Ortiz** to [Databinder-Dispatch].
-
+
### License
_spray-json_ is licensed under [APL 2.0].
@@ -275,7 +282,7 @@ Feedback and contributions to the project, no matter what kind, are always very
However, patches can only be accepted from their original author.
Along with any patches, please state that the patch is your original work and that you license the work to the
_spray-json_ project under the project’s open source license.
-
+
[JSON]: http://json.org
[parboiled]: http://parboiled.org
diff --git a/build.sbt b/build.sbt
index b759d9e..110b93d 100644
--- a/build.sbt
+++ b/build.sbt
@@ -18,26 +18,44 @@ scalaVersion := "2.10.4"
scalacOptions <<= scalaVersion map {
case "2.9.3" => Seq("-unchecked", "-deprecation", "-encoding", "utf8")
- case "2.10.4" => Seq("-feature", "-language:implicitConversions", "-unchecked", "-deprecation", "-encoding", "utf8")
+ case _ => Seq("-feature", "-language:implicitConversions", "-unchecked", "-deprecation", "-encoding", "utf8")
}
resolvers += Opts.resolver.sonatypeReleases
-libraryDependencies <++= scalaVersion { sv =>
- Seq(
- "org.parboiled" %% "parboiled-scala" % "1.1.6" % "compile",
- sv match {
- case "2.9.3" => "org.specs2" %% "specs2" % "1.12.4.1" % "test"
- case "2.10.4" => "org.specs2" %% "specs2" % "2.3.10" % "test"
- }
- )
+libraryDependencies ++= {
+ Seq("org.parboiled" %% "parboiled-scala" % "1.1.6" % "compile") ++
+ (scalaVersion.value match {
+ case "2.9.3" =>
+ Seq(
+ "org.specs2" %% "specs2" % "1.12.4.1" % "test",
+ "org.scalacheck" %% "scalacheck" % "1.10.0" % "test"
+ )
+ // Scala 2.10 and Scala 2.11
+ case _ =>
+ Seq(
+ "org.specs2" %% "specs2" % "2.3.10" % "test",
+ "org.scalacheck" %% "scalacheck" % "1.11.3" % "test"
+ )
+ })
}
-scaladocOptions <<= (name, version).map { (n, v) => Seq("-doc-title", n + " " + v) }
+(scalacOptions in doc) <<= (name, version).map { (n, v) => Seq("-doc-title", n + " " + v) }
// generate boilerplate
Boilerplate.settings
+// OSGi settings
+osgiSettings
+
+OsgiKeys.exportPackage := Seq("""spray.json.*;version="${Bundle-Version}"""")
+
+OsgiKeys.importPackage <<= scalaVersion { sv => Seq("""scala.*;version="$<range;[==,=+);%s>"""".format(sv)) }
+
+OsgiKeys.importPackage ++= Seq("""spray.json;version="${Bundle-Version}"""", "*")
+
+OsgiKeys.additionalHeaders := Map("-removeheaders" -> "Include-Resource,Private-Package")
+
///////////////
// publishing
///////////////
diff --git a/images/Conversions.png b/images/Conversions.png
new file mode 100644
index 0000000..fd73b1a
--- /dev/null
+++ b/images/Conversions.png
Binary files differ
diff --git a/images/Conversions.xml b/images/Conversions.xml
new file mode 100644
index 0000000..ab0bed1
--- /dev/null
+++ b/images/Conversions.xml
@@ -0,0 +1 @@
+<mxfile userAgent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.102 Safari/537.36"><diagram>5VlZk9o4EP41VE0eSPnAGB6HOZKidpOpYmqPR2EL8I5tsbKYgf31acktLFt2WMKRmsoLWG2p1f119+eW3fPvsu0nTtar31lM057nxNuef9/zvJHjwK8U7BqCJU/iUuSiYJPEtKiJBGOpSNZ1YcTynEaiJluwtK5sTZbUEswiktrSP5NYrNA4b1jJP9NkudLbuMNxeacQO60jpguySUVfieCevJ0RrUu56T8ANJwxUCOvsu0dTSU82vUSi8eOu3sjOc3RkO8vCIZDEo1dJ1iEY88djPquX6p4JekGrbbceFslgs7WJJLjNwgiLqFcUIxiiyFKhFZ8oiyjgu9gCi7wnUG5BKPuuqNy/FaB7Y4xE1YG0CO0j2DMl3vVldtwgZ63o4AqDJ+nxR9qIMPrb0nPn9xMZ1+/wKTb2fMHCxHONnlMpTYXptr4+JMFy8UMF8g5apz8p4YDGBeCs5d9ZiEWpyEaoFsaUcxUA9Ax7lPDE2Wn4Gnn0EzwJF+2wBmz6H3AOQ4Poomic6Opra/QXHMqxO4JIBUGpBHLACSB4gakNI9vOWdvMIpSUhRJBCvoNhF/gcTB67/l9ccwkMMcrJT3JLpqULsZPybS3lbs67FRhtDYItcG7mAs23AV4qogBeFLirNUTtnR4TQlInmtaz8F7cBGm/CCTguWHweqAm4PKug1QFWAV6DizQuDilVpgqpwvgKoWBkGqDswzMhd+aAlxljs1vBkfw+8EIzwOX99mkVGMlCFTkc688x6weS5F5RP78MZW8u8H83f69CAQveEjMWlT0yRpA7iACw2g+g5GEWtorQKV5n9VEPRcFBXFDT0lK5YelSk9+78r+Bje2QEX7AjSKo15Mc+By4QcsTrnCTVHqmwEam+Hh8b8iBsKHL9hqbzBd1u120OsBJgCbTZ3aLj4YjM9XTssY9gwBCDphlQqzBbd21XjQNx3kmPawsRu1060wGmA3wbl5rfbUeWi/iNFtf8Hqaw5aRYE6AFA4Dhvxt5wFT12i9UAd/CBHewBu8ncDUn0YtMmzzuRyxlvLzPl/MbL4AGCaxzzIsPapXWCldL/Ffby13kibuEu5r3maavVCSRfO43lj5RDlR2U914iM1Jd94ADg2ldgCq3KC+KYhLt7W4kQIQaClfiUyTIEyP5BnFv5fUhqNnBl3Ffd+VzNjWbzDImUWqyHWVxDHN0d9HkiWprIffNlESg4fOHcsLBrufNdNQRf2gh5RwKAsD7AfPm4XYDP1MPgobDVkbHQ2uVpb2u4WL0ZHy+/t05KOii/vdlgcH2Ojnkc4R1NiyXyWxr8K9QXN53NFLSCbJI58X8g/U2yKtISeZfAul9UljOjjxxI3k609zH/nawVIYGgh1XP1CpNxRb5qGPuKrBE3LelyrR09LzYoM99LzchEWektRXrFcrkAC0+Lr/B94iMgG4odqoj1bO2rSc/p7FTjTmRblG8+OFga6l/PWa9tMS9Rm5pdNNqf8xmioKnu6e6z3VLeHvjs03uvq161mlY5wTq1Gj2+fYFh92SlPf9UXMP/hGw==</diagram></mxfile>
diff --git a/images/HOWTOEDIT.md b/images/HOWTOEDIT.md
new file mode 100644
index 0000000..e79e774
--- /dev/null
+++ b/images/HOWTOEDIT.md
@@ -0,0 +1,5 @@
+ * http://draw.io
+ * Import XML from repo
+ * Change diagram
+ * Save xml back to repo
+ * "Download as" to png to repo
diff --git a/project/build.properties b/project/build.properties
index f069b10..37b489c 100644
--- a/project/build.properties
+++ b/project/build.properties
@@ -1 +1 @@
-sbt.version=0.12.4 \ No newline at end of file
+sbt.version=0.13.1
diff --git a/project/plugins.sbt b/project/plugins.sbt
index 0bea931..b0664ad 100644
--- a/project/plugins.sbt
+++ b/project/plugins.sbt
@@ -1,3 +1,5 @@
-addSbtPlugin("me.lessis" % "ls-sbt" % "0.1.2")
+addSbtPlugin("me.lessis" % "ls-sbt" % "0.1.3")
-addSbtPlugin("io.spray" % "sbt-boilerplate" % "0.5.0")
+addSbtPlugin("io.spray" % "sbt-boilerplate" % "0.5.1")
+
+addSbtPlugin("com.typesafe.sbt" % "sbt-osgi" % "0.7.0")
diff --git a/src/main/boilerplate/spray/json/ProductFormatsInstances.scala.template b/src/main/boilerplate/spray/json/ProductFormatsInstances.scala.template
index fa5cbb2..3d29b58 100644
--- a/src/main/boilerplate/spray/json/ProductFormatsInstances.scala.template
+++ b/src/main/boilerplate/spray/json/ProductFormatsInstances.scala.template
@@ -27,7 +27,7 @@ trait ProductFormatsInstances { self: ProductFormats with StandardFormats =>
def write(p: T) = {
val fields = new collection.mutable.ListBuffer[(String, JsValue)]
fields.sizeHint(1 * 2)
- [#fields ++= productElement2Field[P1](fieldName1, p, 0)#
+ [#fields ++= productElement##2Field[P1](fieldName1, p, 0)#
]
JsObject(fields: _*)
}
diff --git a/src/main/scala/spray/json/BasicFormats.scala b/src/main/scala/spray/json/BasicFormats.scala
index 55fe0bf..96751f6 100644
--- a/src/main/scala/spray/json/BasicFormats.scala
+++ b/src/main/scala/spray/json/BasicFormats.scala
@@ -73,7 +73,10 @@ trait BasicFormats {
}
implicit object BigDecimalJsonFormat extends JsonFormat[BigDecimal] {
- def write(x: BigDecimal) = JsNumber(x)
+ def write(x: BigDecimal) = {
+ require(x ne null)
+ JsNumber(x)
+ }
def read(value: JsValue) = value match {
case JsNumber(x) => x
case x => deserializationError("Expected BigDecimal as JsNumber, but got " + x)
@@ -81,7 +84,10 @@ trait BasicFormats {
}
implicit object BigIntJsonFormat extends JsonFormat[BigInt] {
- def write(x: BigInt) = JsNumber(x)
+ def write(x: BigInt) = {
+ require(x ne null)
+ JsNumber(x)
+ }
def read(value: JsValue) = value match {
case JsNumber(x) => x.toBigInt
case x => deserializationError("Expected BigInt as JsNumber, but got " + x)
@@ -111,7 +117,10 @@ trait BasicFormats {
}
implicit object StringJsonFormat extends JsonFormat[String] {
- def write(x: String) = JsString(x)
+ def write(x: String) = {
+ require(x ne null)
+ JsString(x)
+ }
def read(value: JsValue) = value match {
case JsString(x) => x
case x => deserializationError("Expected String as JsString, but got " + x)
@@ -125,5 +134,4 @@ trait BasicFormats {
case x => deserializationError("Expected Symbol as JsString, but got " + x)
}
}
-
}
diff --git a/src/main/scala/spray/json/CompactPrinter.scala b/src/main/scala/spray/json/CompactPrinter.scala
index b7d2856..eca616f 100644
--- a/src/main/scala/spray/json/CompactPrinter.scala
+++ b/src/main/scala/spray/json/CompactPrinter.scala
@@ -31,7 +31,7 @@ trait CompactPrinter extends JsonPrinter {
}
}
- private def printObject(members: Map[String, JsValue], sb: StringBuilder) {
+ protected def printObject(members: Map[String, JsValue], sb: StringBuilder) {
sb.append('{')
printSeq(members, sb.append(',')) { m =>
printString(m._1, sb)
@@ -41,7 +41,7 @@ trait CompactPrinter extends JsonPrinter {
sb.append('}')
}
- private def printArray(elements: List[JsValue], sb: StringBuilder) {
+ protected def printArray(elements: List[JsValue], sb: StringBuilder) {
sb.append('[')
printSeq(elements, sb.append(','))(print(_, sb))
sb.append(']')
diff --git a/src/main/scala/spray/json/JsonPrinter.scala b/src/main/scala/spray/json/JsonPrinter.scala
index e47989e..d255e03 100644
--- a/src/main/scala/spray/json/JsonPrinter.scala
+++ b/src/main/scala/spray/json/JsonPrinter.scala
@@ -97,21 +97,12 @@ trait JsonPrinter extends (JsValue => String) {
}
object JsonPrinter {
- private[this] val mask = new Array[Int](4)
- private[this] def ascii(c: Char): Int = c & ((c - 127) >> 31) // branchless for `if (c <= 127) c else 0`
- private[this] def mark(c: Char): Unit = {
- val b = ascii(c)
- mask(b >> 5) |= 1 << (b & 0x1F)
- }
- private[this] def mark(range: scala.collection.immutable.NumericRange[Char]): Unit = range foreach (mark)
-
- mark('\u0000' to '\u0019')
- mark('\u007f')
- mark('"')
- mark('\\')
-
- def requiresEncoding(c: Char): Boolean = {
- val b = ascii(c)
- (mask(b >> 5) & (1 << (b & 0x1F))) != 0
- }
+ def requiresEncoding(c: Char): Boolean =
+ // from RFC 4627
+ // unescaped = %x20-21 / %x23-5B / %x5D-10FFFF
+ c match {
+ case '"' => true
+ case '\\' => true
+ case c => c < 0x20
+ }
} \ No newline at end of file
diff --git a/src/main/scala/spray/json/PrettyPrinter.scala b/src/main/scala/spray/json/PrettyPrinter.scala
index 0daaf71..5a2f142 100644
--- a/src/main/scala/spray/json/PrettyPrinter.scala
+++ b/src/main/scala/spray/json/PrettyPrinter.scala
@@ -29,7 +29,7 @@ trait PrettyPrinter extends JsonPrinter {
print(x, sb, 0)
}
- private def print(x: JsValue, sb: StringBuilder, indent: Int) {
+ protected def print(x: JsValue, sb: StringBuilder, indent: Int) {
x match {
case JsObject(x) => printObject(x, sb, indent)
case JsArray(x) => printArray(x, sb, indent)
@@ -37,7 +37,7 @@ trait PrettyPrinter extends JsonPrinter {
}
}
- private def printObject(members: Map[String, JsValue], sb: StringBuilder, indent: Int) {
+ protected def printObject(members: Map[String, JsValue], sb: StringBuilder, indent: Int) {
sb.append("{\n")
printSeq(members, sb.append(",\n")) { m =>
printIndent(sb, indent + Indent)
@@ -50,18 +50,19 @@ trait PrettyPrinter extends JsonPrinter {
sb.append("}")
}
- private def printArray(elements: List[JsValue], sb: StringBuilder, indent: Int) {
+ protected def printArray(elements: List[JsValue], sb: StringBuilder, indent: Int) {
sb.append('[')
printSeq(elements, sb.append(", "))(print(_, sb, indent))
sb.append(']')
}
- @tailrec
- private def printIndent(sb: StringBuilder, indent: Int) {
- if (indent > 0) {
- sb.append(' ')
- printIndent(sb, indent - 1)
- }
+ protected def printIndent(sb: StringBuilder, indent: Int) {
+ @tailrec def rec(indent: Int): Unit =
+ if (indent > 0) {
+ sb.append(' ')
+ rec(indent - 1)
+ }
+ rec(indent)
}
}
diff --git a/src/main/scala/spray/json/ProductFormats.scala b/src/main/scala/spray/json/ProductFormats.scala
index ad57221..971c7a6 100644
--- a/src/main/scala/spray/json/ProductFormats.scala
+++ b/src/main/scala/spray/json/ProductFormats.scala
@@ -17,6 +17,7 @@
package spray.json
import java.lang.reflect.Modifier
+import scala.util.control.NonFatal
/**
* Provides the helpers for constructing custom JsonFormat implementations for types implementing the Product trait
@@ -70,7 +71,7 @@ trait ProductFormats extends ProductFormatsInstances {
sys.error("Cannot determine field order of case class " + clazz.getName)
fields.map(_.getName)
} catch {
- case ex => throw new RuntimeException("Cannot automatically determine case class field names and order " +
+ case NonFatal(ex) => throw new RuntimeException("Cannot automatically determine case class field names and order " +
"for '" + clazz.getName + "', please use the 'jsonFormat' overload with explicit field name specification", ex)
}
}
diff --git a/src/main/scala/spray/json/package.scala b/src/main/scala/spray/json/package.scala
index 5bf5714..fe37c8b 100644
--- a/src/main/scala/spray/json/package.scala
+++ b/src/main/scala/spray/json/package.scala
@@ -40,6 +40,8 @@ package json {
}
private[json] class PimpedString(string: String) {
- def asJson: JsValue = JsonParser(string)
+ @deprecated("deprecated in favor of parseJson", "1.2.6")
+ def asJson: JsValue = parseJson
+ def parseJson: JsValue = JsonParser(string)
}
} \ No newline at end of file
diff --git a/src/test/scala/spray/json/AdditionalFormatsSpec.scala b/src/test/scala/spray/json/AdditionalFormatsSpec.scala
index 7129fdf..eafceb2 100644
--- a/src/test/scala/spray/json/AdditionalFormatsSpec.scala
+++ b/src/test/scala/spray/json/AdditionalFormatsSpec.scala
@@ -51,7 +51,7 @@ class AdditionalFormatsSpec extends Specification {
"properly read a Container[Container[List[Int]]] from JSON" in {
import ReaderProtocol._
- """{"content":{"content":[1,2,3]}}""".asJson.convertTo[Container[Container[List[Int]]]] mustEqual obj
+ """{"content":{"content":[1,2,3]}}""".parseJson.convertTo[Container[Container[List[Int]]]] mustEqual obj
}
}
diff --git a/src/test/scala/spray/json/CompactPrinterSpec.scala b/src/test/scala/spray/json/CompactPrinterSpec.scala
index 5485d7d..6a9560b 100644
--- a/src/test/scala/spray/json/CompactPrinterSpec.scala
+++ b/src/test/scala/spray/json/CompactPrinterSpec.scala
@@ -49,11 +49,16 @@ class CompactPrinterSpec extends Specification {
CompactPrinter(JsString("xyz")) mustEqual "\"xyz\""
}
"properly escape special chars in JsString" in {
- CompactPrinter(JsString("\"\\\b\f\n\r\t\u12AB")) mustEqual """"\"\\\b\f\n\r\t""" + "\\u12ab\""
- CompactPrinter(JsString("\u1000")) mustEqual "\"\\u1000\""
- CompactPrinter(JsString("\u0100")) mustEqual "\"\\u0100\""
+ 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")))
diff --git a/src/test/scala/spray/json/CustomFormatSpec.scala b/src/test/scala/spray/json/CustomFormatSpec.scala
index dcea4f2..2397abc 100644
--- a/src/test/scala/spray/json/CustomFormatSpec.scala
+++ b/src/test/scala/spray/json/CustomFormatSpec.scala
@@ -35,7 +35,7 @@ class CustomFormatSpec extends Specification with DefaultJsonProtocol {
"A custom JsonFormat built with 'asJsonObject'" should {
val value = MyType("bob", 42)
"correctly deserialize valid JSON content" in {
- """{ "name": "bob", "value": 42 }""".asJson.convertTo[MyType] mustEqual value
+ """{ "name": "bob", "value": 42 }""".parseJson.convertTo[MyType] mustEqual value
}
"support full round-trip (de)serialization" in {
value.toJson.convertTo[MyType] mustEqual value
diff --git a/src/test/scala/spray/json/JsonParserSpec.scala b/src/test/scala/spray/json/JsonParserSpec.scala
index a813c68..c6c1589 100644
--- a/src/test/scala/spray/json/JsonParserSpec.scala
+++ b/src/test/scala/spray/json/JsonParserSpec.scala
@@ -50,6 +50,9 @@ class JsonParserSpec extends Specification {
JsonParser(""""\"\\/\b\f\n\r\t"""") mustEqual JsString("\"\\/\b\f\n\r\t")
JsonParser("\"L\\" + "u00e4nder\"") mustEqual JsString("Länder")
}
+ "parse all representations of the slash (SOLIDUS) character in a JsString" in {
+ JsonParser( "\"" + "/\\/\\u002f" + "\"") mustEqual JsString("///")
+ }
"properly parse a simple JsObject" in (
JsonParser(""" { "key" :42, "key2": "value" }""") mustEqual
JsObject("key" -> JsNumber(42), "key2" -> JsString("value"))
diff --git a/src/test/scala/spray/json/PrettyPrinterSpec.scala b/src/test/scala/spray/json/PrettyPrinterSpec.scala
index 8b7bc2b..27137a8 100644
--- a/src/test/scala/spray/json/PrettyPrinterSpec.scala
+++ b/src/test/scala/spray/json/PrettyPrinterSpec.scala
@@ -58,7 +58,7 @@ class PrettyPrinterSpec extends Specification {
| "no": 0
| }, ["a", "b", null], false]
| }
- |}""".stripMargin.replace("\u00f8", "\\u00f8")
+ |}""".stripMargin
}
}
}
diff --git a/src/test/scala/spray/json/ReadmeSpec.scala b/src/test/scala/spray/json/ReadmeSpec.scala
index 89d3dbc..51a1ec5 100644
--- a/src/test/scala/spray/json/ReadmeSpec.scala
+++ b/src/test/scala/spray/json/ReadmeSpec.scala
@@ -25,7 +25,7 @@ class ReadmeSpec extends Specification {
import DefaultJsonProtocol._
val source = """{ "some": "JSON source" }"""
- val jsonAst = source.asJson
+ val jsonAst = source.parseJson
jsonAst mustEqual JsObject("some" -> JsString("JSON source"))
val json2 = jsonAst.prettyPrint
diff --git a/src/test/scala/spray/json/RoundTripSpecs.scala b/src/test/scala/spray/json/RoundTripSpecs.scala
new file mode 100644
index 0000000..51df48d
--- /dev/null
+++ b/src/test/scala/spray/json/RoundTripSpecs.scala
@@ -0,0 +1,63 @@
+package spray.json
+
+import org.specs2.mutable.Specification
+import org.scalacheck._
+import org.specs2.matcher.ScalaCheckMatchers
+
+object JsValueGenerators {
+ import Gen._
+ import Arbitrary.arbitrary
+
+ // some characters have special meaning in parboiled
+ // see org.parboiled.support.Chars, we have to exclude those
+ val parseableString: Gen[String] = arbitrary[String].map(_.filterNot(_ > 0xfd00))
+ 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)
+ 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 ScalaCheckMatchers {
+ 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
+ }
+ }
+}