diff options
author | Mathias <mathias@spray.cc> | 2012-03-13 23:25:23 +0100 |
---|---|---|
committer | Mathias <mathias@spray.cc> | 2012-03-13 23:25:23 +0100 |
commit | 7cb4d5e95af773d4aafc70b31d1e58fde64d3fc9 (patch) | |
tree | 45ba76bc5ce6554f671091770866f7c81f39e6f3 | |
parent | 25b428758a36145e85033fba059ce85aad224929 (diff) | |
parent | 02851ede988090bbdafcabd97eaabd5aa334ee93 (diff) | |
download | spray-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-- | CHANGELOG | 6 | ||||
-rw-r--r-- | README.markdown | 28 | ||||
-rw-r--r-- | build.sbt | 15 | ||||
-rw-r--r-- | notes/1.1.1.markdown | 4 | ||||
-rw-r--r-- | src/main/ls/1.1.1.json | 22 | ||||
-rw-r--r-- | src/main/scala/cc/spray/json/JsonParser.scala | 2 | ||||
-rw-r--r-- | src/main/scala/cc/spray/json/ProductFormats.scala | 15 |
7 files changed, 79 insertions, 13 deletions
@@ -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 @@ -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 +} |