summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMathias <mathias@spray.cc>2012-03-13 23:25:23 +0100
committerMathias <mathias@spray.cc>2012-03-13 23:25:23 +0100
commit7cb4d5e95af773d4aafc70b31d1e58fde64d3fc9 (patch)
tree45ba76bc5ce6554f671091770866f7c81f39e6f3
parent25b428758a36145e85033fba059ce85aad224929 (diff)
parent02851ede988090bbdafcabd97eaabd5aa334ee93 (diff)
downloadspray-json-7cb4d5e95af773d4aafc70b31d1e58fde64d3fc9.tar.gz
spray-json-7cb4d5e95af773d4aafc70b31d1e58fde64d3fc9.tar.bz2
spray-json-7cb4d5e95af773d4aafc70b31d1e58fde64d3fc9.zip
Merge branch 'release/1.1.1'v1.1.1
-rw-r--r--CHANGELOG6
-rw-r--r--README.markdown28
-rw-r--r--build.sbt15
-rw-r--r--notes/1.1.1.markdown4
-rw-r--r--src/main/ls/1.1.1.json22
-rw-r--r--src/main/scala/cc/spray/json/JsonParser.scala2
-rw-r--r--src/main/scala/cc/spray/json/ProductFormats.scala15
7 files changed, 79 insertions, 13 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 9f524e3..14acfb0 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,9 @@
+Version 1.1.1 (2012-03-13)
+--------------------------
+- Fixed significant performance problem in JsonParser
+- Improved automatic field name extraction for case classes
+
+
Version 1.1 (2012-02-01)
------------------------
- Added automatic case class field name extraction via new jsonFormatX overloads
diff --git a/README.markdown b/README.markdown
index 38c0739..b0c428c 100644
--- a/README.markdown
+++ b/README.markdown
@@ -11,11 +11,11 @@ It sports the following features:
### Installation
_spray-json_ is available from the [repo.spray.cc] repository.
-The latest release is `1.1.0` and is built against Scala 2.9.1.
+The latest release is `1.1.1` and is built against Scala 2.9.1.
If you use SBT you can include _spray-json_ in your project with
- "cc.spray" %% "spray-json" % "1.1.0"
+ "cc.spray" %% "spray-json" % "1.1.1"
_spray-json_ has only one dependency: the parsing library [parboiled][]
(which is also a dependency of _spray-server_ and _spray-client_, so if you use _spray-json_ with either of them you
@@ -148,7 +148,7 @@ Here is one way to do it:
class Color(val name: String, val red: Int, val green: Int, val blue: Int)
object MyJsonProtocol extends DefaultJsonProtocol {
- implicit object ColorJsonFormat extends JsonFormat[Color] {
+ implicit object ColorJsonFormat extends RootJsonFormat[Color] {
def write(c: Color) =
JsArray(JsString(c.name), JsNumber(c.red), JsNumber(c.green), JsNumber(c.blue))
@@ -171,7 +171,7 @@ You need to know that the color components are ordered "red, green, blue".
Another way would be to serialize `Color`s as JSON objects:
object MyJsonProtocol extends DefaultJsonProtocol {
- implicit object ColorJsonFormat extends JsonFormat[Color] {
+ implicit object ColorJsonFormat extends RootJsonFormat[Color] {
def write(c: Color) = JsObject(
"name" -> JsString(c.name),
"red" -> JsNumber(c.red),
@@ -192,6 +192,26 @@ This is a bit more verbose in its definition and the resulting JSON but transpor
JSON side. Note that this is the approach _spray-json_ uses for case classes.
+### JsonFormat vs. RootJsonFormat
+
+According to the JSON specification not all of the defined JSON value types are allowed at the root level of a JSON
+document. A JSON string for example (like `"foo"`) does not constitute a legal JSON document by itself.
+Only JSON objects or JSON arrays are allowed as JSON document roots.
+
+In order to distinguish, on the type-level, "regular" JsonFormats from the ones producing root-level JSON objects or
+arrays _spray-json_ defines the [`RootJsonFormat`][1] type, which is nothing but a marker specialization of `JsonFormat`.
+Libraries supporting _spray-json_ as a means of document serialization might choose to depend on a `RootJsonFormat[T]`
+for a custom type `T` (rather than a "plain" `JsonFormat[T]`), so as to not allow the rendering of illegal document
+roots. E.g., the `SprayJsonSupport` trait of _spray-server_ is one notable example of such a case.
+
+All default converters in the `DefaultJsonProtocol` producing JSON objects or arrays are actually implemented as
+`RootJsonFormat`. When "manually" implementing a `JsonFormat` for a custom type `T` (rather than relying on case class
+support) you should think about whether you'd like to use instances of `T` as JSON document roots and choose between
+a "plain" `JsonFormat` and a `RootJsonFormat` accordingly.
+
+ [1]: http://spray.github.com/spray/api/spray-json/cc/spray/json/RootJsonFormat.html
+
+
### JsonFormats for recursive Types
If your type is recursive such as
diff --git a/build.sbt b/build.sbt
index 73e4f98..d88b22b 100644
--- a/build.sbt
+++ b/build.sbt
@@ -1,6 +1,6 @@
name := "spray-json"
-version := "1.1.0"
+version := "1.1.1"
organization := "cc.spray"
@@ -20,11 +20,16 @@ scalacOptions := Seq("-deprecation", "-encoding", "utf8")
libraryDependencies ++= Seq(
"org.parboiled" % "parboiled-scala" % "1.0.2" % "compile",
- "org.specs2" %% "specs2" % "1.6.1" % "test"
+ "org.specs2" %% "specs2" % "1.7.1" % "test"
)
scaladocOptions <<= (name, version).map { (n, v) => Seq("-doc-title", n + " " + v) }
+
+///////////////
+// publishing
+///////////////
+
credentials += Credentials(Path.userHome / ".ivy2" / ".credentials")
publishMavenStyle := true
@@ -40,6 +45,12 @@ publishTo <<= version { version =>
}
}
+
+///////////////
+// ls-sbt
+///////////////
+
+
seq(lsSettings:_*)
(LsKeys.tags in LsKeys.lsync) := Seq("json")
diff --git a/notes/1.1.1.markdown b/notes/1.1.1.markdown
new file mode 100644
index 0000000..b28b82c
--- /dev/null
+++ b/notes/1.1.1.markdown
@@ -0,0 +1,4 @@
+This is a maintenance release introducing the following changes:
+
+- Fixed significant performance problem in JsonParser
+- Improved automatic field name extraction for case classes \ No newline at end of file
diff --git a/src/main/ls/1.1.1.json b/src/main/ls/1.1.1.json
new file mode 100644
index 0000000..f2326ee
--- /dev/null
+++ b/src/main/ls/1.1.1.json
@@ -0,0 +1,22 @@
+
+{
+ "organization":"cc.spray",
+ "name":"spray-json",
+ "version":"1.1.1",
+ "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/JsonParser.scala b/src/main/scala/cc/spray/json/JsonParser.scala
index 21b1d68..3886672 100644
--- a/src/main/scala/cc/spray/json/JsonParser.scala
+++ b/src/main/scala/cc/spray/json/JsonParser.scala
@@ -28,7 +28,7 @@ import java.lang.StringBuilder
object JsonParser extends Parser {
// the root rule
- def Json = rule { WhiteSpace ~ Value ~ EOI }
+ lazy val Json = rule { WhiteSpace ~ Value ~ EOI }
def JsonObject: Rule1[JsObject] = rule {
"{ " ~ zeroOrMore(Pair, separator = ", ") ~ "} " ~~> (JsObject(_ :_*))
diff --git a/src/main/scala/cc/spray/json/ProductFormats.scala b/src/main/scala/cc/spray/json/ProductFormats.scala
index 1f26259..3bd3783 100644
--- a/src/main/scala/cc/spray/json/ProductFormats.scala
+++ b/src/main/scala/cc/spray/json/ProductFormats.scala
@@ -495,16 +495,19 @@ trait ProductFormats {
protected def extractFieldNames(classManifest: ClassManifest[_]): Array[String] = {
val clazz = classManifest.erasure
try {
- val copyDefaultMethods = clazz.getMethods.filter(_.getName.startsWith("copy$default$"))
+ // copy methods have the form copy$default$N(), we need to sort them in order, but must account for the fact
+ // that lexical sorting of ...8(), ...9(), ...10() is not correct, so we extract N and sort by N.toInt
+ val copyDefaultMethods = clazz.getMethods.filter(_.getName.startsWith("copy$default$")).sortBy(
+ _.getName.drop("copy$default$".length).takeWhile(_ != '(').toInt)
val fields = clazz.getDeclaredFields.filterNot(_.getName.startsWith("$"))
if (copyDefaultMethods.length != fields.length)
- sys.error("Case class declares additional fields")
+ sys.error("Case class " + clazz.getName + " declares additional fields")
if (fields.zip(copyDefaultMethods).exists { case (f, m) => f.getType != m.getReturnType })
- sys.error("Cannot determine field order")
+ 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, " +
- "please use the 'jsonFormat' overload with explicit field name specification", ex)
+ case 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)
}
}
}
@@ -524,4 +527,4 @@ trait NullOptions extends ProductFormats {
val value = p.productElement(ix).asInstanceOf[T]
(fieldName, writer.write(value)) :: rest
}
-} \ No newline at end of file
+}