From 6cbaa4c1782bcb4060af9f8ef2fd72e737f7982a Mon Sep 17 00:00:00 2001 From: Mathias Date: Wed, 9 Nov 2011 11:42:25 +0100 Subject: Fix #6 (rename JsValue:fromJson to 'convertTo', add .prettyPrint and .compactPrint --- README.markdown | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'README.markdown') diff --git a/README.markdown b/README.markdown index fcf4ba9..3550dd1 100644 --- a/README.markdown +++ b/README.markdown @@ -37,15 +37,15 @@ and do one or more of the following: 2. Print a JSON AST back to a String using either the `CompactPrinter` or the `PrettyPrinter` - val json = PrettyPrinter(jsonAst) + val json = jsonAst.prettyPrint // or .compactPrint 3. Convert any Scala object to a JSON AST using the pimped `toJson` method val jsonAst = List(1, 2, 3).toJson -4. Convert a JSON AST to a Scala object with the `fromJson` method +4. Convert a JSON AST to a Scala object with the `convertTo` method - val myObject = jsonAst.fromJson[MyObjectType] + val myObject = jsonAst.convertTo[MyObjectType] In order to make steps 3 and 4 work for an object of type `T` you need to bring implicit values in scope that provide `JsonFormat[T]` instances for `T` and all types used by `T` (directly or indirectly). @@ -90,7 +90,7 @@ If your custom type `T` is a case class then augmenting the `DefaultJsonProtocol import MyJsonProtocol._ val json = Color("CadetBlue", 95, 158, 160).toJson - val color = json.fromJson[Color] + val color = json.convertTo[Color] The `jsonFormat` method reduces the boilerplate to a minimum, just pass it the companion object of your case class as well as the field names (in order) and it will return a ready-to-use `JsonFormat` for your type. @@ -139,7 +139,7 @@ Here is one way to do it: import MyJsonProtocol._ val json = Color("CadetBlue", 95, 158, 160).toJson - val color = json.fromJson[Color] + val color = json.convertTo[Color] This serializes `Color` instances as a JSON array, which is compact but does not make the elements semantics explicit. You need to know that the color components are ordered "red, green, blue". -- cgit v1.2.3 From 3ada25874b22c492907f1c72572a81726b83aa60 Mon Sep 17 00:00:00 2001 From: Mathias Date: Wed, 16 Nov 2011 09:46:02 +0100 Subject: Add 'JsonFormats for recursive Types' to README --- README.markdown | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'README.markdown') diff --git a/README.markdown b/README.markdown index 3550dd1..d8c3986 100644 --- a/README.markdown +++ b/README.markdown @@ -172,6 +172,20 @@ This is a bit more verbose in its definition and the resulting JSON but transpor Note that this is the approach _spray-json_ uses for case classes. +### JsonFormats for recursive Types + +If your type is recursive such as + + case class Foo(i: Int, foo: Foo) + +you need to wrap your format constructor with `lazyFormat` and supply an explicit type annotation: + + 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). + + ### API Documentation You can find the documentation for the _spray-json_ API here: -- cgit v1.2.3 From 6a5ccb2e6c37f7922db34f29c1f9cfd61a2397e8 Mon Sep 17 00:00:00 2001 From: Mathias Date: Wed, 7 Dec 2011 10:38:29 +0100 Subject: Move exception into package.scala, simplify throwing of (De)SerializationExceptions --- README.markdown | 4 ++-- src/main/scala/cc/spray/json/BasicFormats.scala | 24 +++++++++++----------- .../scala/cc/spray/json/CollectionFormats.scala | 13 ++++++------ .../cc/spray/json/DeserializationException.scala | 19 ----------------- src/main/scala/cc/spray/json/ProductFormats.scala | 6 ++---- .../cc/spray/json/SerializationException.scala | 19 ----------------- src/main/scala/cc/spray/json/StandardFormats.scala | 16 +++++++-------- src/main/scala/cc/spray/json/package.scala | 11 ++++++++-- .../cc/spray/json/AdditionalFormatsSpec.scala | 2 +- src/test/scala/cc/spray/json/ReadmeSpec.scala | 2 +- 10 files changed, 42 insertions(+), 74 deletions(-) delete mode 100644 src/main/scala/cc/spray/json/DeserializationException.scala delete mode 100644 src/main/scala/cc/spray/json/SerializationException.scala (limited to 'README.markdown') diff --git a/README.markdown b/README.markdown index d8c3986..9034bec 100644 --- a/README.markdown +++ b/README.markdown @@ -131,7 +131,7 @@ Here is one way to do it: case JsArray(JsString(name) :: JsNumber(red) :: JsNumber(green) :: JsNumber(blue) :: Nil) => { new Color(name, red.toInt, green.toInt, blue.toInt) } - case _ => throw new DeserializationException("Color expected") + case _ => deserializationError("Color expected") } } } @@ -163,7 +163,7 @@ Another way would be to serialize `Color`s as JSON objects: ) => { new Color(name.value, red.value.toInt, green.value.toInt, blue.value.toInt) } - case _ => throw new DeserializationException("Color expected") + case _ => deserializationError("Color expected") } } } diff --git a/src/main/scala/cc/spray/json/BasicFormats.scala b/src/main/scala/cc/spray/json/BasicFormats.scala index ebb0b31..0389e3a 100644 --- a/src/main/scala/cc/spray/json/BasicFormats.scala +++ b/src/main/scala/cc/spray/json/BasicFormats.scala @@ -26,7 +26,7 @@ trait BasicFormats { def write(x: Int) = JsNumber(x) def read(value: JsValue) = value match { case JsNumber(x) => x.intValue - case x => throw new DeserializationException("Expected Int as JsNumber, but got " + x) + case x => deserializationError("Expected Int as JsNumber, but got " + x) } } @@ -34,7 +34,7 @@ trait BasicFormats { def write(x: Long) = JsNumber(x) def read(value: JsValue) = value match { case JsNumber(x) => x.longValue - case x => throw new DeserializationException("Expected Long as JsNumber, but got " + x) + case x => deserializationError("Expected Long as JsNumber, but got " + x) } } @@ -43,7 +43,7 @@ trait BasicFormats { def read(value: JsValue) = value match { case JsNumber(x) => x.floatValue case JsNull => Float.NaN - case x => throw new DeserializationException("Expected Float as JsNumber, but got " + x) + case x => deserializationError("Expected Float as JsNumber, but got " + x) } } @@ -52,7 +52,7 @@ trait BasicFormats { def read(value: JsValue) = value match { case JsNumber(x) => x.doubleValue case JsNull => Double.NaN - case x => throw new DeserializationException("Expected Double as JsNumber, but got " + x) + case x => deserializationError("Expected Double as JsNumber, but got " + x) } } @@ -60,7 +60,7 @@ trait BasicFormats { def write(x: Byte) = JsNumber(x) def read(value: JsValue) = value match { case JsNumber(x) => x.byteValue - case x => throw new DeserializationException("Expected Byte as JsNumber, but got " + x) + case x => deserializationError("Expected Byte as JsNumber, but got " + x) } } @@ -68,7 +68,7 @@ trait BasicFormats { def write(x: Short) = JsNumber(x) def read(value: JsValue) = value match { case JsNumber(x) => x.shortValue - case x => throw new DeserializationException("Expected Short as JsNumber, but got " + x) + case x => deserializationError("Expected Short as JsNumber, but got " + x) } } @@ -76,7 +76,7 @@ trait BasicFormats { def write(x: BigDecimal) = JsNumber(x) def read(value: JsValue) = value match { case JsNumber(x) => x - case x => throw new DeserializationException("Expected BigDecimal as JsNumber, but got " + x) + case x => deserializationError("Expected BigDecimal as JsNumber, but got " + x) } } @@ -84,7 +84,7 @@ trait BasicFormats { def write(x: BigInt) = JsNumber(x) def read(value: JsValue) = value match { case JsNumber(x) => x.toBigInt - case x => throw new DeserializationException("Expected BigInt as JsNumber, but got " + x) + case x => deserializationError("Expected BigInt as JsNumber, but got " + x) } } @@ -98,7 +98,7 @@ trait BasicFormats { def read(value: JsValue) = value match { case JsTrue => true case JsFalse => false - case x => throw new DeserializationException("Expected JsBoolean, but got " + x) + case x => deserializationError("Expected JsBoolean, but got " + x) } } @@ -106,7 +106,7 @@ trait BasicFormats { def write(x: Char) = JsString(String.valueOf(x)) def read(value: JsValue) = value match { case JsString(x) if x.length == 1 => x.charAt(0) - case x => throw new DeserializationException("Expected Char as single-character JsString, but got " + x) + case x => deserializationError("Expected Char as single-character JsString, but got " + x) } } @@ -114,7 +114,7 @@ trait BasicFormats { def write(x: String) = JsString(x) def read(value: JsValue) = value match { case JsString(x) => x - case x => throw new DeserializationException("Expected String as JsString, but got " + x) + case x => deserializationError("Expected String as JsString, but got " + x) } } @@ -122,7 +122,7 @@ trait BasicFormats { def write(x: Symbol) = JsString(x.name) def read(value: JsValue) = value match { case JsString(x) => Symbol(x) - case x => throw new DeserializationException("Expected Symbol as JsString, but got " + x) + case x => deserializationError("Expected Symbol as JsString, but got " + x) } } diff --git a/src/main/scala/cc/spray/json/CollectionFormats.scala b/src/main/scala/cc/spray/json/CollectionFormats.scala index c6c3403..93e41ab 100644 --- a/src/main/scala/cc/spray/json/CollectionFormats.scala +++ b/src/main/scala/cc/spray/json/CollectionFormats.scala @@ -26,7 +26,7 @@ trait CollectionFormats { def write(list: List[T]) = JsArray(list.map(_.toJson)) def read(value: JsValue) = value match { case JsArray(elements) => elements.map(_.convertTo[T]) - case x => throw new DeserializationException("Expected List as JsArray, but got " + x) + case x => deserializationError("Expected List as JsArray, but got " + x) } } @@ -37,7 +37,7 @@ trait CollectionFormats { def write(array: Array[T]) = JsArray(array.map(_.toJson).toList) def read(value: JsValue) = value match { case JsArray(elements) => elements.map(_.convertTo[T]).toArray[T] - case x => throw new DeserializationException("Expected Array as JsArray, but got " + x) + case x => deserializationError("Expected Array as JsArray, but got " + x) } } @@ -55,9 +55,10 @@ trait CollectionFormats { } } def read(value: JsValue) = value match { - case JsObject(fields) => - fields.map(field => (JsString(field._1).convertTo[K], field._2.convertTo[V])) (collection.breakOut) - case x => throw new DeserializationException("Expected Map as JsObject, but got " + x) + case x: JsObject => x.fields.map { field => + (JsString(field._1).convertTo[K], field._2.convertTo[V]) + } (collection.breakOut) + case x => deserializationError("Expected Map as JsObject, but got " + x) } } @@ -86,7 +87,7 @@ trait CollectionFormats { def write(iterable: I) = JsArray(iterable.map(_.toJson).toList) def read(value: JsValue) = value match { case JsArray(elements) => f(elements.map(_.convertTo[T])) - case x => throw new DeserializationException("Expected Collection as JsArray, but got " + x) + case x => deserializationError("Expected Collection as JsArray, but got " + x) } } diff --git a/src/main/scala/cc/spray/json/DeserializationException.scala b/src/main/scala/cc/spray/json/DeserializationException.scala deleted file mode 100644 index b317a14..0000000 --- a/src/main/scala/cc/spray/json/DeserializationException.scala +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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 cc.spray.json - -class DeserializationException(msg: String, cause: Throwable = null) extends RuntimeException(msg, cause) \ No newline at end of file diff --git a/src/main/scala/cc/spray/json/ProductFormats.scala b/src/main/scala/cc/spray/json/ProductFormats.scala index d82cbf9..6a79f5f 100644 --- a/src/main/scala/cc/spray/json/ProductFormats.scala +++ b/src/main/scala/cc/spray/json/ProductFormats.scala @@ -16,8 +16,6 @@ package cc.spray.json -import annotation.tailrec - /** * Provides the helpers for constructing custom JsonFormat implementations for types implementing the Product trait * (especially case classes) @@ -416,9 +414,9 @@ trait ProductFormats { catch { case e: NoSuchElementException if !fieldFound => if (reader.isInstanceOf[OptionFormat[_]]) None.asInstanceOf[T] - else throw new DeserializationException("Object is missing required member '" + fieldName + "'", e) + else deserializationError("Object is missing required member '" + fieldName + "'", e) } - case _ => throw new DeserializationException("Object expected") + case _ => deserializationError("Object expected") } } } diff --git a/src/main/scala/cc/spray/json/SerializationException.scala b/src/main/scala/cc/spray/json/SerializationException.scala deleted file mode 100644 index f8cbc00..0000000 --- a/src/main/scala/cc/spray/json/SerializationException.scala +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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 cc.spray.json - -class SerializationException(msg: String) extends RuntimeException(msg) \ No newline at end of file diff --git a/src/main/scala/cc/spray/json/StandardFormats.scala b/src/main/scala/cc/spray/json/StandardFormats.scala index 277dfc9..78bc539 100644 --- a/src/main/scala/cc/spray/json/StandardFormats.scala +++ b/src/main/scala/cc/spray/json/StandardFormats.scala @@ -48,8 +48,8 @@ trait StandardFormats { def read(value: JsValue) = (value.convertTo(safeReader[A]), value.convertTo(safeReader[B])) match { case (Right(a), _: Left[_, _]) => Left(a) case (_: Left[_, _], Right(b)) => Right(b) - case (_: Right[_, _], _: Right[_, _]) => throw new DeserializationException("Ambiguous Either value: can be read as both, Left and Right, values") - case (Left(ea), Left(eb)) => throw new DeserializationException("Could not read Either value:\n" + ea + "---------- and ----------\n" + eb) + case (_: Right[_, _], _: Right[_, _]) => deserializationError("Ambiguous Either value: can be read as both, Left and Right, values") + case (Left(ea), Left(eb)) => deserializationError("Could not read Either value:\n" + ea + "---------- and ----------\n" + eb) } } @@ -62,7 +62,7 @@ trait StandardFormats { def write(t: (A, B)) = JsArray(t._1.toJson, t._2.toJson) def read(value: JsValue) = value match { case JsArray(a :: b :: Nil) => (a.convertTo[A], b.convertTo[B]) - case x => throw new DeserializationException("Expected Tuple2 as JsArray, but got " + x) + case x => deserializationError("Expected Tuple2 as JsArray, but got " + x) } } @@ -70,7 +70,7 @@ trait StandardFormats { def write(t: (A, B, C)) = JsArray(t._1.toJson, t._2.toJson, t._3.toJson) def read(value: JsValue) = value match { case JsArray(a :: b :: c :: Nil) => (a.convertTo[A], b.convertTo[B], c.convertTo[C]) - case x => throw new DeserializationException("Expected Tuple3 as JsArray, but got " + x) + case x => deserializationError("Expected Tuple3 as JsArray, but got " + x) } } @@ -78,7 +78,7 @@ trait StandardFormats { def write(t: (A, B, C, D)) = JsArray(t._1.toJson, t._2.toJson, t._3.toJson, t._4.toJson) def read(value: JsValue) = value match { case JsArray(a :: b :: c :: d :: Nil) => (a.convertTo[A], b.convertTo[B], c.convertTo[C], d.convertTo[D]) - case x => throw new DeserializationException("Expected Tuple4 as JsArray, but got " + x) + case x => deserializationError("Expected Tuple4 as JsArray, but got " + x) } } @@ -88,7 +88,7 @@ trait StandardFormats { def read(value: JsValue) = value match { case JsArray(a :: b :: c :: d :: e :: Nil) => (a.convertTo[A], b.convertTo[B], c.convertTo[C], d.convertTo[D], e.convertTo[E]) - case x => throw new DeserializationException("Expected Tuple5 as JsArray, but got " + x) + case x => deserializationError("Expected Tuple5 as JsArray, but got " + x) } } } @@ -99,7 +99,7 @@ trait StandardFormats { def read(value: JsValue) = value match { case JsArray(a :: b :: c :: d :: e :: f :: Nil) => (a.convertTo[A], b.convertTo[B], c.convertTo[C], d.convertTo[D], e.convertTo[E], f.convertTo[F]) - case x => throw new DeserializationException("Expected Tuple6 as JsArray, but got " + x) + case x => deserializationError("Expected Tuple6 as JsArray, but got " + x) } } } @@ -110,7 +110,7 @@ trait StandardFormats { def read(value: JsValue) = value match { case JsArray(a :: b :: c :: d :: e :: f :: g :: Nil) => (a.convertTo[A], b.convertTo[B], c.convertTo[C], d.convertTo[D], e.convertTo[E], f.convertTo[F], g.convertTo[G]) - case x => throw new DeserializationException("Expected Tuple7 as JsArray, but got " + x) + case x => deserializationError("Expected Tuple7 as JsArray, but got " + x) } } } diff --git a/src/main/scala/cc/spray/json/package.scala b/src/main/scala/cc/spray/json/package.scala index f080312..1bc592f 100644 --- a/src/main/scala/cc/spray/json/package.scala +++ b/src/main/scala/cc/spray/json/package.scala @@ -19,8 +19,11 @@ package cc.spray package object json { type JsField = (String, JsValue) - - def jsonReader[T](implicit reader: JsonReader[T]) = reader + + def deserializationError(msg: String, cause: Throwable = null) = throw new DeserializationException(msg, cause) + def serializationError(msg: String) = throw new SerializationException(msg) + + def jsonReader[T](implicit reader: JsonReader[T]) = reader def jsonWriter[T](implicit writer: JsonWriter[T]) = writer implicit def pimpAny[T](any: T): PimpedAny[T] = new PimpedAny(any) @@ -28,6 +31,10 @@ package object json { } package json { + + class DeserializationException(msg: String, cause: Throwable = null) extends RuntimeException(msg, cause) + class SerializationException(msg: String) extends RuntimeException(msg) + private[json] class PimpedAny[T](any: T) { def toJson(implicit writer: JsonWriter[T]): JsValue = writer.write(any) } diff --git a/src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala b/src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala index f40fa37..217ae4a 100644 --- a/src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala +++ b/src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala @@ -27,7 +27,7 @@ class AdditionalFormatsSpec extends Specification { 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 _ => throw new DeserializationException("Unexpected format: " + value.toString) + case _ => deserializationError("Unexpected format: " + value.toString) } } } diff --git a/src/test/scala/cc/spray/json/ReadmeSpec.scala b/src/test/scala/cc/spray/json/ReadmeSpec.scala index 3514d41..7e1d246 100644 --- a/src/test/scala/cc/spray/json/ReadmeSpec.scala +++ b/src/test/scala/cc/spray/json/ReadmeSpec.scala @@ -66,7 +66,7 @@ class ReadmeSpec extends Specification { case JsArray(JsString(name) :: JsNumber(red) :: JsNumber(green) :: JsNumber(blue) :: Nil) => { new Color(name, red.toInt, green.toInt, blue.toInt) } - case _ => throw new DeserializationException("Color expected") + case _ => deserializationError("Color expected") } } } -- cgit v1.2.3 From 1c5fec0d5a860a61e3e79a55be4298f7d54cc269 Mon Sep 17 00:00:00 2001 From: James Earl Douglas Date: Tue, 3 Jan 2012 08:18:53 -0800 Subject: fix the embedded links to parboiled --- README.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'README.markdown') diff --git a/README.markdown b/README.markdown index 9034bec..f33b132 100644 --- a/README.markdown +++ b/README.markdown @@ -3,7 +3,7 @@ _spray-json_ is a lightweight, clean and efficient [JSON] implementation in Scal It sports the following features: * Simple immutable model of the JSON language elements -* An efficient JSON PEG parser (implemented with [parboiled]) +* 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) @@ -17,7 +17,7 @@ If you use SBT (0.7.x) you can include _spray-json_ in your project with val sprayJson = "cc.spray.json" %% "spray-json" % "1.0.1" % "compile" withSources() -_spray-json_ has only one dependency: the parsing library [parboiled] +_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 are not incurring any additional dependency). -- cgit v1.2.3 From ae6bfb608e007f7d652383e618b9c6af7b726fc7 Mon Sep 17 00:00:00 2001 From: James Earl Douglas Date: Tue, 3 Jan 2012 08:19:54 -0800 Subject: fix typo --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'README.markdown') diff --git a/README.markdown b/README.markdown index f33b132..fb35301 100644 --- a/README.markdown +++ b/README.markdown @@ -30,7 +30,7 @@ Just bring all relevant elements in scope with and do one or more of the following: -1. Parse a JSON string into it's Abstract Syntax Tree (AST) representation +1. Parse a JSON string into its Abstract Syntax Tree (AST) representation val json = """{ "some": "JSON source" }""" val jsonAst = JsonParser(json) -- cgit v1.2.3 From cfbc301ecaaac325d3a1ad87305107d2cc785b7d Mon Sep 17 00:00:00 2001 From: Mathias Date: Tue, 17 Jan 2012 17:22:43 +0100 Subject: Pimp Strings with 'asJson' --- README.markdown | 4 ++-- src/main/scala/cc/spray/json/package.scala | 8 ++++++-- src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala | 2 +- src/test/scala/cc/spray/json/CustomFormatSpec.scala | 2 +- src/test/scala/cc/spray/json/ReadmeSpec.scala | 4 ++-- 5 files changed, 12 insertions(+), 8 deletions(-) (limited to 'README.markdown') diff --git a/README.markdown b/README.markdown index fb35301..3ff1d02 100644 --- a/README.markdown +++ b/README.markdown @@ -32,8 +32,8 @@ and do one or more of the following: 1. Parse a JSON string into its Abstract Syntax Tree (AST) representation - val json = """{ "some": "JSON source" }""" - val jsonAst = JsonParser(json) + val source = """{ "some": "JSON source" }""" + val jsonAst = source.asJson // or JsonParser(source) 2. Print a JSON AST back to a String using either the `CompactPrinter` or the `PrettyPrinter` diff --git a/src/main/scala/cc/spray/json/package.scala b/src/main/scala/cc/spray/json/package.scala index 1bc592f..ec12939 100644 --- a/src/main/scala/cc/spray/json/package.scala +++ b/src/main/scala/cc/spray/json/package.scala @@ -26,8 +26,8 @@ package object json { def jsonReader[T](implicit reader: JsonReader[T]) = reader def jsonWriter[T](implicit writer: JsonWriter[T]) = writer - implicit def pimpAny[T](any: T): PimpedAny[T] = new PimpedAny(any) - + implicit def pimpAny[T](any: T) = new PimpedAny(any) + implicit def pimpString(string: String) = new PimpedString(string) } package json { @@ -38,4 +38,8 @@ package json { private[json] class PimpedAny[T](any: T) { def toJson(implicit writer: JsonWriter[T]): JsValue = writer.write(any) } + + private[json] class PimpedString(string: String) { + def asJson: JsValue = JsonParser(string) + } } \ No newline at end of file diff --git a/src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala b/src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala index 217ae4a..f25c9d8 100644 --- a/src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala +++ b/src/test/scala/cc/spray/json/AdditionalFormatsSpec.scala @@ -51,7 +51,7 @@ class AdditionalFormatsSpec extends Specification { "properly read a Container[Container[List[Int]]] from JSON" in { import ReaderProtocol._ - JsonParser("""{"content":{"content":[1,2,3]}}""").convertTo[Container[Container[List[Int]]]] mustEqual obj + """{"content":{"content":[1,2,3]}}""".asJson.convertTo[Container[Container[List[Int]]]] mustEqual obj } } diff --git a/src/test/scala/cc/spray/json/CustomFormatSpec.scala b/src/test/scala/cc/spray/json/CustomFormatSpec.scala index 237e3f4..e8feb59 100644 --- a/src/test/scala/cc/spray/json/CustomFormatSpec.scala +++ b/src/test/scala/cc/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 { - JsonParser("""{ "name": "bob", "value": 42 }""").convertTo[MyType] mustEqual value + """{ "name": "bob", "value": 42 }""".asJson.convertTo[MyType] mustEqual value } "support full round-trip (de)serialization" in { value.toJson.convertTo[MyType] mustEqual value diff --git a/src/test/scala/cc/spray/json/ReadmeSpec.scala b/src/test/scala/cc/spray/json/ReadmeSpec.scala index 7e1d246..d60aa69 100644 --- a/src/test/scala/cc/spray/json/ReadmeSpec.scala +++ b/src/test/scala/cc/spray/json/ReadmeSpec.scala @@ -24,8 +24,8 @@ class ReadmeSpec extends Specification { "behave as expected" in { import DefaultJsonProtocol._ - val json = """{ "some": "JSON source" }""" - val jsonAst = JsonParser(json) + val source = """{ "some": "JSON source" }""" + val jsonAst = source.asJson jsonAst mustEqual JsObject("some" -> JsString("JSON source")) val json2 = PrettyPrinter(jsonAst) -- cgit v1.2.3 From 86a975a4857bf6ee207e30fe4a7fc427345fb08b Mon Sep 17 00:00:00 2001 From: Mathias Date: Wed, 1 Feb 2012 15:17:39 +0100 Subject: Update README --- README.markdown | 89 +++++++++++++++------------ src/test/scala/cc/spray/json/ReadmeSpec.scala | 6 +- 2 files changed, 53 insertions(+), 42 deletions(-) (limited to 'README.markdown') diff --git a/README.markdown b/README.markdown index 3ff1d02..cca4603 100644 --- a/README.markdown +++ b/README.markdown @@ -10,15 +10,16 @@ It sports the following features: ### Installation -_spray-json_ is available from the [scala-tools.org] repositories. -The latest release is `1.0.1` and is built against Scala 2.9.1. +_spray-json_ is available from the [repo.spray.cc] repositories. +The latest release is `1.1.0` and is built against Scala 2.9.1. -If you use SBT (0.7.x) you can include _spray-json_ in your project with +If you use SBT you can include _spray-json_ in your project with - val sprayJson = "cc.spray.json" %% "spray-json" % "1.0.1" % "compile" withSources() + "cc.spray" %% "spray-json" % "1.1.0" _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 are not incurring any additional dependency). +(which is also a dependency of _spray-server_ and _spray-client_, so if you use _spray-json_ with either of them you +are not incurring any additional dependency). ### Usage @@ -54,16 +55,24 @@ The way you normally do this is via a "JsonProtocol". ### JsonProtocol -_spray-json_ uses [SJSON]s Scala-idiomatic type-class-based approach to connect an existing type `T` with the logic how to (de)serialize its instances to and from JSON. (In fact _spray-json_ even reuses some of [SJSON]s code, see the 'Credits' section below). +_spray-json_ uses [SJSON]s Scala-idiomatic type-class-based approach to connect an existing type `T` with the logic how +to (de)serialize its instances to and from JSON. (In fact _spray-json_ even reuses some of [SJSON]s code, see the +'Credits' section below). This approach has the advantage of not requiring any change (or even access) to `T`s source code. All (de)serialization -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. +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. +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. -_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`: +_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 @@ -74,7 +83,8 @@ Here are the types already taken care of by the `DefaultJsonProtocol`: * collection.{Iterable, Seq, IndexedSeq, LinearSeq, Set} * JsValue -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. +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 @@ -84,7 +94,7 @@ If your custom type `T` is a case class then augmenting the `DefaultJsonProtocol case class Color(name: String, red: Int, green: Int, blue: Int) object MyJsonProtocol extends DefaultJsonProtocol { - implicit val colorFormat = jsonFormat(Color, "name", "red", "green", "blue") + implicit val colorFormat = jsonFormat4(Color) } import MyJsonProtocol._ @@ -92,26 +102,32 @@ If your custom type `T` is a case class then augmenting the `DefaultJsonProtocol val json = Color("CadetBlue", 95, 158, 160).toJson val color = json.convertTo[Color] -The `jsonFormat` method reduces the boilerplate to a minimum, just pass it the companion object of your case class as -well as the field names (in order) and it will return a ready-to-use `JsonFormat` for your type. -There is one quirk though: If you explicitly declare the companion object for your case class the notation above will +The `jsonFormatX` methods reduce the boilerplate to a minimum, just pass the right one the companion object of your +case class and it will return a ready-to-use `JsonFormat` for your type (the right one is the one matching the number +of arguments to your case class constructor, e.g. if your case class has 13 fields you need to use the `jsonFormat13` +method). The `jsonFormatX` methods try to extract the field names of your case class before calling the more general +`jsonFormat` overloads, which let you specify the field name manually. So, if spray-json has trouble determining the +field names or if your JSON objects use member names that differ from the case class fields you can also use +`jsonFormat` directly. + +There is one additional quirk: If you explicitly declare the companion object for your case class the notation above will stop working. You'll have to explicitly refer to the companion objects `apply` method to fix this: case class Color(name: String, red: Int, green: Int, blue: Int) object Color object MyJsonProtocol extends DefaultJsonProtocol { - implicit val colorFormat = jsonFormat(Color.apply, "name", "red", "green", "blue") + implicit val colorFormat = jsonFormat4(Color.apply) } -If your case class is generic in that it takes type parameters itself the `jsonFormat` method can also help you. +If your case class is generic in that it takes type parameters itself the `jsonFormat` methods can also help you. However, there is a little more boilerplate required as you need to add context bounds for all type parameters and explicitly refer to the case classes `apply` method as in this example: case class NamedList[A](name: String, items: List[A]) object MyJsonProtocol extends DefaultJsonProtocol { - implicit def namedListFormat[A :JsonFormat] = jsonFormat(NamedList.apply[A], "name", "items") + implicit def namedListFormat[A :JsonFormat] = jsonFormat2(NamedList.apply[A]) } @@ -124,13 +140,12 @@ Here is one way to do it: object MyJsonProtocol extends DefaultJsonProtocol { implicit object ColorJsonFormat extends JsonFormat[Color] { - def write(c: Color) = { + def write(c: Color) = JsArray(JsString(c.name), JsNumber(c.red), JsNumber(c.green), JsNumber(c.blue)) - } + def read(value: JsValue) = value match { - case JsArray(JsString(name) :: JsNumber(red) :: JsNumber(green) :: JsNumber(blue) :: Nil) => { + case JsArray(JsString(name) :: JsNumber(red) :: JsNumber(green) :: JsNumber(blue) :: Nil) => new Color(name, red.toInt, green.toInt, blue.toInt) - } case _ => deserializationError("Color expected") } } @@ -146,30 +161,26 @@ 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 { + object MyJsonProtocol extends DefaultJsonProtocol { implicit object ColorJsonFormat extends JsonFormat[Color] { def write(c: Color) = JsObject( - JsField("name", c.name), - JsField("red", c.red), - JsField("green", c.green), - JsField("blue", c.blue) + "name" -> JsString(c.name), + "red" -> JsNumber(c.red), + "green" -> JsNumber(c.green), + "blue" -> JsNumber(c.blue) ) - def read(value: JsValue) = value match { - case JsObject( - JsField("name", JsString(name)), - JsField("red", JsNumber(red)), - JsField("green", JsNumber(green)), - JsField("blue", JsNumber(blue)) - ) => { - new Color(name.value, red.value.toInt, green.value.toInt, blue.value.toInt) + def read(value: JsValue) = { + value.asJsObject.getFields("name", "red", "green", "blue") match { + case Seq(JsString(name), JsNumber(red), JsNumber(green), JsNumber(blue)) => + new Color(name, red.toInt, green.toInt, blue.toInt) + case _ => throw new DeserializationException("Color expected") } - case _ => deserializationError("Color expected") } } } -This is a bit more verbose in its definition and the resulting JSON but transports the field semantics over to the JSON side. -Note that this is the approach _spray-json_ uses for case classes. +This is a bit more verbose in its definition and the resulting JSON but transports the field semantics over to the +JSON side. Note that this is the approach _spray-json_ uses for case classes. ### JsonFormats for recursive Types @@ -214,7 +225,7 @@ _spray-json_ project under the project’s open source license. [JSON]: http://json.org [parboiled]: http://parboiled.org - [scala-tools.org]: http://scala-tools.org + [repo.spray.cc]: http://repo.spray.cc [SJSON]: https://github.com/debasishg/sjson [Databinder-Dispatch]: https://github.com/n8han/Databinder-Dispatch [APL 2.0]: http://www.apache.org/licenses/LICENSE-2.0 diff --git a/src/test/scala/cc/spray/json/ReadmeSpec.scala b/src/test/scala/cc/spray/json/ReadmeSpec.scala index e67312d..791720f 100644 --- a/src/test/scala/cc/spray/json/ReadmeSpec.scala +++ b/src/test/scala/cc/spray/json/ReadmeSpec.scala @@ -28,7 +28,7 @@ class ReadmeSpec extends Specification { val jsonAst = source.asJson jsonAst mustEqual JsObject("some" -> JsString("JSON source")) - val json2 = PrettyPrinter(jsonAst) + val json2 = jsonAst.prettyPrint json2 mustEqual """{ | "some": "JSON source" @@ -45,7 +45,7 @@ class ReadmeSpec extends Specification { "The case class example" should { "behave as expected" in { object MyJsonProtocol extends DefaultJsonProtocol { - implicit val colorFormat = jsonFormat(Color, "name", "red", "green", "blue") + implicit val colorFormat = jsonFormat4(Color) } import MyJsonProtocol._ color.toJson.convertTo[Color] mustEqual color @@ -60,7 +60,7 @@ class ReadmeSpec extends Specification { JsArray(JsString(c.name), JsNumber(c.red), JsNumber(c.green), JsNumber(c.blue)) def read(value: JsValue) = value match { - case JsArray(Seq(JsString(name), JsNumber(red), JsNumber(green), JsNumber(blue))) => + case JsArray(JsString(name) :: JsNumber(red) :: JsNumber(green) :: JsNumber(blue) :: Nil) => new Color(name, red.toInt, green.toInt, blue.toInt) case _ => deserializationError("Color expected") } -- cgit v1.2.3 From 129760aa1a75c62e6cd2088fa6ad37ff7ee35d68 Mon Sep 17 00:00:00 2001 From: Mathias Date: Fri, 3 Feb 2012 09:40:07 +0100 Subject: Add NullOptions section to README --- README.markdown | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'README.markdown') diff --git a/README.markdown b/README.markdown index cca4603..38c0739 100644 --- a/README.markdown +++ b/README.markdown @@ -10,7 +10,7 @@ It sports the following features: ### Installation -_spray-json_ is available from the [repo.spray.cc] repositories. +_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. If you use SBT you can include _spray-json_ in your project with @@ -131,6 +131,15 @@ and explicitly refer to the case classes `apply` method as in this example: } +#### NullOptions + +The `NullOptions` trait supplies an alternative rendering mode for optional case class members. Normally optional +members that are undefined (`None`) are not rendered at all. By mixing in this trait into your custom JsonProtocol you +can enforce the rendering of undefined members as `null`. +(Note that this only affect JSON writing, spray-json will always read missing optional members as well as `null` +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. -- cgit v1.2.3