Skip to content

Commit

Permalink
Merge branch 'release/1.1.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
sirthias committed Mar 13, 2012
2 parents 25b4287 + 02851ed commit 7cb4d5e
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 13 deletions.
6 changes: 6 additions & 0 deletions 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
Expand Down
28 changes: 24 additions & 4 deletions README.markdown
Expand Up @@ -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
Expand Down Expand Up @@ -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))

Expand All @@ -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),
Expand All @@ -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
Expand Down
15 changes: 13 additions & 2 deletions build.sbt
@@ -1,6 +1,6 @@
name := "spray-json"

version := "1.1.0"
version := "1.1.1"

organization := "cc.spray"

Expand All @@ -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
Expand All @@ -40,6 +45,12 @@ publishTo <<= version { version =>
}
}


///////////////
// ls-sbt
///////////////


seq(lsSettings:_*)

(LsKeys.tags in LsKeys.lsync) := Seq("json")
Expand Down
4 changes: 4 additions & 0 deletions 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
22 changes: 22 additions & 0 deletions 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
}
2 changes: 1 addition & 1 deletion src/main/scala/cc/spray/json/JsonParser.scala
Expand Up @@ -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(_ :_*))
Expand Down
15 changes: 9 additions & 6 deletions src/main/scala/cc/spray/json/ProductFormats.scala
Expand Up @@ -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)
}
}
}
Expand All @@ -524,4 +527,4 @@ trait NullOptions extends ProductFormats {
val value = p.productElement(ix).asInstanceOf[T]
(fieldName, writer.write(value)) :: rest
}
}
}

0 comments on commit 7cb4d5e

Please sign in to comment.