diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/main/scala/spray/json/BasicFormats.scala | 2 | ||||
-rw-r--r-- | src/main/scala/spray/json/CompactPrinter.scala | 6 | ||||
-rw-r--r-- | src/main/scala/spray/json/JsValue.scala | 3 | ||||
-rw-r--r-- | src/main/scala/spray/json/JsonParser.scala | 35 | ||||
-rw-r--r-- | src/main/scala/spray/json/JsonPrinter.scala | 8 | ||||
-rw-r--r-- | src/main/scala/spray/json/PrettyPrinter.scala | 14 | ||||
-rw-r--r-- | src/main/scala/spray/json/SortedPrinter.scala | 25 | ||||
-rw-r--r-- | src/main/scala/spray/json/package.scala | 24 | ||||
-rw-r--r-- | src/test/scala/spray/json/BasicFormatsSpec.scala | 2 | ||||
-rw-r--r-- | src/test/scala/spray/json/JsonParserSpec.scala | 18 | ||||
-rw-r--r-- | src/test/scala/spray/json/RoundTripSpecs.scala | 16 | ||||
-rw-r--r-- | src/test/scala/spray/json/SortedPrinterSpec.scala | 65 | ||||
-rw-r--r-- | src/test/scala/spray/json/StandardFormatsSpec.scala | 12 |
13 files changed, 193 insertions, 37 deletions
diff --git a/src/main/scala/spray/json/BasicFormats.scala b/src/main/scala/spray/json/BasicFormats.scala index 65b9ecb..2e6342f 100644 --- a/src/main/scala/spray/json/BasicFormats.scala +++ b/src/main/scala/spray/json/BasicFormats.scala @@ -98,7 +98,7 @@ trait BasicFormats { implicit object UnitJsonFormat extends JsonFormat[Unit] { def write(x: Unit) = JsNumber(1) - def read(value: JsValue) {} + def read(value: JsValue): Unit = {} } implicit object BooleanJsonFormat extends JsonFormat[Boolean] { diff --git a/src/main/scala/spray/json/CompactPrinter.scala b/src/main/scala/spray/json/CompactPrinter.scala index a51583d..7260c2f 100644 --- a/src/main/scala/spray/json/CompactPrinter.scala +++ b/src/main/scala/spray/json/CompactPrinter.scala @@ -23,7 +23,7 @@ import java.lang.StringBuilder */ trait CompactPrinter extends JsonPrinter { - def print(x: JsValue, sb: StringBuilder) { + def print(x: JsValue, sb: StringBuilder): Unit = { x match { case JsObject(x) => printObject(x, sb) case JsArray(x) => printArray(x, sb) @@ -31,7 +31,7 @@ trait CompactPrinter extends JsonPrinter { } } - protected def printObject(members: Map[String, JsValue], sb: StringBuilder) { + protected def printObject(members: Map[String, JsValue], sb: StringBuilder): Unit = { sb.append('{') printSeq(members, sb.append(',')) { m => printString(m._1, sb) @@ -41,7 +41,7 @@ trait CompactPrinter extends JsonPrinter { sb.append('}') } - protected def printArray(elements: Seq[JsValue], sb: StringBuilder) { + protected def printArray(elements: Seq[JsValue], sb: StringBuilder): Unit = { sb.append('[') printSeq(elements, sb.append(','))(print(_, sb)) sb.append(']') diff --git a/src/main/scala/spray/json/JsValue.scala b/src/main/scala/spray/json/JsValue.scala index b21672d..08a673b 100644 --- a/src/main/scala/spray/json/JsValue.scala +++ b/src/main/scala/spray/json/JsValue.scala @@ -1,7 +1,7 @@ /* * Copyright (C) 2009-2011 Mathias Doenitz * Inspired by a similar implementation by Nathan Hamblen - * (https://github.com/n8han/Databinder-Dispatch) + * (https://github.com/dispatch/classic) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,7 @@ sealed abstract class JsValue { def toString(printer: (JsValue => String)) = printer(this) def compactPrint = CompactPrinter(this) def prettyPrint = PrettyPrinter(this) + def sortedPrint = SortedPrinter(this) def convertTo[T :JsonReader]: T = jsonReader[T].read(this) /** diff --git a/src/main/scala/spray/json/JsonParser.scala b/src/main/scala/spray/json/JsonParser.scala index b1e59d5..71c4c11 100644 --- a/src/main/scala/spray/json/JsonParser.scala +++ b/src/main/scala/spray/json/JsonParser.scala @@ -38,10 +38,14 @@ class JsonParser(input: ParserInput) { private[this] var cursorChar: Char = input.nextChar() private[this] var jsValue: JsValue = _ - def parseJsValue(): JsValue = { + def parseJsValue(): JsValue = + parseJsValue(false) + + def parseJsValue(allowTrailingInput: Boolean): JsValue = { ws() `value`() - require(EOI) + if (!allowTrailingInput) + require(EOI) jsValue } @@ -266,23 +270,25 @@ object ParserInput { private val UTF8 = Charset.forName("UTF-8") /** - * ParserInput reading directly off a byte array which is assumed to contain the UTF-8 encoded representation - * of the JSON input, without requiring a separate decoding step. + * ParserInput that allows to create a ParserInput from any randomly accessible indexed byte storage. */ - class ByteArrayBasedParserInput(bytes: Array[Byte]) extends DefaultParserInput { + abstract class IndexedBytesParserInput extends DefaultParserInput { + def length: Int + protected def byteAt(offset: Int): Byte + private val byteBuffer = ByteBuffer.allocate(4) private val charBuffer = CharBuffer.allocate(2) private val decoder = UTF8.newDecoder() def nextChar() = { _cursor += 1 - if (_cursor < bytes.length) (bytes(_cursor) & 0xFF).toChar else EOI + if (_cursor < length) (byteAt(_cursor) & 0xFF).toChar else EOI } def nextUtf8Char() = { @tailrec def decode(byte: Byte, remainingBytes: Int): Char = { byteBuffer.put(byte) if (remainingBytes > 0) { _cursor += 1 - if (_cursor < bytes.length) decode(bytes(_cursor), remainingBytes - 1) else ErrorChar + if (_cursor < length) decode(byteAt(_cursor), remainingBytes - 1) else ErrorChar } else { byteBuffer.flip() val coderResult = decoder.decode(byteBuffer, charBuffer, false) @@ -300,8 +306,8 @@ object ParserInput { result } else { _cursor += 1 - if (_cursor < bytes.length) { - val byte = bytes(_cursor) + if (_cursor < length) { + val byte = byteAt(_cursor) if (byte >= 0) byte.toChar // 7-Bit ASCII else if ((byte & 0xE0) == 0xC0) decode(byte, 1) // 2-byte UTF-8 sequence else if ((byte & 0xF0) == 0xE0) decode(byte, 2) // 3-byte UTF-8 sequence @@ -310,7 +316,16 @@ object ParserInput { } else EOI } } - def length = bytes.length + } + + /** + * ParserInput reading directly off a byte array which is assumed to contain the UTF-8 encoded representation + * of the JSON input, without requiring a separate decoding step. + */ + class ByteArrayBasedParserInput(bytes: Array[Byte]) extends IndexedBytesParserInput { + protected def byteAt(offset: Int): Byte = bytes(offset) + def length: Int = bytes.length + def sliceString(start: Int, end: Int) = new String(bytes, start, end - start, UTF8) def sliceCharArray(start: Int, end: Int) = UTF8.decode(ByteBuffer.wrap(java.util.Arrays.copyOfRange(bytes, start, end))).array() diff --git a/src/main/scala/spray/json/JsonPrinter.scala b/src/main/scala/spray/json/JsonPrinter.scala index 258fc5a..f132ab9 100644 --- a/src/main/scala/spray/json/JsonPrinter.scala +++ b/src/main/scala/spray/json/JsonPrinter.scala @@ -39,9 +39,9 @@ trait JsonPrinter extends (JsValue => String) { sb.toString } - def print(x: JsValue, sb: JStringBuilder) + def print(x: JsValue, sb: JStringBuilder): Unit - protected def printLeaf(x: JsValue, sb: JStringBuilder) { + protected def printLeaf(x: JsValue, sb: JStringBuilder): Unit = { x match { case JsNull => sb.append("null") case JsTrue => sb.append("true") @@ -52,7 +52,7 @@ trait JsonPrinter extends (JsValue => String) { } } - protected def printString(s: String, sb: JStringBuilder) { + protected def printString(s: String, sb: JStringBuilder): Unit = { import JsonPrinter._ @tailrec def firstToBeEncoded(ix: Int = 0): Int = if (ix == s.length) -1 else if (requiresEncoding(s.charAt(ix))) ix else firstToBeEncoded(ix + 1) @@ -85,7 +85,7 @@ trait JsonPrinter extends (JsValue => String) { sb.append('"') } - protected def printSeq[A](iterable: Iterable[A], printSeparator: => Unit)(f: A => Unit) { + protected def printSeq[A](iterable: Iterable[A], printSeparator: => Unit)(f: A => Unit): Unit = { var first = true iterable.foreach { a => if (first) first = false else printSeparator diff --git a/src/main/scala/spray/json/PrettyPrinter.scala b/src/main/scala/spray/json/PrettyPrinter.scala index 57cf35e..7526dab 100644 --- a/src/main/scala/spray/json/PrettyPrinter.scala +++ b/src/main/scala/spray/json/PrettyPrinter.scala @@ -25,11 +25,11 @@ import annotation.tailrec trait PrettyPrinter extends JsonPrinter { val Indent = 2 - def print(x: JsValue, sb: StringBuilder) { + def print(x: JsValue, sb: StringBuilder): Unit = { print(x, sb, 0) } - protected def print(x: JsValue, sb: StringBuilder, indent: Int) { + protected def print(x: JsValue, sb: StringBuilder, indent: Int): Unit = { x match { case JsObject(x) => printObject(x, sb, indent) case JsArray(x) => printArray(x, sb, indent) @@ -37,9 +37,11 @@ trait PrettyPrinter extends JsonPrinter { } } - protected def printObject(members: Map[String, JsValue], sb: StringBuilder, indent: Int) { + protected def organiseMembers(members: Map[String, JsValue]): Seq[(String, JsValue)] = members.toSeq + + protected def printObject(members: Map[String, JsValue], sb: StringBuilder, indent: Int): Unit = { sb.append("{\n") - printSeq(members, sb.append(",\n")) { m => + printSeq(organiseMembers(members), sb.append(",\n")) { m => printIndent(sb, indent + Indent) printString(m._1, sb) sb.append(": ") @@ -50,13 +52,13 @@ trait PrettyPrinter extends JsonPrinter { sb.append("}") } - protected def printArray(elements: Seq[JsValue], sb: StringBuilder, indent: Int) { + protected def printArray(elements: Seq[JsValue], sb: StringBuilder, indent: Int): Unit = { sb.append('[') printSeq(elements, sb.append(", "))(print(_, sb, indent)) sb.append(']') } - protected def printIndent(sb: StringBuilder, indent: Int) { + protected def printIndent(sb: StringBuilder, indent: Int): Unit = { @tailrec def rec(indent: Int): Unit = if (indent > 0) { sb.append(' ') diff --git a/src/main/scala/spray/json/SortedPrinter.scala b/src/main/scala/spray/json/SortedPrinter.scala new file mode 100644 index 0000000..28db225 --- /dev/null +++ b/src/main/scala/spray/json/SortedPrinter.scala @@ -0,0 +1,25 @@ +/* + * 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 spray.json + +trait SortedPrinter extends PrettyPrinter { + + override protected def organiseMembers(members: Map[String, JsValue]): Seq[(String, JsValue)] = + members.toSeq.sortBy(_._1) +} + +object SortedPrinter extends SortedPrinter diff --git a/src/main/scala/spray/json/package.scala b/src/main/scala/spray/json/package.scala index f79b99e..37d63c2 100644 --- a/src/main/scala/spray/json/package.scala +++ b/src/main/scala/spray/json/package.scala @@ -25,9 +25,14 @@ 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) = new PimpedAny(any) - implicit def pimpString(string: String) = new PimpedString(string) + + implicit def enrichAny[T](any: T) = new RichAny(any) + implicit def enrichString(string: String) = new RichString(string) + + @deprecated("use enrichAny", "1.3.4") + def pimpAny[T](any: T) = new PimpedAny(any) + @deprecated("use enrichString", "1.3.4") + def pimpString(string: String) = new PimpedString(string) } package json { @@ -35,13 +40,26 @@ package json { case class DeserializationException(msg: String, cause: Throwable = null, fieldNames: List[String] = Nil) extends RuntimeException(msg, cause) class SerializationException(msg: String) extends RuntimeException(msg) + private[json] class RichAny[T](any: T) { + def toJson(implicit writer: JsonWriter[T]): JsValue = writer.write(any) + } + + private[json] class RichString(string: String) { + @deprecated("deprecated in favor of parseJson", "1.2.6") + def asJson: JsValue = parseJson + def parseJson: JsValue = JsonParser(string) + } + + @deprecated("use RichAny", "1.3.4") private[json] class PimpedAny[T](any: T) { def toJson(implicit writer: JsonWriter[T]): JsValue = writer.write(any) } + @deprecated("use RichString", "1.3.4") private[json] class PimpedString(string: String) { @deprecated("deprecated in favor of parseJson", "1.2.6") def asJson: JsValue = parseJson def parseJson: JsValue = JsonParser(string) } + } diff --git a/src/test/scala/spray/json/BasicFormatsSpec.scala b/src/test/scala/spray/json/BasicFormatsSpec.scala index 00da813..8417df2 100644 --- a/src/test/scala/spray/json/BasicFormatsSpec.scala +++ b/src/test/scala/spray/json/BasicFormatsSpec.scala @@ -127,7 +127,7 @@ class BasicFormatsSpec extends Specification with DefaultJsonProtocol { ().toJson mustEqual JsNumber(1) } "convert a JsNumber to Unit" in { - JsNumber(1).convertTo[Unit] mustEqual () + JsNumber(1).convertTo[Unit] mustEqual (()) } } diff --git a/src/test/scala/spray/json/JsonParserSpec.scala b/src/test/scala/spray/json/JsonParserSpec.scala index 0f7ae7f..a97f021 100644 --- a/src/test/scala/spray/json/JsonParserSpec.scala +++ b/src/test/scala/spray/json/JsonParserSpec.scala @@ -74,7 +74,8 @@ class JsonParserSpec extends Specification { } "be reentrant" in { val largeJsonSource = scala.io.Source.fromInputStream(getClass.getResourceAsStream("/test.json")).mkString - List.fill(20)(largeJsonSource).par.map(JsonParser(_)).toList.map { + import scala.collection.parallel.immutable.ParSeq + ParSeq.fill(20)(largeJsonSource).map(JsonParser(_)).toList.map { _.asInstanceOf[JsObject].fields("questions").asInstanceOf[JsArray].elements.size } === List.fill(20)(100) } @@ -107,5 +108,18 @@ class JsonParserSpec extends Specification { | ^ |""".stripMargin } + + "parse multiple values when allowTrailingInput" in { + val parser = new JsonParser("""{"key":1}{"key":2}""") + parser.parseJsValue(true) === JsObject("key" -> JsNumber(1)) + parser.parseJsValue(true) === JsObject("key" -> JsNumber(2)) + } + "reject trailing input when !allowTrailingInput" in { + def parser = JsonParser("""{"key":1}x""") + parser must throwA[JsonParser.ParsingException].like { + case e: JsonParser.ParsingException => e.getMessage must contain("expected end-of-input") + } + } + } -}
\ No newline at end of file +} diff --git a/src/test/scala/spray/json/RoundTripSpecs.scala b/src/test/scala/spray/json/RoundTripSpecs.scala index 24b2354..6bee7b4 100644 --- a/src/test/scala/spray/json/RoundTripSpecs.scala +++ b/src/test/scala/spray/json/RoundTripSpecs.scala @@ -1,3 +1,19 @@ +/* + * 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 spray.json import org.specs2.mutable.Specification diff --git a/src/test/scala/spray/json/SortedPrinterSpec.scala b/src/test/scala/spray/json/SortedPrinterSpec.scala new file mode 100644 index 0000000..f91640e --- /dev/null +++ b/src/test/scala/spray/json/SortedPrinterSpec.scala @@ -0,0 +1,65 @@ +/* + * 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 spray.json + +import org.specs2.mutable._ + +class SortedPrinterSpec extends Specification { + + "The SortedPrinter" should { + "print a more complicated JsObject nicely aligned with fields sorted" in { + val obj = JsonParser { + """{ + | "Unic\u00f8de" : "Long string with newline\nescape", + | "Boolean no": false, + | "number": -1.2323424E-5, + | "key with \"quotes\"" : "string", + | "key with spaces": null, + | "simpleKey" : "some value", + | "zero": 0, + | "sub object" : { + | "sub key": 26.5, + | "a": "b", + | "array": [1, 2, { "yes":1, "no":0 }, ["a", "b", null], false] + | }, + | "Boolean yes":true + |}""".stripMargin + } + SortedPrinter(obj) mustEqual { + """{ + | "Boolean no": false, + | "Boolean yes": true, + | "Unic\u00f8de": "Long string with newline\nescape", + | "key with \"quotes\"": "string", + | "key with spaces": null, + | "number": -0.000012323424, + | "simpleKey": "some value", + | "sub object": { + | "a": "b", + | "array": [1, 2, { + | "no": 0, + | "yes": 1 + | }, ["a", "b", null], false], + | "sub key": 26.5 + | }, + | "zero": 0 + |}""".stripMargin + } + } + } + +} diff --git a/src/test/scala/spray/json/StandardFormatsSpec.scala b/src/test/scala/spray/json/StandardFormatsSpec.scala index 89f01ac..833f06a 100644 --- a/src/test/scala/spray/json/StandardFormatsSpec.scala +++ b/src/test/scala/spray/json/StandardFormatsSpec.scala @@ -69,7 +69,7 @@ class StandardFormatsSpec extends Specification with DefaultJsonProtocol { (42, 4.2).toJson mustEqual json } "be able to convert a JsArray to a (Int, Double)]" in { - json.convertTo[(Int, Double)] mustEqual (42, 4.2) + json.convertTo[(Int, Double)] mustEqual ((42, 4.2)) } } @@ -79,7 +79,7 @@ class StandardFormatsSpec extends Specification with DefaultJsonProtocol { (42, 4.2, 3).toJson mustEqual json } "be able to convert a JsArray to a (Int, Double, Int)]" in { - json.convertTo[(Int, Double, Int)] mustEqual (42, 4.2, 3) + json.convertTo[(Int, Double, Int)] mustEqual ((42, 4.2, 3)) } } "The tuple4Format" should { @@ -88,7 +88,7 @@ class StandardFormatsSpec extends Specification with DefaultJsonProtocol { (42, 4.2, 3, 4).toJson mustEqual json } "be able to convert a JsArray to a (Int, Double, Int, Int)]" in { - json.convertTo[(Int, Double, Int, Int)] mustEqual (42, 4.2, 3, 4) + json.convertTo[(Int, Double, Int, Int)] mustEqual ((42, 4.2, 3, 4)) } } "The tuple5Format" should { @@ -97,7 +97,7 @@ class StandardFormatsSpec extends Specification with DefaultJsonProtocol { (42, 4.2, 3, 4, 5).toJson mustEqual json } "be able to convert a JsArray to a (Int, Double, Int, Int, Int)]" in { - json.convertTo[(Int, Double, Int, Int, Int)] mustEqual (42, 4.2, 3, 4, 5) + json.convertTo[(Int, Double, Int, Int, Int)] mustEqual ((42, 4.2, 3, 4, 5)) } } "The tuple6Format" should { @@ -106,7 +106,7 @@ class StandardFormatsSpec extends Specification with DefaultJsonProtocol { (42, 4.2, 3, 4, 5, 6).toJson mustEqual json } "be able to convert a JsArray to a (Int, Double, Int, Int, Int, Int)]" in { - json.convertTo[(Int, Double, Int, Int, Int, Int)] mustEqual (42, 4.2, 3, 4, 5, 6) + json.convertTo[(Int, Double, Int, Int, Int, Int)] mustEqual ((42, 4.2, 3, 4, 5, 6)) } } "The tuple7Format" should { @@ -115,7 +115,7 @@ class StandardFormatsSpec extends Specification with DefaultJsonProtocol { (42, 4.2, 3, 4, 5, 6, 7).toJson mustEqual json } "be able to convert a JsArray to a (Int, Double, Int, Int, Int, Int, Int)]" in { - json.convertTo[(Int, Double, Int, Int, Int, Int, Int)] mustEqual (42, 4.2, 3, 4, 5, 6, 7) + json.convertTo[(Int, Double, Int, Int, Int, Int, Int)] mustEqual ((42, 4.2, 3, 4, 5, 6, 7)) } } } |